Skip to content

Open-rig miss buckets: miss_taxonomy --cities + open_rig_misses_258 (#263 Track 1) - #265

Merged
jonfroehlich merged 15 commits into
mainfrom
analysis/open-rig-misses-258
Oct 21, 2026
Merged

jonfroehlich merged 15 commits into
mainfrom
analysis/open-rig-misses-258

Conversation

@jonfroehlich

@jonfroehlich jonfroehlich commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

Track 1 of #263: bucket RampNet's misses on the open-imagery splits with the #46 taxonomy, to see whether the open-rig recall gap is calibration (sub_threshold) or labels (silent). Revised after review (findings 1 to 12 and the nits).

What changed

  • scripts/analysis/miss_taxonomy.py gains --cities (comma- or space-separated; pools the named splits that are in US_SPLITS; unknown or empty names are an error). With no flag, stdout and --json-out are byte-identical to main. --threshold 0.55 already worked.
  • scripts/analysis/open_rig_misses_258.py (new, CPU, --check):
    • Populations. Each open split on its own row. The GoPro Max pool is laurens_mapillary + bayonne only; budapest_district5 is HELD_OUT (low-confidence single-rater GT), so it gets its own row and is never pooled. Also morgantown (GoPro Max inside the reference), the Qualitative failure taxonomy: bucket each model's FP/FN from cached detections #46 reference (7 US splits) and the reference without paterson.
    • Thresholds and subsets. 0.30 and 0.55, each on all panos and on the unbiased subset (verdicts group != "top").
    • Statistics. Wilson and pano-bootstrap intervals on every share, plus the reading rule. A strict silent count (no floor peak within 2R) sits beside silent. Every recall-point excess over the reference carries a pano-bootstrap interval, and so does (sub excess − silent excess).
    • Detail. Near/far, merged separations, sub-threshold peak scores, nulls, and camera models from records.jsonl.
    • Output and check. It writes analysis_out/open_rig_misses_258.json (LF-pinned) and generates every table in the doc between BEGIN/END GENERATED markers. --check fails if either the JSON or a doc table drifts.
  • docs/open_rig_misses_258.md. In order: corrected bucket definitions; the reading rule with a plain account of when the bootstrap test was chosen; which table each reading uses; the result; the generated tables; the per-rig reading; caveats (seam, GT anchoring, single rater, camera height); gaps; and the reproduce commands. A row is added to docs/README.md.
  • scripts/analysis/open_rig_misses_258_gpu.sh: the laurens_gsv extract. Its header now says it has not been run to completion. It runs under set -euo pipefail, with gates in this order:
    1. imagery_manifest.py --verify
    2. the laurens_mapillary repro (exits 1 unless identical)
    3. the extract
    4. low_floor_sweep.py parity
    5. tp/fp/fn at 0.55 against scoreboard.json (exits 1 on any difference)
    6. one paid: false row appended to $LEDGER via rampnet.ledger.append_rows
  • tests/test_open_rig_misses_258.py:

Result (0.30, all panos; the unbiased subset gives the same readings)

population GT misses sub_threshold silent (strict) sub − silent, boot 95% reading
laurens_mapillary 249 118 59.3% 28.8% (19.5%) +12.6 to +48.0 calibration
bayonne 301 152 47.4% 46.1% (30.3%) −13.6 to +14.9 neither
budapest_district5 (held out) 300 107 32.7% 43.9% (23.4%) −28.2 to +5.8 neither
juneau 297 72 50.0% 33.3% (16.7%) −4.2 to +35.6 neither
GoPro Max pool (laurens_mapillary + bayonne) 550 270 52.6% 38.5% (25.6%) +2.1 to +25.2 calibration
#46 reference (7 US) 2060 427 38.9% 30.0% (12.4%) +0.9 to +17.4 calibration (marginal)
  • On shares, sub_threshold leads on the GoPro Max pool. The earlier "neither leads" depended on pooling budapest and is withdrawn.
  • In recall points both levers are large and not separable. The pool is 0.284 behind the reference (0.229 to 0.333). Of that, sub_threshold excess is +0.178 and silent excess +0.127, and their difference is +0.051 (−0.008 to +0.109).
  • The rescoring ceiling is all of sub_threshold: 0.258 recall points, of which 0.178 is excess over the reference.
  • Strict silent misses cost +0.100 over the reference. In the near field they are 51 of 407 ramps, against 16 of 1,360 on the reference.
  • Merged excess is +0.001 against the reference without paterson.

Not done

  • laurens_gsv is not bucketed. It has no op_cache. The makelab2 call that launched the extract timed out client-side, and by the standing rule there were no retries and no further contact. Nothing from that attempt is committed. The doc gives the exact fill path.
  • The silent-activation breakdown has not been run on the open splits (silent_activation.py, GPU), so even strict silent remains an upper bound.

No GPU or paid model was used for any committed number, so there is no usage-log row.

Refs #263, #258, #46, #158.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

jonfroehlich and others added 3 commits October 20, 2026 12:41
…ck 1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
miss_taxonomy.py gains --cities (default output byte-identical; with the
seven original US splits it reproduces the committed #46 pool exactly).
open_rig_misses_258.py buckets laurens_mapillary, bayonne,
budapest_district5 and juneau at 0.30 and 0.55 with Wilson and
pano-bootstrap intervals, near/far, merged separations and the reading
rule; laurens_gsv is a recorded gap (no op_cache yet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 Track 1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jonfroehlich and others added 2 commits October 20, 2026 13:04
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… last-digit flip)

Python 3.12's compensated sum() and 3.10's naive sum() disagree in the last
bits, which flipped two 4-dp null rates. fsum is exact on every version;
--check now passes on 3.10, 3.12 and 3.14 with the committed JSON unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich
jonfroehlich marked this pull request as ready for review October 20, 2026 20:28

@jonfroehlich jonfroehlich left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review: open-rig miss buckets (#263 Track 1)

Reviewed at f24a3d6. Every number below marked [ran] was measured on this branch, with throwaway scripts that call the PR's own mt.load_rows, orm.load_split, orm.summarize_population and orm.bootstrap on the committed op_caches. Nothing was changed on the branch.

What checks out [ran]:

  • miss_taxonomy.py with no --cities is byte-identical to origin/main's version, in stdout and in --json-out, at both 0.30 and 0.55. The only differing line is Wrote <path>, because I wrote to two different paths.
  • open_rig_misses_258.py --check passes on py3.12. tests/test_open_rig_misses_258.py and tests/test_miss_taxonomy.py pass (40 tests), and CI is green on 3.10 and 3.12. The JSON is i/lf w/lf attr/text eol=lf with 0 CRs. The fsum null mean is the only float sum in the JSON. String seeding of random.Random is hash-seed independent.
  • The bootstrap readings hold across 10 other seeds. The reference lower bound is +0.004 to +0.012 on all 10, and GoPro Max reads "neither" on all 10.
  • These claims match the data: budapest is 89/89 GoPro Max; morgantown is 95/95 GoPro Max; bayonne is 87/87 GoPro/Max; juneau's scored panos are 92 Trimble MX7 + 2 GoPro Max; the reference is 3 GSV + 4 Mapillary; Paterson has 80 of the 124 merged misses; near-field silent is 104/608 vs 45/1,360; laurens_mapillary has 94 panos, 59 with GT; laurens confidence is unrecorded.
  • The per-bucket recall-point excesses sum to the total gap (0.2361 vs 0.2362, a rounding difference). Every cell I spot-checked in the doc's five tables matches the JSON.
  • The cited figures match their sources: #151 ΔF1 +0.112, #82 all@gopro −0.209, silent_activation 8/62/30, and 1.82–2.13 m giving 15–27%.

Findings, most severe first.

1. High: the headline "neither leads on GoPro Max" depends on Budapest, the split the repo holds out for GT quality

scripts/analysis/open_rig_misses_258.py:68-70 (GROUPS["gopro_max"]), docs/open_rig_misses_258.md:34-42, the docs/README.md row, and the #258 and #263 comments.

HELD_OUT in miss_decomposition.py excludes budapest_district5 for "single-rater GT at low reviewer confidence", and the doc calls it "the split to trust least" (l.167-168). Yet it is one of three members of the pool that carries the headline, contributing 107 of 377 misses and 47 of 151 silent. [ran] Without it (laurens_mapillary + bayonne, 0.30):

pool misses sub_threshold silent sub − silent, boot 95% reading excess sub / silent gap
3 GoPro Max (as committed) 377 46.9% 40.1% −2.1 to +16.1 neither leads +0.128 / +0.116 0.236
laurens_mapillary + bayonne 270 52.6% 38.5% +2.4 to +26.1 sub_threshold leads: calibration +0.178 / +0.127 0.284

So "the two levers are about the same size" holds only with the low-confidence split included. Without it, calibration is the larger lever by about 0.05 recall points.

Fix: report both pools, and lead with the one that does not include a GT-quality hold-out, or make Budapest a sensitivity row. Say in the Result paragraph that the "neither" reading depends on Budapest. Correct the #258 and #263 summaries to match.

2. Medium: the "silent" bucket is defined wrongly in the doc, and three sentences overclaim from it

The definition is at doc l.21 ("nothing at all, even at the 0.05 floor") and at open_rig_misses_258.py:12. It is inherited from the miss_taxonomy.py docstring.

classify_miss checks the 0.05 floor only inside R. Its annulus test (R to 2R) looks only at preds_kept, the predictions at or above threshold. A ramp with a 0.2 peak at 1.5R is therefore "silent". [ran] At 0.30:

  • 57 of 151 GoPro Max silent misses (38%) have a floor peak within 2R, and in 32 of them every such peak is below threshold.
  • On the reference it is 75 of 128 (59%).
  • Bayonne: 24 of 70.

The definition then drives these sentences:

  • l.161, "Half of what Bayonne misses, the model never responds to."
  • l.54, "labels or data are the only thing that reaches the silent excess."
  • l.107-108, "Those 104 are the population that labels could plausibly reach."

Each states as fact what is an upper bound on single-rater GT. The doc's own caveat at l.194 says so, but only in the Caveats section.

The stricter count supports the near-field reading: [ran] near-field silent misses with no floor peak within 2R are 70 of 608 on GoPro Max (11.5%) against 16 of 1,360 on the reference (1.2%).

Fix:

  • Correct the definition: "no floor peak inside R, and no unclaimed above-threshold peak within 2R".
  • Add a strict "nothing within 2R" count beside silent.
  • Reword the three sentences as upper bounds.

3. Medium: the stated ceiling on what rescoring can recover is the wrong quantity

Doc l.53-54, and the #258 comment ("The verifier's target population on the open rigs is about 0.13 recall points").

Rescoring sub-threshold peaks can recover any sub_threshold miss. The ceiling is therefore the sub_threshold recall points: 0.208 on the GoPro Max pool and 0.281 on laurens_mapillary. The +0.128 excess is the part that would close the gap to the reference, and the reference has its own 0.081 that a verifier would also chase.

Fix: "can recover at most 0.208 recall points on GoPro Max, of which 0.128 is the excess over the reference". Correct #258 to match.

4. Medium: the doc compares cities on all panos, though the benchmark README says to use the unbiased subset for that

benchmark/README.md ("Every city split is stratified, so read the unbiased column too") says the random + empty subset is "the honest between-city comparison", because each split includes 5 hand-picked high-density panos. The doc never mentions the stratification. It also quotes the all-pano recall (bayonne 0.375) against #263's unbiased 0.322. [ran] Dropping the top group from verdicts.json on every split:

  • GoPro Max: gap 0.248, sub excess +0.132, silent +0.125, still "neither leads" (−4.6 to +15.2).
  • Reference: still calibration, but the lower bound falls to +0.26 points.
  • laurens_mapillary: still calibration.

No reading changes, so this is a disclosure fix. Fix: one sentence plus an unbiased row (or columns) for the GoPro Max pool and the reference. Note that the reference's "calibration" verdict is marginal on both subsets.

5. Medium: the doc says #263 fixed the operational test before any number was looked at, but #263 did not define one

Doc l.12-29, and the #258 comment ("fixed in #263 before the run").

#263 fixed only the rule: "silent dominates means labels; sub_threshold dominates means calibration". The operational test (the pano-bootstrap 95% CI of the difference excludes 0) first appears in this PR. A plurality reading of "dominates" would say sub_threshold leads on GoPro Max (46.9 vs 40.1). In a research record, the provenance of a decision rule is part of the result.

Fix: "#263 fixed the rule; this PR operationalizes 'dominates' as …". Say whether that choice was made before the tables were seen, and if it was not, say so.

6. Low: two sentences read as conclusions rather than readings

  • l.49, "So the silent excess is not the camera model." The evidence is one split (52 misses, no interval). It is confounded by country and rubric: bayonne and budapest are the two GT regimes where the rubric fought back. It also skips richmond, which has 18 GoPro Max panos (the code comment at open_rig_misses_258.py:63 names only morgantown). Suggest "is not explained by the camera model alone".
  • l.49-50, "concentrated in bayonne, budapest, laurens_mapillary". Those are all three GoPro Max splits, so this is not a concentration.

The "Which to do first is Jon's call" framing is right.

7. Low: the stated reason recall differs from the scoreboard is wrong

Doc l.148-150 attributes the difference to the fn_confirmed gate and to panos with GT. [ran] fn_confirmed is true on 125/125 budapest and bayonne panos, and on all panos of the four open splits. Panos with no GT add nothing to recall. So 0.510 vs 0.503 (budapest) and 0.375 vs 0.372 (bayonne) come from something else, most likely cache-peak re-matching versus verdict counting. Fix: drop the explanation, or find the real cause.

8. Low: seam handling is not the same across the populations being compared

  • Matching. The taxonomy matches without wrap_x (miss_taxonomy.py:281, and _d2 in classify_miss). score_pano has wrapped by default since #140. [ran] With wrapping, 1 bayonne and 1 juneau miss become hits at 0.30.
  • Extractor. The 7 reference caches and budapest's predate the f4c71c8 exclude_border fix. [ran] They carry 0 floor peaks within 10 columns of the edge, against 4 / 39 / 13 for laurens_mapillary / bayonne / juneau. That asymmetry lies between the populations being compared. The reference seam-strip GT is richmond 1 silent, bend 1 silent, paterson 1 silent + 2 merged.

The effect is at most ~0.002 recall points and changes no reading. It deserves one caveat line beside the reference row; rampnet1_findings.md already says "committed op_cache files predate the extractor fix".

9. Low: the negative merged excess comes almost entirely from Paterson

Doc l.42. [ran] Without paterson, the GoPro Max merged excess is +0.008, not −0.026. Sub / silent become +0.118 / +0.109 and the gap 0.254. The doc notes the 80 of 124 (l.74) but does not connect it to "Merged misses go the other way". Add half a sentence.

10. Low: the distance caveat leans one way and sits away from its table

Doc l.94 and l.200-205. "The near-field counts above are, if anything, low" applies to the reference too. docs/da3_calibration_101.md puts paterson 2025 + gainesville 2026 at about 1.8 m, clovis at 2.02 m, annapolis at 2.12 m, and morgantown is GoPro Max. So the 2.5 m assumption biases both sides, and the direction of the near-field contrast is not established by it. Fix: one line under the near/far table that points to the caveat and says the bias is on both sides.

11. Low: the laurens_gsv fill path is not replicable as written

  • open_rig_misses_258_gpu.sh:3-5 says "Run on makelab2 (A40) on 2026-10-20; writes analysis_out/op_cache/laurens_gsv.json (committed)". The PR body says the run was not confirmed and nothing was committed. The header should say that.
  • The repro step prints a count but never gates. With set -u and no -e, extract runs and ALL_STEPS_DONE prints whatever repro finds. Make it exit 1 on a mismatch, or say plainly in the log that it does not gate.
  • There is no parity step. juneau_254_gpu.sh runs low_floor_sweep.py parity --cities juneau. Doc l.221 says "tp/fp/fn at 0.55 must equal the scoreboard" but names no command, and per finding 7 the cache-derived recall does not equal the scoreboard exactly on two splits. Name the command and the tolerance.
  • The doc does not say how to obtain benchmark/laurens_gsv/panos and benchmark/laurens_mapillary/panos. Point to imagery_manifest.json and the fetch path.
  • "record the paid:false usage row" does not name a tool. Say which (slurm_usage.py if on klone, or the row format if on makelab2).

12. Low: the "about evenly" claim rests on two point estimates with no interval

+0.128 vs +0.116 are point estimates. A pano bootstrap of (sub excess − silent excess), stratified like the existing one, would say whether "about evenly" is distinguishable from either lever being larger. That matters more now, given finding 1.

Nits

  • l.37 and l.160 call bayonne "a tie", while l.193 says the "neither leads" readings are "mostly underpowered, not ties". Pick one framing. Bayonne's point estimates are close (47.4 vs 46.1), so "close, and underpowered" is accurate.
  • l.168, "The brief for this work called Budapest…": a reader of the doc will not know which brief this is. Cite the source, or drop it, since the records settle the question.
  • miss_taxonomy.py --cities "" gives an empty tuple and prints "all held out". n_pool counts the names given, not the caches found. Neither matters today.
  • The doc's tables are transcribed by hand and nothing checks them. I spot-checked them against the JSON and found no mismatch.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

…biased subset, generated tables

1. GoPro Max pool is laurens_mapillary + bayonne; budapest_district5 (HELD_OUT)
   is its own row. Headline, decomposition and comments recomputed.
2. strict_silent (no floor peak within 2R) beside silent; silent definition fixed.
3. Rescoring ceiling is all sub_threshold recall points, not the excess.
4. Unbiased subset (verdicts group != top) for every between-city table.
5. Provenance of the bootstrap test stated as it happened.
6. Morgantown hedged. 7. Real cause of the README recall difference.
8. Seam note. 9. Reference without paterson. 10. Camera height beside the table.
11. GPU script: status header, set -euo pipefail, manifest/repro/parity/score
    gates, ledger row via rampnet.ledger.append_rows.
12. Pano-bootstrap intervals on every recall-point excess and on
    (sub excess - silent excess).
Nits: --cities '' errors; n_pool counts loaded splits; doc tables are
generated from the JSON and --check verifies them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jonfroehlich jonfroehlich left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 543d6a6

I re-checked every finding from the first review at 543d6a6. [ran] means I measured it on this commit. The independent checks use throwaway scripts that read the committed caches, verdicts and records directly, not the PR's helpers.

What I ran:

  • open_rig_misses_258.py --check passes on Python 3.10 (uv, numpy only), 3.11 and 3.12. It now covers the JSON and all 10 generated doc blocks.
  • tests/test_open_rig_misses_258.py and tests/test_miss_taxonomy.py pass (45 tests).
  • miss_taxonomy.py with no --cities is still byte-identical to origin/main, in stdout and in --json-out, at 0.30 and at 0.55.
  • The new pool numbers match my own count:
    • laurens_mapillary + bayonne: 142 / 270 sub_threshold, 104 / 270 silent.
    • Excess +0.178 sub, +0.127 silent; rescoring ceiling 0.258.
  • The strict-silent counts match my own count. Strict silent is 23 / 46 / 25 / 12 per split, and 53 on the reference. Near field it is 51 / 407 on the pool against 16 / 1,360 on the reference.
  • The seam-strip ramps on the reference are as stated (5 ramps, all missed).

I found no per-finding reply as a comment in the PR thread. The PR body was rewritten instead, and I read that.

# Finding Status
1 Headline depended on pooling Budapest Fixed [ran]. GROUPS["gopro_max"] is now laurens_mapillary + bayonne, and test_budapest_is_never_pooled pins it. Budapest has its own row. The doc says plainly that the earlier reading is withdrawn.
2 "silent" defined wrongly; overclaims built on it Fixed [ran]. The definition is correct in the doc (l.24-26) and the docstring. The strict-silent column is computed correctly; counts above. "Half of what Bayonne misses…" and "the only thing that reaches" are gone.
3 Rescoring ceiling was the wrong quantity Fixed. 0.258, of which 0.178 is the excess; the doc says so, and so does #258.
4 Stratified sampling ignored Fixed [ran]. The unbiased subset is used for the between-city tables, and the reference's marginal verdict is called out.
5 Provenance of the reading-rule test Fixed. l.44-49 says when the test was chosen and what had already been seen. That is the right disclosure.
6 Morgantown and "concentrated" overclaims Fixed. Softened, with the confounds and richmond's 18 GoPro Max panos named.
7 Wrong explanation of the scoreboard recall gap Fixed [ran]. The new explanation is exactly right. Bayonne has one detection judged False within R of a confident missed mark, and budapest has two judged unsure (112 + 1 and 151 + 2 against verdict-True counts). The taxonomy's hits at 0.55 equal scoreboard.json tp on all four open splits. Bend / paterson / gainesville are 2 / 2 / 7 short, as the doc says.
8 Seam handling differs across populations Fixed. The caveat is in the doc, with the measured counts.
9 Merged excess is a Paterson artifact Fixed. There is now a no-Paterson reference and an excess table against it.
10 Distance caveat leaned one way Fixed. The caveat now sits under the near/far table and covers both sides.
11 laurens_gsv fill path not replicable Mostly fixed, one new defect (A below). The STATUS header is accurate. The run has set -euo pipefail. The manifest-verify, repro-equality and parity steps all exist. The pano source is named. [ran] The score step's logic reproduces the scoreboard exactly on juneau (204 / 7 / 93) and laurens_mapillary, and low_floor_sweep.py parity --cities juneau passes.
12 No interval on the decomposition Fixed. Every excess carries a pano-bootstrap interval, and sub − silent is +0.051 (−0.008 to +0.109).
nits Fixed: "tie", "the brief", --cities "" (now an error, with a test), n_pool (now counts splits actually loaded), and the doc tables (now generated and checked).

The issue comments say what the doc says. The edited #258, #263 and #158 comments, and their follow-ups, match the doc on the pool, the shares, the bootstrap, the ceiling, the excess interval, strict silent and the provenance. Superseded text is struck through rather than deleted, which is the right way to do it.

New

A. Medium: a score-gate failure stops the run before the cost is recorded

open_rig_misses_258_gpu.sh:25-26, 98-139. Under set -e, a score mismatch exits before the ledger step, so the extract's GPU time is never recorded. The script's own header expects a mismatch on GSV ("bend's committed cache gives tp 247 / fp 5 against the scoreboard's 249 / 10; paterson and gainesville are 2 and 7 TPs short"), and laurens_gsv is a GSV split. So the likeliest outcome of a real run is a mismatch that skips the row.

That is the same failure #139 / #143 turned into a standing rule: a ledger row has to outlive the run.

[ran] Even a non-GSV split can trip the gate. The script's own score_pano call wraps the x axis, and on bayonne it gives tp 114 / fp 25 against the scoreboard's 113 / 23.

Fix: move ledger to run right after extract, before parity and score, or write the row from a trap ... EXIT whenever the extract times file exists. Optionally default LEDGER through rampnet.ledger.canonical_repo_root, so a worktree run lands in the main checkout without the env var.

B. Medium: on a clone with autocrlf=true, --check fails because the doc is not LF-pinned

.gitattributes pins analysis_out/open_rig_misses_258.json but not docs/open_rig_misses_258.md. BLOCK_RE matches -->\n, and the doc is read with newline="".

[ran] With a CRLF copy of the doc, --check --doc <crlf copy> exits 1 with doc is missing generated blocks [all 10]. A default Git-for-Windows install (core.autocrlf=true) gets exactly this, and so does test_committed_report_rederives_byte_for_byte. The message points at the doc's content, not its line endings. Jon's checkout is not affected, because autocrlf=input. CI is not affected either, because it runs on Linux.

Fix: add docs/open_rig_misses_258.md text eol=lf. The same pattern pins other checked markdown, e.g. analysis_out/recall_by_depth_112.md (.gitattributes:89; the #101 tables, line 86, were pinned for exactly this failure in the #203 review). Optionally make BLOCK_RE accept \r?\n, or have the error mention line endings.

C. Nits

  • Mixed units in the excess cells. Each excess cell mixes a fraction point estimate with an interval in percentage points, e.g. 0.258 (+0.178; +13.6 to +22.0). l.132-133 says so, but the prose uses 0.229 to 0.333 for the same kind of interval. One unit throughout would read more easily.
  • Minus signs. The generated tables print an ASCII hyphen-minus (-13.6), while the prose uses U+2212.
  • Morgantown's excess interval is conservative. bootstrap_excess resamples morgantown independently of the reference that contains it, so it ignores their positive covariance.
  • "+0.0 to +22.0 · neither leads" (unbiased, reference without Paterson) reads as a contradiction at one decimal. The lower bound is ≤ 0 before rounding. Printing two decimals, or "≤ 0", would avoid it.

Verdict

NEEDS FIXES. The fixes are small: A (step order) and B (one .gitattributes line). The analysis, the doc and the issue comments are sound at 543d6a6. Nothing in A or B changes a committed number.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

…able units

A. open_rig_misses_258_gpu.sh: the ledger row is written right after extract,
   before parity/score, and an EXIT trap records an extract that died part-way
   as status failed; LEDGER defaults to the main checkout via
   rampnet.ledger.canonical_repo_root. score now reports instead of aborting
   (it uses score_pano's wrap_x=True, the scoreboard's convention; cache-vs-
   records differences such as bayonne's 3 seam peaks and GSV drift are
   expected), and parity is the gate. Both paths exercised with a stub.
B. docs/open_rig_misses_258.md pinned eol=lf; fill_doc names CRLF as the
   cause when no block matches.
C. Excess cells in one unit (recall-point fractions), U+2212 minus, exact-zero
   bounds unsigned with the rule stated, morgantown covariance noted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

Replies to the two reviews

Both reviews are covered here: the first at f24a3d6, fixed in 543d6a6, and the re-review at 543d6a6 (pullrequestreview-5591791903), fixed in 4fa23c1. Last time I rewrote the PR body instead of replying here; this comment is the per-finding record.

First review (fixed in 543d6a6)

# Finding What changed
1 Headline depended on pooling Budapest GROUPS["gopro_max"] is now laurens_mapillary + bayonne. Budapest has its own "held out, not pooled" row, and test_budapest_is_never_pooled pins that. Every pooled number is recomputed. The headline now reads: sub_threshold leads on shares (+2.1 to +25.2 points). The doc says the old "neither" reading is withdrawn, and why.
2 Silent defined wrongly, and claims built on it The definition is corrected in the docstring and the doc. strict_silent (no floor peak within 2R) is computed in open_rig_misses_258.py; classify_miss is unchanged. The three sentences are rewritten on strict numbers: 51 of 407 near ramps on the pool, against 16 of 1,360.
3 Rescoring ceiling It is all sub_threshold recall points: 0.258, of which 0.178 is excess over the reference. Fixed in the doc and in the #258 comment.
4 Stratification ignored Every population is computed on all panos and on the unbiased subset (verdicts group != "top"). The doc says which table each reading uses, and that the reference's own verdict is marginal.
5 Provenance of the test The doc says #263 fixed the rule, and that I chose the bootstrap test after the default run had printed the per-split 0.30 shares, but before any interval or pooled number existed.
6 Morgantown / "concentrated" Hedged, with the confounds, n and interval. Richmond's 18 GoPro Max panos are named. "Concentrated" is removed.
7 Wrong explanation of the recall difference The taxonomy's hits at 0.55 equal scoreboard.json tp on all four open splits. The README figures come from score_validation.py verdict counting. The difference is one detection judged False within R of a missed mark on bayonne, and two judged unsure on budapest.
8 Seam handling Caveat added with the measured counts (5 reference strip ramps; bound about 0.002).
9 Paterson drives merged Added a reference without Paterson, with its own excess table. The merged excess against it is +0.001.
10 Distance caveat Moved under the near/far table. It now covers both sides, since the reference's rigs also sit at 1.8 to 2.1 m.
11 laurens_gsv fill path Added: a STATUS header, set -euo pipefail, manifest / repro / parity / score steps, the pano source, and the ledger tool. (Follow-up A below.)
12 No interval on the decomposition bootstrap_excess gives intervals on every excess, on the gap, and on (sub excess − silent excess): +0.051 (−0.008 to +0.109).
nits "close and underpowered"; the "brief" sentence is dropped; --cities "" is an error; n_pool counts loaded splits; the doc tables are generated from the JSON and checked by --check.

Re-review (fixed in 4fa23c1)

A. Ledger lost on a gate failure.

  • The ledger step now runs right after extract, before parity and score.
  • An EXIT trap writes the row with status: "failed" and the elapsed time if the extract started but never finished, i.e. its times file holds one timestamp.
  • LEDGER defaults to the main checkout through rampnet.ledger.canonical_repo_root.
  • I exercised both paths locally with a stub PY:
    • a failing extract wrote one failed row;
    • a "successful" extract followed by a failing parity wrote one ok row, and no duplicate.

Seam / gate rule.

  • The score step passes wrap_x=True explicitly. That is score_pano's default and the scoreboard's convention.
  • Wrapping does not explain bayonne. I measured it: bayonne reads 114 / 25 wrapped and 113 / 26 unwrapped, against the scoreboard's 113 / 23. The difference is cache vs records: the cache keeps the 3 seam peaks that the labeler's extractor drops.
  • The GSV caches differ for the documented drift: bend 247 / 5 against 249 / 10; paterson and gainesville 2 and 7 TPs short.
  • So the rule I chose:
    • parity is the gate, with the repo's existing tolerance.
    • score reports and does not abort. Any difference must be quoted in the doc beside the laurens_gsv row before the cache is committed.
  • The header says this, with the measured numbers.

B. CRLF clone breaks --check.

  • .gitattributes now pins docs/open_rig_misses_258.md text eol=lf beside the JSON.
  • fill_doc now names CRLF as the cause when no block matches. I checked it on a CRLF copy: the message now ends "the doc has CRLF line endings; it is pinned eol=lf in .gitattributes, so re-check it out".

C. Nits.

  • The excess cells now use one unit: recall-point fractions for the point and the interval, e.g. 0.258 (+0.178; +0.136 to +0.220).
  • The generated tables use U+2212 minus, matching the prose.
  • An exact-zero bound prints unsigned, as 0.0 to +22.0. The doc states the rule as "strictly above or strictly below zero; a bound of exactly zero counts as including zero".
  • Morgantown's excess interval is noted as conservative, because it is resampled independently of the reference that contains it. The note is under the excess table and in the per-rig paragraph.

No committed number changed. --check passes on Python 3.10 and 3.12, and the targeted tests (75) and test_slurm_scripts.py (44) pass locally.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

@jonfroehlich jonfroehlich left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final re-review at 4fa23c1

I checked out 4fa23c1 into a fresh detached worktree and ran everything below there. Your per-finding reply (#issuecomment-6303890331) matches what I found.

A, a gate failure losing the ledger row: fixed. I tested the GPU script with a stub PY that fakes the manifest, extract and parity steps and runs the real interpreter for everything else. Each case wrote to a scratch ledger:

case exit ledger
extract dies part-way 3 1 row, status: "failed", elapsed_s set, panos_scored: null (written by the EXIT trap)
extract ok, parity fails 1 1 row, status: "ok", no duplicate from the trap
everything passes 0 1 row, ok; score prints DIFFERS … (parity is the gate) and does not abort

The canonical_repo_root default for LEDGER resolves. The wc -w comparison still holds if wc pads its output with spaces. The worktree was clean after each run.

B, CRLF clone breaks --check: fixed. git ls-files --eol reports the doc as attr/text eol=lf. On a CRLF copy, --check now says why it failed ("the doc has CRLF line endings; it is pinned eol=lf…").

C, nits: fixed.

  • The excess cells use one unit, fractions, for both the point and the interval.
  • The tables print U+2212 minus.
  • Exact-zero bounds print unsigned, and the rule is stated in the doc. The one 0.0 to +22.0 · neither leads cell is a true 0.0: the raw bootstrap bound is 0.0.
  • The morgantown covariance note is in the doc in both places.

Nothing regressed:

  • --check passes on Python 3.10 (uv) and 3.12.
  • tests/test_open_rig_misses_258.py and tests/test_miss_taxonomy.py pass (45).
  • miss_taxonomy.py with no --cities is still byte-identical to origin/main at 0.30 and 0.55.
  • The JSON is unchanged since 543d6a6.
  • CI is green on 3.10, 3.12 and checks.

One cosmetic nit, not blocking. A bound that rounds to zero from below prints −0.000, for example the morgantown unbiased strict-silent excess. That is the true sign, so leave it unless it bothers you.

READY

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

jonfroehlich and others added 2 commits October 20, 2026 16:56
…U step

The d74d680 launch on makelab2 (torch 2.13 cu130) did complete: its
laurens_mapillary repro kept all 374 peak positions, but 88 of 94 panos
differed in score by up to 5.4e-5, so the exact-equality gate would stop
every run on that env. The gate now requires identical pano set, peak
counts and positions, |score diff| <= 1e-3 and no crossing at 0.30/0.55.
The repro step's GPU time now gets its own ledger row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Track 1)

9f4b7ce on the A40: manifest OK, laurens_mapillary repro within tolerance,
extract 86 panos in 164.6 s, then low_floor_sweep parity FAILED on the
count arm (records 121 vs cache 111 at 0.55; every displacement < 0.44 R).
All 18 records-only detections have a cache peak within 0.44 R scoring
0.38-0.54: the deployed-vs-re-extraction score gap #151 observed. The cache
is kept as analysis_out/open_rig_258/laurens_gsv_candidate_op_cache.json,
read by nothing, pending a decision on a parity exception; laurens_gsv stays
out of the tables and miss_taxonomy's default output is unchanged.

Usage rows: this run's repro + extract, and the superseded d74d680 launch's
two (elapsed from log stamps), appended to the main checkout ledger first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

laurens_gsv: the extract ran, failed the parity gate, and is not in the tables

The GPU fill path ran on makelab2 (A40) on 2026-10-20 at 9f4b7ce. The result is not the cache plus tables I expected. The cache failed the parity gate, so laurens_gsv stays out of every table, and miss_taxonomy.py's default output is unchanged. Whether to admit the cache as a parity exception is a decision for Jon.

Two script changes before the run (9f4b7ce)

  • The repro gate is now a tolerance, not bit-equality. The earlier d74d680 launch did run to completion. Its laurens_mapillary repro kept all 374 peak positions, but 88 of 94 panos differed in score by up to 5.4e-5 (torch 2.13 cu130 against the committed cache), so an exact-equality gate stops every run on this environment. The gate now requires:
    • the same pano set;
    • the same peak counts and positions;
    • a score difference of at most 1e-3;
    • no peak crossing 0.30 or 0.55.
  • Each GPU step gets its own ledger row.

The run (analysis_out/open_rig_258/gpu_run.log)

  • manifest: OK (86 and 94 panos).
  • repro: passed within tolerance (max score difference 5.45e-5).
  • extract: 86 panos in 164.6 s.
  • ledger: both rows written before the gate.
  • parity: FAILED on the count arm.
    • At 0.55 the records hold 121 detections and the cache 111.
    • 74.8% land in the identical cell and 92.8% within 0.5 R. Every displacement is under 0.44 R.
    • All 18 records-only detections have a cache peak within 0.44 R scoring 0.38 to 0.54. The cache sees the same peaks and scores them lower.
    • Scored at 0.55 with wrap_x=True, the cache gives tp 101 / fp 7, against the scoreboard's 112 / 8.
    • This is the gap Recall collapses on flush / minimal-reveal curb ramps: Laurens R 0.390, and the experiment that isolates it #151 observed and did not investigate: "the deployed GSV run beats the r2048 re-extraction".
    • sao_paulo failed the same count arm and was admitted as a ratified PARITY_EXCEPTIONS entry.

What is committed (82e6f75)

  • analysis_out/open_rig_258/laurens_gsv_candidate_op_cache.json (sha256 af95b501…). No analysis reads it.
  • analysis_out/open_rig_258/gpu_run.log, and gpu_run_d74d680_superseded.log.
  • Four paid: false usage rows, appended to the main-checkout ledger first and then copied here:
    • this run: open-rig-258:repro 89.5 s and open-rig-258:extract 164.6 s;
    • the superseded d74d680 launch: two rows marked SUPERSEDED, 91 s and 167 s from its log stamps.
  • The doc's Gaps section states all of the above, including the admit-it path: copy the cache to op_cache/laurens_gsv.json, add a PARITY_EXCEPTIONS entry, regenerate the tables, and regenerate miss_taxonomy's default output and the scoreboard AP row.

No number in the doc or analysis_out/open_rig_misses_258.json changed, and --check passes.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

…gsv (#263 Track 1)

The cache from the 9f4b7ce makelab2 run moves to analysis_out/op_cache/laurens_gsv.json
with a low_floor_sweep.PARITY_EXCEPTIONS entry on the sao_paulo precedent: count arm
only (111 cache vs 121 records detections at 0.55), every displacement < 0.44 R, the
same peaks scored lower than the deployed GSV run (the #151 deployed-vs-re-extraction
drift). It reproduces #25's r2048 re-extraction of laurens_gsv exactly.

open_rig_misses_258: laurens_gsv joins the tables (held out, with the under-scoring
caveat) and a generated 'One town, two rigs' table: at 0.30 silent 0.137 vs 0.123,
sub_threshold 0.281 vs 0.164 recall points (GoPro Max vs GSV, single rater).

Regenerated because the cache now exists, each check passing:
- scoreboard: laurens_gsv RampNet AP from op_cache (0.768 vs bundle 0.494); tp/fp
  112/8 unchanged (records-based).
- input_res_sweep_25: instrument check now covers laurens_gsv (SHA256SUMS updated).
- laurens_paired_151: rampnet@0.30 gains its GSV side (= r2048 row); doc tables,
  dated note, rows_sha256 pin.
- eval_protocol_150 reproduction.json and cascade_cost_35 summary.json (gaps).
Tests that asserted 'laurens_gsv has no op_cache' are updated to the new state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

Addition: laurens_gsv admitted as a parity exception, and a same-town read

On Jon's go-ahead, the laurens_gsv cache that failed the count arm of the parity gate (comment above) is admitted on the sao_paulo precedent.

What changed:

laurens_gsv in the tables (held out, all panos):

threshold GT misses recall merged sub_threshold localization silent (strict) reading
0.30 220 76 0.654 13 (17.1%) 36 (47.4%) 0 27 (35.5%), strict 11 neither leads (−10.7 to +38.6)
0.55 220 119 0.459 7 85 (71.4%) 0 27, strict 11 calibration

Caveat, in the doc beside the row: the cache under-scores the deployed run. Recall at 0.55 is 0.459 from the cache, against the scoreboard's records-based 0.509. So the row's sub_threshold / silent split is conservative on the sub_threshold side.

One town, two rigs (new section; single rater, whole-arm, not paired). In recall points at 0.30:

laurens_mapillary (GoPro Max) laurens_gsv (GSV)
silent 0.137 0.123
sub_threshold 0.281 0.164
strict silent 0.092 0.050

The silent rate is close on the two rigs, and the gap is in sub_threshold. That points at calibration as the Laurens rig effect. The GSV cache's under-scoring would, if anything, understate that contrast.

What else adding the cache moved. Each of these is regenerated, and each check passes:

Commit c079b76. open_rig_misses_258.py --check is byte-exact, check_all.py --all passes locally (including the slow benchmark_power_135), and CI is green (pytest 3.10, pytest 3.12, checks).

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

@jonfroehlich jonfroehlich left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at c079b76 (laurens_gsv cache admitted)

I checked out c079b76 into a fresh detached worktree and ran everything below there.

What holds up [ran]:

  • The parity-exception diagnosis matches the cache. I recomputed it from the cache and records.jsonl without going through low_floor_sweep:
    • 121 records detections against 111 in the cache at 0.55.
    • All 18 records-only detections have a cache peak within 0.439 R, scoring 0.381–0.54.
    • The maximum displacement is 0.439 R.
    • Matched peaks score lower in the cache 70 times and higher 51 times.
    • low_floor_sweep.py parity --cities laurens_gsv,sao_paulo prints both as ratified exceptions and exits 0.
  • The #25 claim holds. Against input_res_sweep_25/cache/r2048/laurens_gsv.json, the cache has the same peaks on all 86 panos. The maximum score difference is 3.98e-5, and the maximum position difference is 5e-7, which is the r2048 file's 6-dp rounding. The cache sha256 starts af95b501, as the doc says.
  • The scoreboard's tp/fp did not move. The laurens_gsv RampNet cell is still tp 112 / fp 8 / fn 108, with P/R/F1 unchanged. Only ap (0.494 → 0.768499) and ap_source changed.
  • No other number moved. I diffed scoreboard.json, eval_protocol_150/reproduction.json, input_res_sweep_25/instrument_check.json, cascade_cost_35/summary.json and laurens_paired_151.json field by field against 4fa23c1. Every change is a laurens_gsv addition, except the moved rows_sha256. In laurens_paired_151, no arm row was removed and 86 laurens_gsv rows were added. The gt rows only gain a rampnet@0.30 hit key. inputs_sha256 only gains the new cache. No existing two_by_two row changed. In reproduction.json, 13 cells now take their AP from the op_cache.
  • scripts/check_all.py --all: 23 passed, 0 failed, 2 skipped. The skips are the two local-cache entries. benchmark_power_135 passed (541 s). The tree was clean afterwards.
  • Tests: 447 passed across test_open_rig_misses_258, test_miss_taxonomy, test_scoreboard, test_laurens_paired_151, test_input_res_sweep_25, test_eval_protocol_150, test_low_floor_sweep, test_cascade_*, test_slurm_usage and test_vertex_usage.
  • The four updated tests are honest re-pins, not loosened assertions.
    • test_input_res_sweep_25 keeps the extras total (173) and the at-seam assertion.
    • test_scoreboard now also pins tp/fp at 112/8.
    • test_laurens_paired_151 asserts the rampnet@0.30 GSV side equals rampnet_r2048@0.30 cell for cell, which is stronger than before.
  • open_rig_misses_258.py --check is byte-exact on Python 3.10 (uv) and 3.12.
  • The four usage rows are well-formed. Each parses with rampnet.ledger.read_rows, each has a distinct run_id key, and none collides with an existing row, so all four count in ledger_totals. Their elapsed_s and ts match the stamps in both committed logs (91 s / 167 s superseded; 89.5 s / 164.6 s current).
  • The doc's under-scoring caveat reads correctly. The cache scores deployed hits lower, so it inflates GSV sub_threshold, and the two-rig sub_threshold contrast is understated. The doc says exactly that.

NEEDS FIXES

1. Medium [ran]: the ledger still drops a completed step's GPU time in two failure paths

open_rig_misses_258_gpu.sh, the on_exit trap and the ledger step. Rows are written only at the ledger step. The trap records only a step whose times file holds one timestamp, meaning it started and never finished. A step that finished before the run died is in neither path. I ran a stub PY against scratch ledgers:

case exit rows written
repro extract finishes, repro tolerance gate fails 1 none (no ledger file)
repro ok, laurens_gsv extract dies 3 extract: failed only; the finished repro's time is lost
extract ok, parity fails 1 repro: ok, extract: ok
all pass 0 repro: ok, extract: ok

The first case is the repro gate's whole reason to exist, and it records nothing. This is the same #139 / #143 rule as the last round's finding A. The real makelab2 run hit neither path, so the committed ledger is complete.

Fix: write each step's row as soon as that step's GPU work finishes, before its gate. Alternatively, have the trap write ok for any two-timestamp step not yet ledgered, and keep a done-set so nothing is written twice.

2. Low: the Cost paragraph contradicts the doc's header

docs/open_rig_misses_258.md, Cost section: "The laurens_gsv extract … is not used by any number either". That is false. Every laurens_gsv row, the "One town, two rigs" table and its prose come from that cache. The header correctly says every number is CPU-only "except the laurens_gsv cache".

3. Low: four places still say laurens_gsv has no op_cache

  • scripts/analysis/laurens_paired_151.py:265, and so the committed laurens_paired_151.json legs note, still reads "analysis_out/op_cache (laurens_gsv has none)", beside arms: ["laurens_gsv", "laurens_mapillary"].
  • docs/eval_protocol_150.md:143-144 says "the 11 RampNet cells whose ap_source is the op_cache (every split but laurens_gsv, which has no op_cache …)". reproduction.json now has 13. Bayonne and Juneau each got a dated note at l.259 and l.270, and laurens_gsv has none.
  • docs/model_comparison.md:836-839 says "laurens_gsv does not [have a 0.05-floor op_cache]". The AP shown in that generated block (0.494) is still the bundle AP, as for every split, so only the sentence is stale.
  • docs/juneau_split_254.md:196 (optional) says the two figure plotters "exit on" laurens_gsv. That blocker is gone, so the figures could now be regenerated.

Nit. In the Gaps section, "What adding the cache changed elsewhere. Nothing else moved:" is followed by a list of what moved. Something like "These moved, and nothing else did" would read correctly.

Fixes 1 to 3 are each a few lines and change no committed number. With them, READY.

NEEDS FIXES

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

jonfroehlich and others added 4 commits October 20, 2026 18:59
…ale laurens_gsv text

1. open_rig_misses_258_gpu.sh writes each GPU step's row the moment its GPU work
   finishes, before its own gate and any later step, with a done-set so the EXIT
   trap only adds what is missing. Exercised with the reviewer's stub harness:
   repro gate fails -> repro ok; extract dies -> repro ok + extract failed;
   parity fails -> both ok; all ok -> both ok; ledger off -> no file.
2. Cost paragraph: the laurens_gsv cache is a GPU product every laurens_gsv
   number reads.
3. Stale 'laurens_gsv has no op_cache': laurens_paired_151 leg note (rows_sha256
   pin moves to 6a94d27b), eval_protocol_150.md (13 op_cache cells, dated note),
   model_comparison.md and juneau_split_254.md dated notes.
Nit: 'Nothing else moved' header reworded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eval_protocol_150.md: keep both dated notes; the laurens_gsv note quotes the
post-merge counts (224 cells, 13 op_cache cells). All checks re-run clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts resolved:
- usage_log.jsonl: main's file plus this branch's four open-rig-258 rows.
- laurens_paired_151: rebuilt from main's JSON with its curb probe; now carries both
  the Vistas row and the laurens_gsv GSV side (rows_sha256 6b1376d1, pin updated).
- docs/laurens_paired_151.md, docs/eval_protocol_150.md: both dated notes kept;
  ours quotes the post-merge counts (233 cells, 13 op_cache cells).
check_all --ci and the affected tests pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ens_gsv gap row)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

Reply to the re-review at c079b76 (pullrequestreview-5593973293)

The fixes are in 3c8396c, followed by two merges of main (4679f09 for #259 and #262, 470d0c6 for #267) and a regenerated cascade_cost_35 summary (af646f2). No number of mine changed.

# Finding What changed
1 The GPU script loses a finished step's time when repro's tolerance gate fails, or when extract dies after repro open_rig_misses_258_gpu.sh now writes each GPU step's row via record the moment that step's GPU work finishes. That is before the step's own gate (repro's tolerance check) and before any later step. A done-set (DONE) makes the EXIT trap add only what is missing: a step that died part-way is written as failed, and a step that finished but was never recorded as ok. Nothing is written twice. ledger is now a switch rather than a step; leaving it out of STEPS writes no rows. I exercised five cases with your stub harness (r265/stub/pystub2 plus bin/nvidia-smi, scratch ledgers); the results are below the table.
2 "Not used by any number" in the Cost paragraph Rewritten. Every number is CPU-derived from committed caches, one of which (laurens_gsv) is the product of the recorded GPU extract that every laurens_gsv number reads.
3 Stale "laurens_gsv has no op_cache" text Fixed in four places, listed below the table.
nit The "Nothing else moved:" header Now reads "Every change is a laurens_gsv value appearing where there was none; no existing number moved".

Finding 1, the five stub cases:

Case Exit code Rows written
repro's tolerance gate fails (STUB_REPRO_BAD=1) 1 repro=ok
extract dies after repro (STUB_EXTRACT_FAIL=1) 3 repro=ok, extract=failed
parity fails (STUB_PARITY_RC=1) 1 repro=ok, extract=ok
all steps pass 0 repro=ok, extract=ok
STEPS without ledger 0 none (no ledger file)

Finding 3, the four places:

  • laurens_paired_151.py:265 and the leg note it writes. The note now says laurens_gsv's cache is a parity exception, the same re-extraction instrument as the r2048 leg. The JSON and markdown are regenerated with the committed probe, and the rows_sha256 pin moved to 6a94d27b…; no row value changed.
  • docs/eval_protocol_150.md. It now says "13 RampNet cells whose ap_source is the op_cache", and has a dated 2026-10-20 note in the style of the Bayonne and Juneau notes. The conflicts with Publish the YOLO11x perspective-tiles detections: y11x_tiles + six seed legs (#258, #51, #135) #259's and Vistas 1024 parity arm on all fourteen splits (stacked on #261) #267's dated notes are resolved: all the notes are kept, and mine quotes the post-merge counts (233 cells, 13 op_cache cells).
  • docs/model_comparison.md. A dated note under the Laurens AP caveat: the scoreboard now reads 0.768 from the op_cache, while the 0.494 in that table is still the bundle's.
  • docs/juneau_split_254.md. The figure-blocker sentence now says laurens_gsv had no op_cache then.

The #267 merge. It conflicted in laurens_paired_151 (the Vistas row), usage_log.jsonl and two docs:

  • laurens_paired_151 is rebuilt from main's JSON with main's curb probe, so it now carries both the Vistas row and the laurens_gsv GSV side (rows_sha256 6b1376d1…, pin updated, with both history comments kept).
  • The ledger is main's file plus this branch's four open-rig-258 rows.
  • cascade_cost_35/summary.json needed one more regeneration, for the new Vistas laurens_gsv gap row.

Checks after the merges. open_rig_misses_258.py --check, laurens_paired_151.py --check, eval_protocol_150.py --check (233 cells, all equal) and scoreboard.py --check pass, as does check_all.py --ci. CI is green on af646f2 (pytest 3.10, pytest 3.12, checks), and the PR merges cleanly.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

@jonfroehlich jonfroehlich left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at af646f2

I reviewed this in a fresh detached worktree at af646f2. Its merge-base is origin/main (31de31a).

The previous round's findings are fixed [ran].

  1. Ledger rows per step. I ran a stub PY against scratch ledgers, with the five cases from your reply plus one more:
case exit rows
repro tolerance gate fails 1 repro: ok
extract dies after repro 3 repro: ok, extract: failed
parity fails 1 repro: ok, extract: ok
all pass 0 repro: ok, extract: ok
STEPS without ledger 0 none (no ledger file)
repro's own extract dies (extra case) 4 repro: failed

No case wrote a row twice. The tree and the cache (af95b501…) were unchanged after every run.

  1. Cost paragraph. It now reads correctly.
  2. Stale "no op_cache" text. It is fixed in the four places I named: laurens_paired_151.py and its leg note, eval_protocol_150.md (13 cells, plus a dated note), model_comparison.md, and juneau_split_254.md.

The nit is fixed too.

The merge resolutions are correct [ran].

  • usage_log.jsonl: its first 294 lines are byte-identical to origin/main's. The 4 rows after them are byte-identical to the open-rig rows at c079b76.
  • laurens_paired_151.json against origin/main: every difference is a laurens_gsv addition.
    • 86 laurens_gsv scores rows were added and none was removed.
    • The 469 gt rows differ only by the added rampnet@0.30 hit key.
    • The only leg that changed is rampnet@0.30: its arms gained laurens_gsv and its note was updated. The Vistas leg is main's, unchanged.
    • inputs_sha256 only gains the cache. two_by_two only gains the two rampnet@0.30 keys.
    • The only tables.scores row that changed is rampnet@0.30.
    • rows_sha256 is 6b1376d1…, and laurens_paired_151.py --check re-derives it.
  • scoreboard.json, reproduction.json, instrument_check.json and cascade_cost_35/summary.json against origin/main: a field-by-field diff shows only laurens_gsv changes:
    • The scoreboard changes only laurens_gsv's AP (0.493735 → 0.768499) and ap_source; tp/fp stay 112/8.
    • The cascade summary adds laurens_gsv gap rows, one per challenger, the Vistas 1024 row included.
    • No pre-existing number moved.
  • eval_protocol_150.py --check: 233/233 scoreboard cells and 30/30 benchmark_eval cells equal, 0 differing, 0 unchecked.
  • scoreboard.py --check: scoreboard.json and model_comparison.md are current.

Checks [ran].

  • open_rig_misses_258.py --check is byte-exact on Python 3.10 (uv) and 3.12.
  • scripts/check_all.py --ci: 22 passed, 0 failed, 3 skipped (local-cache).
  • 322 tests passed across the open-rig, taxonomy, paired-151, scoreboard, input-res-25, eval-protocol-150, low-floor and cascade-cost test files.

One stale sentence came in with the #267 merge (Low). docs/vistas_transfer_126.md:868-870 still says summary.json lists "eight of the nine" new Vistas pairs as gaps, and that "the ninth, laurens_gsv, has no op_cache, so it cannot enter the cascade at all". After af646f2, summary.json lists the Vistas laurens_gsv pair as "published detections exist but this pair was not run", like the other eight. The fix is a one-line dated note, like the ones in cascade_cost_35.md and model_comparison.md. It moves no number, and summary.json itself is right, so I am not holding the PR on it.

READY

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

…eption) op_cache

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jonfroehlich

Copy link
Copy Markdown
Member Author

One more stale line, from the #267 merge, is fixed in 5de1092. docs/vistas_transfer_126.md said summary.json lists "eight of the nine" Vistas pairs as gaps and that laurens_gsv "has no op_cache". It now carries a dated 2026-10-20 note: laurens_gsv has a parity-exception op_cache, all nine pairs are listed under gaps, and the laurens_gsv pair is simply not run. CI is green on 5de1092 and the PR merges cleanly.

🤖 Generated with Claude Code — Opus 5.5, claude-opus-5-5

@jonfroehlich
jonfroehlich merged commit ba1dd5d into main Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant