Skip to content

fix: answer unaddressed review comments in recon, verification, experiments, viewer and docs - #242

Merged
SamGu-NRX merged 80 commits into
alphafrom
fix/review-debt-bs-pipeline-opus
Oct 14, 2026
Merged

SamGu-NRX merged 80 commits into
alphafrom
fix/review-debt-bs-pipeline-opus

Conversation

@SamGu-NRX

@SamGu-NRX SamGu-NRX commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

This PR fixes review comments on ten already-merged PRs that nobody addressed before merging. It covers everything outside the iPhone app: the reconstruction worker, the verification harness, the packet proposals, four experiments, the integration viewer and the docs. Each original PR has its own section below, linked to its threads. The remaining items include contract decisions, an unsafe photos-only bound needing a product tradeoff, and a fixable replay defect reassigned to the iOS caretaker. They are listed at the end.

#20: recon: handoff to the server team

  • Free space could rest on one view (r4114128918, r4115044065).
    • Wall and ground coverage need two camera positions at least 0.25 m apart. Facing and overhead clearance only needed the fused volume's weight, which proves just that some frame's ray reached the voxel.
    • compute() now hands free_space a SeenFromTwo view of the volume. Its observed() also requires two such positions, each with a ray that reaches the sample according to that frame's own depth. The clearance march batches 16 slabs at a time; wall-front geometry receives the raw volume while clearance samples retain the two-view check. Visibility projections are bounded and use cached packed partner masks instead of repeated dense camera-pair multiplication.
    • A new test gets no clearance from one camera, none from two cameras 0.1 m apart, and 2.5 m from two cameras 0.4 m apart.
    • Current combined recon pytest suite: 158 passed on 6c0639e.
    • Caretaker microbenchmark: 24 cells, 32 frames and 5 cm voxels, three runs; median 0.7403 s before vs 0.3146 s after, 2,976 vs 192 visibility queries, identical answers. This small synthetic run does not establish full-capture throughput.

#240's ZIP admission follow-up is included: footer selection agrees with Python's reader, and malformed comment footers or ZIP64 locator overrides are refused before metadata allocation. Tiny archive regressions preserve the cap and normal-comment extraction.

The handoff now lists two-view free-space coverage among the fixed behaviors. All 11 visibility tests pass; the overhead footprint and photos-only uncertainty decisions remain open. Handoff review.

Vet follow-ups protect the effective ZIP extraction directory and its ancestors/descendants before startup cleanup. Cached members are verified by name, size and streamed CRC and damaged caches are re-extracted under a lock. Overhead samples include both edges of the existing 0.56 m footprint, catching the missed 1.90 m eave; support for other battery depths still needs the server footprint contract. At df7b75d, twelve new recon cases failed before these fixes; the guarded recon suite passes 125 tests on #240 and 149 on #242.

The Sol breadth follow-up closes the occupied-slab bypass: obstructions always stop clearance marches, but a measured gap and observed band require two-view support at the hit. One camera and two nearby cameras now export no unsupported measurement; two separated cameras retain supported measurements. Both edges of the existing 0.56 m footprint remain covered. Four controls fail on aaacc0f before this repair. Reported corner allowances must also be finite and nonnegative before rule adoption, with zero allowed; seven new controls fail on aaacc0f. Guarded suites on 6c0639e pass 474 verification, 158 recon, 99 meter-closeup and 20 drift tests.

Facing, overhead and wall-band samples now measure heights from ground.height_at(x,z) at each point, including slopes away from the wall. An uphill eave reported as 2.50 m before the fix is conservatively reported at 1.40 m above its cell ground. Observation at every slab still requires two separated camera positions. Local ground review.

#13: verification: independent end-to-end checks (S4)

  • Hardcoded headroom (r4114226410). The ETH3D facade scene builder reads headroom.min_ft from server/rules.yaml at --rules-ref (default origin/t3/server, as the e2e harness uses) and records the ref and value in build_report.json. case.json's rules_assumed now lists headroom, so the existing mismatch check catches a private override through the threshold the server reports. BATTERY_HEIGHT_FT is still a constant. No thread raised it, but it is the same kind of problem.
  • Unbounded response reads (r4114226421).
    • post() now uses http.client. One deadline covers DNS, TCP connect, TLS handshake, headers and body, and every status is capped at 4 MB. A bounded worker wait returns on deadline and closes sockets registered before TCP/TLS. OS DNS cannot be cancelled; a late result is checked before it can allocate or send. The cap's only evidence is that the largest result seen so far was 16 KB.
    • Tests run a local server that floods or trickles headers and body, on both 200 and 500.
    • Unlike urllib, http.client ignores http_proxy. Tests cover reduced remaining TCP/TLS budgets, blocked DNS/TCP/TLS cleanup and a localhost TLS peer that never completes its handshake.
  • make sim-app passed on a shortcut (r4114865922).
    • The run must now log findMeter, meterCloseUp, wallWalk, markFeatures, uploading and result in that order. Each gapRequest must be followed later by an uploading. The names are ScanPhase's.
    • Tests accept the recorded runs at a39d0a5, f14947e and c820cf1. They reject an immediate result, a result with no upload, an unanswered gap request, and an early result hidden by a later complete flow.
    • Limit: a recapture the app never shows logs no state, so this check can't catch it.
  • A new passing start slipped past the monotonicity check (r4114865928).
    • A start that the original sweep never evaluated now takes the outcomes of the original's neighbouring runs on that wall. This is sound because the solver evaluates every point where an outcome can change, plus the midpoints between them (server/solver.py:9-10).
    • A new pass is flagged only when neither neighbour passed, or when the wall had no run. The more-error check gets the same rule.
    • Regressions cover a pass in a fail gap, a pass on an unswept wall and a pass past the last run, and each fails on alpha. A boundary that legitimately moved still passes.
  • Accessibility audit treated different screens as one (r4114865931, r4115043215). The screen key is now every element in tree order, by type, identifier and label. Unlabelled groups are skipped, and values and frames are left out. It typechecks against the iOS simulator SDK, but I didn't build or run the UI test.

python -m pytest -q in verification/: 554 passed on the combined branch. Ruff lint and formatting pass for verification and recon. Facade regressions retain clipped tail probes and the active headroom in the same observed entry.

This branch also contains #240's shared root Makefile dispatcher repair: verification is included, with standalone, missing-folder and failure-propagation cases in tests/makefile.sh. That helper is the cross-folder change required to make the verification suite run from the root.

The merged #240 deep-review repairs also require actual execution of all five hostile probes for full S2-8 credit. Generated and committed geometric cases bind their expectations to battery width and the applicable corner allowance; altered rules skip those expectations and do not establish acceptance. The shared root verifier target now runs locked dependency sync before child tests.

Codex found that a server-reported sweep such as [0, 1e9] could allocate billions of starts. Sweep admission now rejects spans outside the scanned chain before sampling, and the sampler validates eagerly and yields lazily with a 10,000-sample cap including the endpoint. Oversized runs fail with a specific message rather than get truncated. Regressions cover both outcomes, both scan ends, a distant singleton, tiny rule steps and the exact cap boundary. Sweep sampling review.

The rule-assumption follow-up compares case assumptions with reported spot width and rule-keyed thresholds. Geometry adopts a validated reported width only for a different policy fingerprint; a matching public fingerprint retains the loaded rules and checks reported dimensions against them. When the result omits a value that differs from the public rule, the report says "assumed public rules; server may override" and marks the record assumptions unverified. Rule-dependent checks are withheld, rule-independent failures remain failures, and JSON, Markdown and the CLI expose the gap without granting acceptance. An otherwise clean inconclusive run exits 2. All 392 verification tests pass, covering matching overrides, real reported mismatches, absent values, report/scoreboard gates and the existing hostile-span bound. No private backend files were read. Rule assumption review.

One runtime meter counts the geometry and comparison work that actually runs, including unsure checks, footprint lookups, ground grids, coverage and corner checks. Identical sweep records are deduplicated, and identical geometry checks are memoized. Structural admission rejects nonfinite numbers, overflowing span lengths, invalid dimensions and unknown wall references before geometry. The ground sampler is lazy; ordering comparisons use an interval index. A failed admission or invariant stops property comparisons, and any check exception becomes a named report problem. Exceeding the runtime limit produces verification work limit exceeded (N samples). Verification ZIP admission still counts directories before metadata allocation, responses remain bounded at 4 MiB, and required simulator states must occur in order.

The runtime limit is now 7,834,000 units, twenty times the remeasured valid peak of 391,700 on a saved ETH3D facade response. After caching, 35 valid public-fixture judgments including property variants peak at 31,649 units. Twenty additional measurements cover an 80-foot surveyed wall written as twenty walls, each subdivided 1, 4, 12, 13 or 20 times, original and mirrored, with full-judge resends. Those peak at 88,999 units. Another 468 distinct saved responses are included in the measurement corpus. These are operational work counts, not physical clearance accuracy.

The recorded public-solver response for an 80-foot wall has 683 candidates and passes with 29,760 actual visits and no ground-grid calls. Both missing-evidence branches use 130 visits for one unsure check or 10,000 duplicates. The old 2d959c7 prediction rejects the valid response and invokes geometry 10,000 times in either branch. The remeasured valid peak is 391,700 units.

Reported dimensions must be finite, positive and consistent with reported spans and footprint edges before rule adoption. A large reported width with no geometry to sample incurs only the work actually performed. Adopted dimensions and property variants charge the same judge meter. More error may add a failing run on a wall the original response never evaluated; a newly passing run remains a violation. Regressions cover invalid dimensions, span and footprint agreement, newly evaluated walls and bounded remaining response reads.

Current guarded suites pass: 594 verification, 168 recon, 99 meter-closeup and 20 drift-anatomy tests. All ran through run-guarded.py --min-free-gib 8 after disk met the 10 GiB startup threshold. Six sloped-ground controls and both reported-rule controls fail on 6c0639e before this repair. Ruff format checks pass in verification, recon and all touched experiment directories. The nine held formatting files preserve their Python syntax trees.

Unknown public-rule assumptions still withhold rule-dependent checks and case expectations, but identical-input, mirror and coverage comparisons continue. A nondeterministic response therefore fails verification even when the result omits its width. Rule-independent checks review.

#22: packet: point to the server's capture packet 0.4 and propose 0.5 additions

  • Ground height in raw world coordinates (r4114080314). Proposal 2 now carries meterAnchor.groundOffset along the anchor's +y, so the ground moves with the meter when ARKit corrects the anchor. The app's local packet already records it this way (ground_y_m, PacketWriter.swift:412).

#59: experiments: signals lab round 1, which extra phone signals sharpen clearances

  • A3 measured along travel, not along the wall's normal (r4113839737, r4115917036).
    • The axis across the walk is the wall's normal only while the walk runs straight along the wall. A new table restricts A3 to windows whose path stays within 0.25 m or 0.5 m of its start-to-end line. Both widths are untuned.
    • On those windows, along-travel error is 0.98 and 0.95 of the p90 on MARViN at 20 ft. This is a straight-path check, labeled throughout the code, table, README and METHODS. Without wall annotations it cannot identify wall-parallel travel or establish wall-plane benefit. The numerical results are unchanged; 20 metric/report tests pass.
    • Rerun against the evals harness at 190ed33. New strict tests are in tests/test_metrics.py.
  • A4's anchor moves (r4113839728, r4115917030). Not a bug. Each window is one session that starts at the meter, and the second phone stays at the window's start. Sliding the start samples many meter placements. METHODS, the README and the generated report now say so.
  • M3 picked the narrowest photo pair (r4115044359).
    • run.py --pair=widest reruns M3 with the widest qualifying pair and writes separate results.
    • On electro with modern_assumed poses, the 3-D p90 is 158.7 in for the widest pair against 154.6 for the narrowest, and 41% of points match either way. With exact poses the wide pair is worse, 156.4 in against 112.5, because fewer of its matches land within 2 px. P4 fails under both, so the narrow pairing isn't what makes M3 look bad.
    • Wide pairs overflowed cv2.remap's row limit, so the epipolar patches are now sampled in chunks. A segment that fit before is still one chunk. The test for this fails on alpha.

A3's failed 60% numeric criterion is now labeled a travel-axis proxy in README and METHODS. Wall-plane benefit is untested without wall annotations; all numerical tables remain unchanged.

#86: experiments: live dots, a macOS prototype of the holographic capture view

  • Fog mask kept the previous view (r4115037778). An explicit seek counter resets the mask on same-keyframe seeks, forward/backward scrubs and resuming a settled preview. Natural same/adjacent playback and ordinary pause/resume preserve easing. The renderer uses the tested continuity decision for both motion settings; mode changes and fog re-entry still reset.
  • Low-confidence depth carved lasting edges (r4115037781). Carving now skips pixels below Tuning.minimumConfidence, as fusion already did. The new test fails with alpha's DotField.swift.
  • --still 2 --at 0.3 rendered keyframe 3 under keyframe 2's name (r4115037787). An offset past the keyframe's time on screen is now an error that suggests --still 3, because stills are named and compared by keyframe.
  • Constellation hid every feature point (r4115916347). The edge birth time now applies only to non-feature sprites.

On b6bc4ad, swift build --jobs 2 succeeds and swift test --jobs 2 passes all 45 tests in seven suites. Both commands ran through run-guarded.py --min-free-gib 8. The first test build exposed direct mutating calls inside #expect; the tests now evaluate those calls before asserting their results. Production playback and reset decisions are covered, including same-keyframe seek, settled scrub resume under both motion settings and ordinary pause/resume continuity. Metal pixels and native interaction were not inspected.

#16: experiments: meter close-up reading, tap-to-confirm and two retake checks

  • OCR joined distant fields on one row (r4114120264).
    • Boxes in a row now join only across a gap of at most 3 character widths. A character width is a box's width over its characters, in the image's own x units, so no aspect ratio is needed.
    • On the cached Vision reads (clean and locate runs, every configuration, strict and lenient), any limit from 2 to 6 widths leaves every number match unchanged, and 1.5 loses three. The committed results therefore stand.
    • I didn't rescore the sweep's degraded reads.
  • A stale Keep survived a label change (r4114120267). Each saved answer now records the number it was given for, and the page keeps it only while it still shows that number. Answers saved before this change are dropped. The new test fails on alpha.

PYTHONPATH=src python -m pytest -q in experiments/meter-closeup/: 99 passed on the combined branch.

The deep-review follow-up found that CSV import discarded which reader label a Keep confirmed. Downloads and ingest now preserve shown plus whether it was recorded, including a reviewed empty label. Both the page and manifest reject unbound or mismatched imported Keeps. A legacy Keep cannot upgrade an earlier correction's verdict. Tests download, ingest, change the reader label and reload in fresh/stale browsers; numeric, empty and NONE labels, repeated ingest and prior corrections are covered. The original import fallback fails the new legacy-empty regression. Deep-review finding.

#124: tools: viewer that films a capture reaching the server, as a labeled replay or read-only live view

  • Counters missed a final batch that couldn't animate (r4114688573). Count timing moved into count-timers.js. Each render queues what it drew and when its marks land. Its totals reach the counters once its own marks and every earlier render's marks have landed. A batch with no marks still shows, and counters never run ahead of marks in flight. Tests on a virtual clock fail with alpha's rule.
  • Large batches expanded every file (r4115036581). markGroups chooses the at most six marks from per-group totals, making the same picks as before. send() reads nothing when motion is off.
  • Run switching (r4114355831, r4114542142, r4114585684). No change. Commit 9c0680b made the status endpoint the only thing that chooses the run, and removed the ranking code the last two threads describe. An event naming a run the status hasn't named makes live.js read status at once (model.js:197, live.js:79, test at model.test.js:311).

node --test "test/*.test.js" in tools/integration-viewer/: 104 passed, including the headless browser test.

#134: docs: rebuild the README around the demo, with quick start, data and limits

  • Animations ignored reduced motion (r4115037528). Both animated pictures now list still WebPs first, for each colour scheme, under prefers-reduced-motion: reduce. Each still is its animation's last frame, 29–37 KB. I checked the selection in headless Chrome against the live GitHub page and the new markup, under all four scheme and motion combinations.
Before (alpha, reduced motion) After (reduced motion)
README illustration with reduced motion on alpha: the animated WebP loads README illustration with reduced motion after this change: the still WebP loads

#14: docs: keep only the current plan and the facts that still hold

  • Light-theme text contrast (r4112033407). docs/how-it-works.html gains text-only colours --phone-ink and --server-ink, while diagram fills keep the originals. In light mode, links go from 4.04:1 to 4.63:1, blue labels from 4.42 to 5.06, and orange labels from 3.20 to 5.03. Dark mode is unchanged and already at least 4.69:1.
  • Coverage zone stopped at cable reach (r4112033409). The diagram now draws the candidate spots within the 20 ft of cable, and around them the zone the checks look at: about 11 ft out, past the cable's reach at both ends. Nothing outside cable reach is called "not needed". The caption gives each check's reach and calls the pool and driveway values placeholders.
Before (alpha) After
Coverage diagram on alpha: a rectangle at cable reach, everything else labelled not needed Coverage diagram after: candidate spots inside a rounded needed zone that reaches past the cable
Drift chart on alpha with the lighter orange and blue labels Drift chart after with darker text colours for the labels

Light theme at 900 px wide, from the page served locally. Dark-theme and 390 px captures were checked but aren't attached.

#18: Research handoff: live survey findings, options and tested prototypes

  • The ground-replay patch loses evidence past a marked end (r4111600380). The defect is real. The patch is an unrun review artifact for an old baseline, so native-proposals/README.md now records it next to the patch's other known gaps, and the patch bytes are unchanged. Alpha's replay also drops regular sightings past current ends, but clearing a limit end calls replayPastLimits() for past-limit ground. It does not rebuild those regular wall/ground sightings. The README distinguishes these behaviors. The remaining replay loss is a fixable defect reassigned to the iOS caretaker, not a policy decision.

Verification accepts root scene.json or a scene under one shared top-level capture folder, including the public API Finder fixture. Unlike the public API's root precedence, it refuses both forms together so the verifier has one unambiguous input. Every non-metadata entry must lie beneath the shared folder for the wrapped form. All entries still count toward archive limits. Duplicate and ambiguous layouts remain rejected. Wrapped bundle review.

Merge order

Merge #240 first, then #242. #242 contains #240 at a7b3169 through merge commit daa36a2. Both include alpha 3c33ad7, retain the same runtime work meter and ingestion bounds, and preserve the shared Makefile dispatch repairs. Neither branch was rebased or force-pushed.

Still open

The ground-reach, variable-footprint and yaw contracts remain open. Photos-only uncertainty is an existing unsafe bound needing a product tradeoff; the iOS replay defect has been reassigned.

  • How far out the worker reports ground. It stops at 10 ft, and the checks need the rule distance plus position error, 13 ft per the docs (#20 r4114022057).
  • Variable battery depth in overhead sampling. The worker now samples both edges of the 0.56 m footprint, offset from the reconstructed wall front. The rear gap is fixed; other battery depths still require a server footprint contract (#20 r4114128923, r4115044069).
  • The existing unsafe error bound on photos-only clearances. The current ±0.56 ft comes from surface pair error, not measured clearance-boundary uncertainty. Sam must choose a measured real error bar or withholding unsupported clearance evidence (#20 r4114128935, r4115044080).
  • A per-capture yaw uncertainty for the meter anchor in packet 0.5 (#22 r4114003866).
  • The fixable iOS replay defect in clearEnd, reassigned to the iOS caretaker (#18 r4111600380).

Checklist

  • The scope is focused; related changes belong together. Every change answers an existing review thread.
  • I explained what changed and why
  • I included before/after images for visible changes
  • The earlier static changes have before/after images above. This round adds tested fog-continuity repairs; no new native renderer video or runtime accessibility inspection was captured.

Current validation limits

Scene/rule derivatives and wall lookups are cached per judgment. Direct coverage builds no ground fallback strips, and redundant collinear vertices no longer multiply work. Full judgments at 13 and 20 subdivisions fail at the old limit and pass now within 20% of the one-segment representation. Distinct evidence IDs and duplicate diagnostics use hash lookups.

Metadata admission precedes schema validation, copies and resend comparison. It bounds walls to 50, vertices per wall to 500, runs to 30,000, checks and requests to 16,000 each, numeric leaves to 1,000,000, JSON nodes to 2,000,000, nesting to 32 and string/key characters to 4 MiB per document. Local case rules have 64 queries. Boundary controls verify clean admission and specific over-limit diagnostics.

Reported widths and corner allowances now share the same handling in both PRs. A case mismatch uses the response's reported value. Geometry adopts a validated reported override only for a different policy fingerprint. Matching public fingerprints retain the loaded rules and contract-check all reported battery dimensions, route height and corner allowance, including transformed responses. An unreported value is unverified when policy sources include private rules or the normalized rule fingerprint differs from the tested public rules, even if the assumed numeric value equals the public default. Four private-source/fingerprint controls fail on 30489f8 before this repair. Encrypted NPZ flags now trigger locked cache repair; the damaged-file control fails before the repair with a password-required RuntimeError.

Research-handoff has ten pre-existing Ruff lint diagnostics, confirmed against 6c0639e. Its formatting checks pass, and the formatting changes preserve Python syntax trees.

Python checks use the existing Python 3.12 environment at /Users/samgu/Programming Projects/house-scanning-real-intake-20261004/.venv/bin/python. Recon has 165 passing tests; its OpenCV 5.0 install is outside the declared <5 range. No dependencies were installed. Root Makefile dispatch cases pass. No model inference, dataset regeneration, private backend, simulator or phone checks ran in this caretaker round. Earlier experiment/viewer/media evidence above belongs to the original Opus run.

Summary by CodeRabbit

  • New Features
    • Reduced-motion illustrations now use separate still images for light and dark themes.
    • Integration viewer counters track marks as animations land, including when updates arrive out of order.
  • Bug Fixes
    • Live-dot visibility and fog transitions respond more consistently to playback changes and depth confidence.
    • Meter-number recognition is less likely to combine text that is too far apart, and saved review decisions are less likely to apply to a different number.
    • Free-space coverage now requires evidence from two camera positions with sufficient separation.
  • Documentation
    • Clarified scan coverage boundaries and updated guidance on review, playback, and measurement results.

Null rules_assumed and null expectation query arrays now produce bad input records before counting. A regression saves both failed cases and a subsequent valid case in one report. The shared repair is merged from #240, which lands first. The matching-public wrong-width and malformed-case controls fail on a7b3169. Public-policy review. Case metadata review.

Repeated sweep and check expectation failures now produce one message per expectation, with a failure count, the reason once and at most three examples. A shared report budget retains at most 1,000 diagnostic entries and 262,144 serialized diagnostic characters, including duplicate JSON fields, and emits one omitted-count summary. Contract counts and acceptance are computed before display truncation; raw response payloads remain separate. Markdown-visible explanations receive budget priority over their duplicate JSON classifications. Both 1,000-error regressions retain the first explanation in the Markdown row and stay within the shared limits; both fail on c32ee97. Numeric cache and interval-map keys use exact rational strings, preserving equal integer/float values while preventing cheap modulus collisions. Regressions cover 1,000 runs with a 4,096-character reason, oversized reasons, JSON escaping and 2,000 colliding integers across four key paths.

The sampler count API now explicitly counts starts before the final endpoint; run_starts yields those starts and then the endpoint. Pinned db1e4c2 controls confirm that the old implementation already checked the last pre-end start, checked a sub-step run's first start and charged each yielded sample. The new count-API assertions fail there; the functional sampling controls already pass. Sampler review 1. Sampler review 2.

Free-space visibility now rejects camera rays blocked by fused occupied voxels, using the same stop-short tolerance as regular visibility. A nearer thin fused wall defeats both depth-backed rays, while clear rays and the target surface itself remain supported. The blocked-ray control fails on c557e3a. Fused-ray review.

SamGu-NRX and others added 30 commits October 13, 2026 17:41
…s meter is each window's start

A3 split error along and across each window's travel, but a wall plane
removes error along the wall's normal, which is the across axis only while
the walk runs straight along the wall. Windows whose reference path stays
within 0.25 m or 0.5 m of its start-to-end line now get their own table.
On MARViN at 20 ft along is 0.98 and 0.95 of the p90 there, so the
conclusion holds on the windows that test it. Graded rows are unchanged.

A4 already ranges from a fixed meter: each window is one session that
starts at the meter, and the range runs from that start to the window's
end. METHODS, README and the generated report now say so.

#59 (comment)
#59 (comment)
#59 (comment)
#59 (comment)

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

The review page restored browser answers from a fixed localStorage key by
photo id, so after a label correction or dataset update a stale Keep showed
as done and was exported for a number nobody had checked. Each saved
answer now records the number it was given for, and startingAnswers keeps
it only while the page still shows that number; otherwise the ingested
answer, if any, applies and the photo is open again. Answers saved before
this change carry no number and are dropped the same way.

#16 (comment)

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

rows_of_text joined every box sharing a row, so digits from an unrelated
field further along the row could complete a labelled number and union its
box with that field. Boxes now join only across a gap of at most three
character widths (box width over its characters, in the image's own x
units, so no aspect ratio is needed); a row splits into chains at wider
gaps.

Checked on the cached Vision reads of the labelled photos (clean.jsonl and
scan.jsonl, every configuration, strict and lenient): limits from 2 to 6
widths change no number match and 1.5 loses three, so the committed
results stand without a rerun. The sweep's degraded reads were not
rescored. The limit does split unrelated fields on these photos, but no read depended on those joins.

#16 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…past a marked end

The unrun patch replays accepted cameras under the current marked ends, so
sightings recorded before an end was marked are dropped and clearEnd has
nothing to restore them from. The README now says so next to the patch's
other known gaps. The patch bytes are unchanged, so patch-identities.json
still holds. Alpha's in-app replay has the same shape; that is reported for
the iOS lane, not changed here.

#18 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Facing and overhead clearance used the fused volume's weight as "observed",
and that weight only says some frame's ray reached a voxel. A capture with
many frames could still report clearance that a single view vouched for,
while wall and ground already need two positions 0.25 m apart.

compute() now hands free_space a SeenFromTwo view of the volume. Its
observed() also requires that two camera positions at least BASELINE_M
apart each had a ray reach the sample, judged by that frame's own depth at
the sample's pixel. free_space and _slab are unchanged, so this sits beside
#240's change to the march rather than on it. On a synthetic 66-cell wall
with 150 frames the free-space pass takes 8 s instead of 0.6 s.

#20 (comment)
#20 (comment)

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

build_scene.py probed the overhead band at a hardcoded 6.5 ft. It now reads
headroom.min_ft from server/rules.yaml at --rules-ref (default
origin/t3/server, the e2e default) and records the ref, commit and value in
build_report.json.

case.json pins headroom 6.5 in rules_assumed. A server whose headroom check
reports another threshold, from changed rules or a private override that git
cannot show, is then marked "assumption mismatch" by the existing e2e check
instead of being judged against a scene probed at the old height.

Review thread: #13 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rebuild and unseen illustrations were animated WebPs in both colour
schemes, so they kept moving under prefers-reduced-motion. Each <picture>
now lists a still light and dark source first, gated on
(prefers-reduced-motion: reduce). The stills are the last frame of each
animation, the frame it holds longest, encoded as WebP at quality 85
(29 to 37 KB each).

Review thread: #134 (comment)

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

When an older batch's count timer was pending and a later batch could not
animate (motion paused, animation clock stopped, mark cap reached), the
later batch skipped its immediate count update and the older timer wrote
only its own snapshot, so the newest files stayed off the counters until
some unrelated render. The last pending timer now writes the current
totals; earlier timers still write their own snapshot, so a counter never
runs ahead of marks still travelling.

The timer bookkeeping moves out of app.js into count-timers.js so a test
can drive it with fake timers. Two of its four tests fail when the timer
writes only its snapshot, as on alpha.

Review thread: #124 (comment)

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

Conduit.send built an array entry for every acknowledged file before
capping the animation at six marks, so a large batch allocated memory in
proportion to its size just to draw a few marks. markGroups now walks the
per-group totals and returns only the marks, with the same evenly spaced
picks as before. send() returns before reading the batch when motion is
off, the batch predates the connection, or the line is not laid out.

The tests compare markGroups with the old expansion over small batches,
and check that a 5e9-file batch sends six marks and that motion-off reads
nothing. On alpha, the send() tests fail (RangeError and a read with
motion off).

Review thread: #124 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proposal 2 put the ground at a raw world groundY. ARKit corrects the meter
anchor as tracking improves (proposal 10), so a world height recorded at
one time and an anchor pose read at another give the server a wrong
meter-to-ground offset, and with it wrong vertical placement and headroom.
The proposal now carries groundOffset along the anchor's +y, which moves
with the meter, as AGENTS.md asks of AR results.

#22 (comment)

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

Links used --phone (#2a78d6, 4.04:1 on --bg) and the blue and orange SVG
labels used --phone and --server (#eb6834, 3.20:1 on --card), under the
4.5:1 WCAG AA minimum for normal text. New --phone-ink (#1e6ecb) and
--server-ink (#c44400) carry text: same hue, OKLCH lightness lowered until
they clear 4.5:1 on both backgrounds (4.63/5.06 and 4.61/5.03). Diagram
fills, strokes and the focus ring keep the original tokens, which only
need 3:1. In dark mode the ink tokens equal the fills, which already pass
(4.69:1 and 5.26:1 on --card).

Review thread: #14 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
post() read success and HTTPError bodies with an unbounded read(), and
urllib's timeout applies per socket operation, so a server that trickled
headers or body could hold a run past its budget or fill the harness's
memory.

post() now speaks http.client directly. A timer shuts the socket down at
timeout_s, which ends any read in progress, and the body is read in 64 KB
chunks up to MAX_RESPONSE_BYTES (4 MB) for every status. Past either limit
the answer is status None with the reason as payload, and the callers report
that reason. Tests use a local server that floods or trickles, for both 200
and 500, with a 16 KB cap and a 0.5 s deadline.

One behaviour change: http.client does not read http_proxy/https_proxy as
urllib did.

Review thread: #13 (comment)

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

M3 pairs each point's reference photo with the narrowest ray angle at or
above 5 degrees, which keeps the patches alike for matching but
triangulates worst. `--pair=widest` picks the widest qualifying angle and
writes results/edge_geometry_widest_pair.*, leaving the pre-registered
tables alone, so the two policies can be compared before M3's error is
treated as representative.

#59 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fog's lagged reveal mask eased from the previous frame unless the
frame was settled. A scrub during playback, a LiDAR mode switch or the
fog coming back on therefore eased from another view's mask and briefly
left unmeasured regions clear. The renderer now snaps the mask unless the
frame continues the last fogged one (same mode, same or next keyframe).

Review thread: #86 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Surface fusion skips depth pixels below medium confidence, but free-space
carving read them anyway. A low-confidence zero or far reading could mark
a wall voxel's neighbour as seen-empty, and since free space and edges are
both sticky, that left a lasting false boundary edge. Carving now applies
the same confidence gate. The new tests build one keyframe of a flat wall
with a strip of bad depth and check that only a confident hole carves.

Review thread: #86 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`--still 2 --at 0.3` rendered keyframe 3 while the log named keyframe 2
and printed its stats. Stills are named and compared by keyframe, so the
export now fails with the keyframe's on-screen time instead of writing a
mislabelled image. The last keyframe takes any offset, since it stays on
screen.

Review thread: #86 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Constellation delays a dot's birth to when it became an edge. Feature
points never do, so their edge time is the clamped 1e6 s and every
simulated no-LiDAR point stayed hidden. The delay now applies only to
voxel dots.

Review thread: #86 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
make sim-app passed any run that reached STATE=result and logged a bundle,
so an app that showed a cached or prebuilt scan straight away passed too.

simrun gains --require-order (states that must appear in that order, others
allowed between) and --require-then FIRST:THEN (every FIRST must be followed
later by a THEN). sim-app now requires findMeter > meterCloseUp > wallWalk >
markFeatures > uploading > result, and gapRequest:uploading, so a recapture
round the app opens has to end in another upload. The names are ScanPhase on
t3/ios-mvf. A round the app never opens leaves no STATE behind, and these
checks cannot see it.

The tests read the flags from the Makefile recipe. They accept the state
sequences of recorded runs at a39d0a5, f14947e and c820cf1 and reject a jump
to the result, a result without an upload, an upload before the walk, and a
gap request that never led to an upload.

Review thread: #13 (comment)

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

The less-coverage check skipped any start that the original sweep did not
contain, so a changed scene could add a passing start in a gap or on a wall
the original never swept. If the original already passed somewhere, the
decision guard missed it too. Failing every None-to-pass would be wrong:
coverage edges are evaluated starts, so moving them adds starts inside the
original's gaps between runs.

outcomes_near() reads such a start from the original's neighbouring runs on
that wall. The server evaluates every start where a check can change outcome,
plus the midpoints between them (solver.py docstring, t3/server 1d1e7e1), so
between two evaluated starts the outcome is one of theirs. A new pass is then
a violation only when neither neighbour passed, or when the original had no
run on that wall. The more-error check had the same skip and now reads gaps
the same way.

Regressions: a pass inside a fail/unsure gap, a pass on an unswept wall while
the original decision was already pass, and a pass past the last run all fail.
A boundary that moved into a fail/pass gap still passes.

Review thread: #13 (comment)

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

The harness keyed screens on static-text and button labels only. A later
screen with the same labels plus a text field, toggle, image or link was
treated as already audited, so performAccessibilityAudit never ran on it.

The key now lists every element in tree order by type, identifier and
label. Unlabelled grouping containers are left out so view nesting does not
decide the key. Values (typed text, toggle state, slider position) and frames
are left out too, because they change while the screen stays the same and
each change would start another audit and reset the idle timer. The
A11Y_SCREEN labels the report shows are unchanged.

Checked with swiftc -typecheck against the iOS 27 simulator SDK and XCTest.
The UI-test fixture was not built or run.

Review threads:
#13 (comment)
#13 (comment)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous two commits added the count-timer and mark tests to the
"The tests cover" line, which open PR #238 also rewrites. Restore alpha's
wording so the two branches merge without a conflict; the file table row
still names count-timers.js, and each test file describes its own cases.

Review threads: #124 (comment)
#124 (comment)

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

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30489f8a6c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verification/hsverify/resultcheck.py Outdated
Comment on lines +1591 to +1593
if configured.get(key) is not None
and not reported_assumption_values(key, result)
and abs(configured[key] - value) > EPS

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark unreported private overrides as unverified

When a case assumes the public value and a server uses a private override but reports no corresponding value—for example, a wider private battery leaves no spot, nearest_considered, or width check—this condition suppresses the warning because configured[key] == value. judge() then runs expectations and geometry using the public rule and may claim contract_ok even though the response's policy.sources or rules_sha256 identifies different rules. Treat an unreported assumption as unverified whenever the response used non-public rules rather than using equality with the public default as evidence.

AGENTS.md reference: AGENTS.md:L33-L33

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

Reviewed commit: daa36a243b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verification/hsverify/e2e.py
Keep loaded dimensions when the response claims the tested public fingerprint, including transformed responses. Reject malformed local case fields without losing the report.

Reviews: #240 (comment) #240 (comment)
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c557e3af38

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verification/hsverify/resultcheck.py Outdated
Comment on lines +1042 to +1045
def run_starts(span: list[float], step_ft: float | None) -> Iterator[float]:
count = run_start_count(span, step_ft)
a, b = span
return itertools.chain((a + k * step_ft for k in range(count - 1)), (b,))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Sample every pre-end start in each sweep run

For any nonzero run, run_start_count already counts the sampled starts before the final endpoint, but run_starts iterates range(count - 1), dropping the last step (and for a run shorter than one step, dropping the first endpoint entirely). Coverage, footprint, and declared-height checks therefore skip valid battery positions and can accept a passing result whose omitted start would fail. Iterate all counted pre-end samples before appending b.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

run_start_count returns count + 1, including the final endpoint. run_starts therefore emits count - 1 pre-end samples and b. At c557e3a, [0, 0.1] with step 1 emits [0, 0.1], and [0, 2] with step 1 emits [0, 1, 2]; changing the range would duplicate or overshoot the endpoint.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Comment thread verification/hsverify/resultcheck.py Outdated
Comment on lines +1042 to +1045
def run_starts(span: list[float], step_ft: float | None) -> Iterator[float]:
count = run_start_count(span, step_ft)
a, b = span
return itertools.chain((a + k * step_ft for k in range(count - 1)), (b,))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Sample the first start before stepping through sweep runs

For any nonzero run, run_start_count already counts the sampled starts before the final endpoint, but run_starts iterates range(count - 1), dropping the last step (and for a run shorter than one step, dropping the first endpoint entirely). Coverage, footprint, and declared-height checks therefore skip valid battery positions and can accept a passing result whose omitted start would fail. Iterate all counted pre-end samples before appending b.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

run_start_count returns count + 1, including the final endpoint. run_starts therefore emits count - 1 pre-end samples and b. At c557e3a, [0, 0.1] with step 1 emits [0, 0.1], and [0, 2] with step 1 emits [0, 1, 2]; changing the range would duplicate or overshoot the endpoint.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Comment thread recon/recon/coverage.py Outdated
Comment on lines +161 to +168
n_i = np.flatnonzero(ok)
dh, dw = depth_map.shape
scale = dw / w
du = np.clip((u[n_i] * scale).astype(np.int64), 0, dw - 1)
dv = np.clip((v[n_i] * scale).astype(np.int64), 0, dh - 1)
measured = depth_map[dv, du]
reached = np.isfinite(measured) & (measured >= z[n_i] - tolerance(dist[n_i]))
out[n_i[reached], j] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reject free-space rays blocked by fused occupied voxels

seen_through accepts a sample whenever the camera depth is at or behind the sample, but unlike seen_by it never calls vol.blocked (or otherwise tests occupied voxels between the camera and the sample). If the depth map sees through a thin or missed obstacle that the fused volume contains, two separated cameras can therefore mark the slab as observed and free_space can report clearance past the obstacle. Apply the same occupied-ray blocking test used by the regular visibility path before setting out.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Incremental review skipped.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Aggregate expectation failures with bounded examples and cap serialized diagnostic text across the report. Use exact string keys instead of adversarial numeric hashes. Make the pre-end sampling count explicit while preserving sampled starts.

Reviews: Astra resource addendum; #242 (comment) #242 (comment)
Use the regular visibility path stop-short tolerance before crediting a camera ray. Preserve support for the target surface itself and require two unobstructed views.

Review: #242 (comment)
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 1ed45b65b5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread verification/hsverify/diagnostics.py
Prioritize the Markdown-visible problems array before duplicate JSON classifications. Regressions retain the first explanation for 1,000 contract or expectation failures without exceeding the shared diagnostic budget.

Review: #240 (comment)
@SamGu-NRX

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: c4461b22bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@SamGu-NRX
SamGu-NRX merged commit 5fb8e96 into alpha Oct 14, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ effective changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant