Skip to content

Stage 0: six gaps a second site exposed, fixed one commit each - #53

Draft
PSheon wants to merge 15 commits into
devfrom
fix/stage0-gaps
Draft

PSheon wants to merge 15 commits into
devfrom
fix/stage0-gaps

Conversation

@PSheon

@PSheon PSheon commented Sep 11, 2026

Copy link
Copy Markdown
Member

What this fixes

Six gaps in Stage 0 that a second site exposed (FTI factory, 2026-09-10/11; record in /home/paul/hydranet_fti_stage0/runs/REVIEW.md). One commit each; every commit stands alone and carries its test.

commit gap what changed
fix(demo_video) track boxes drawn with / 2.0 — a 1920-wide source as a constant; 2/3 off on 1280, 2× on 3840 analytics.world.panel_box: the panel/source ratio per axis, used by the demo
fix(commissioning) pick_daytime_slot hardcoded UTC+8; a US-Central site passed the daytime gate by coincidence cameras.json carries utc_offset_hours (8 when absent); static_plates records it; onboard_camera passes it
feat(onboard) one camera pinned by name; floor_calibrate's measured vfov had no consumer --vfov-pins reads the instrument's pins file over BUILTIN_PINS; the pin is the camera's primary row and joins the sweep; vfov_source/k1_source say where each number came from
fix(rulers) catalogue consensus fooled by shared depth bias (600 mm floor read 0.85/0.74 → picked 0.80); person_ruler anchored factor 1 to a bootstrap scale Ruler.anchored; person_ruler is None on unmeasured/bootstrap; combine returns an explicit unanchored verdict instead of "within 10%"; scale_rulers marks the consensus tile unanchored
feat(tools) 30 tools derived ROOT from their own file — a second site meant a second copy of the code every tool honours SYNCAI_ROOT (the pattern scene_mesh.py already used); shipped.py resolves checkpoints through paths.repo_root; a test keeps bare Path(__file__) roots out of the chain
feat(camera_json) contract had no native frame size; a 720p or 4K stream returned metres, not an error CameraFile.source_size_px (optional; v3 files read unchanged), recorded by static_plates, carried by onboard_camera, kept by from_onboard_calib, aspect-checked in validate(), and demo_video says when the clip is not the commissioned stream
docs(figures) the figure tax both README GIFs re-cut last with the recorded render arguments — byte-identical to the published ones; only the audit sidecars (render, commit stamp) move

CI-equivalent locally: ruff format --check, ruff check, scripts/coverage_ratchet.sh — 3581 passed, 16 skipped; the two figure-staleness failures were cleared by the last commit. One local-only failure is not this branch: test_map_anything_eval asserts the 8 shipped cameras and this box now holds 9 (Tao-Hsin-cam15, another session) plus test1; CI has no runs/ and skips it.

Deliberately not in this PR

  • Files not yet in git (another session's in-flight Gate D work: commission_camera.py, floor_calibrate.py, geometry_cache.py, rebuild_geometry.py, ground_control.py): the vfov instrument's no-crossing fallback (cam632's 48→50° sign flip is a plane-pick flip reported as "no crossing"), a labelled bootstrap route for personless cameras in commission_camera, and depth_scale in recipe-built caches (scale_rulers.apply refuses them) all land there. Listed in REVIEW.md with evidence; to follow once that work is committed.
  • Site name inside the paths (datasets/studioa_static, runs/commission01): root is now switchable, the literal names are not — a paths config is the next step, after the dirty tools land.
  • GeomTeacher's runs/zones01 dependency (it only uses the walkable polygon propose_zones derives): same file as above.
  • Design decisions the code cannot make: a rack/machine/pallet class for the structure vote (racks are wall today), absolute object rulers beyond the tile catalogue, cross-camera registration into one world, and thresholds on overlay IoU (Gate D, in flight elsewhere).

Evidence the changes act on

  • FTI-SMT-cam815 (1280×720): boxes at 2/3 of the people before, on them after (demo_video, check frame 112).
  • FTI-MDF-cam821/cam632 (3840×2160): the catalogue picked 0.80 m for a 600 mm raised floor; the folding table (0.74 m) settled it at ×0.845 vs the tile's ×0.847.
  • FTI-MDF-cam821: floor_calibrate measured 62.8°; applied by hand before, --vfov-pins now.

🤖 Generated with Claude Code

@PSheon
PSheon marked this pull request as draft September 11, 2026 05:41
PSheon and others added 14 commits September 16, 2026 15:24
…onstant

The left panel is a 960x540 resize of the source frame and every track box was
drawn at box / 2.0 -- 1920/960 written as a number. The blur boxes beside them
already scaled by the probed source size, so on any non-1080p clip the two
disagreed on one panel: a 1280-wide FTI clip drew the boxes at two thirds of the
people, a 3840-wide one drew them at twice their size. analytics.world carries
the source size for placement; it now carries the panel ratio too, and the demo
draws through it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…not a +8 constant

pick_daytime_slot turned a UTC slot into store-local with (hour + 8) % 24 and the
static-plate index repeated +8 in prose. A US-Central factory (UTC-5) passed the
daytime gate by coincidence -- its 10 UTC capture read as 18 local -- and its real
daytime, 15 UTC, would have been refused as 23. cameras.json now carries
utc_offset_hours (8 when absent, so every existing file means what it meant),
static_plates records it in index.json, and onboard_camera passes it through.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-pins

The sweep pinned one camera by name (camera == "Taichung-cam01") and took the
70.4 constant as every other camera's primary row, so floor_calibrate.py's
measured vfov had no consumer: on FTI-MDF-cam821 the 62.8 the floor's right
angles gave had to be written into the calib by hand. The tile pin is now data
(BUILTIN_PINS), --vfov-pins reads the instrument's own pins file over it, the
camera's pin is its primary vfov and always joins the sweep, and vfov_source /
k1_source say where each number came from.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… metres it reads

Every ruler reads through the camera's metres; what makes one a witness is where
its reference length came from. The 1.70 m prior is anchored. A catalogue tile
chosen because the store's cameras agree on it is not: on FTI's 600 mm raised
floor DA-V2's raw metres read 0.85 and 0.74 m, standard_tile picked 0.80, and the
two cameras agreed with each other rather than the floor. A store-median counter
is relative by construction. Rulers now carry `anchored`; person_ruler is None
on an unmeasured or bootstrap calibration instead of anchoring factor 1 to the
reading under test; and combine() returns an explicit unanchored verdict when no
ruler is anchored, in place of 'within 10% of the current metres'. scale_rulers
marks the consensus tile unanchored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…oning tool

Thirty tools and the two scripts the chain loads derived their root from their
own file and nothing else, so runs/commission01, runs/site30k_qa and
datasets/studioa_static could only ever be the checkout's own. Commissioning a
second site (FTI, 2026-09-10) meant copying src/ tools/ scripts/ into a
checkout-shaped directory. scene_mesh.py already read SYNCAI_ROOT; every tool
now takes the same override, shipped.py resolves its checkpoints through
syncai_hydranet.paths.repo_root so they move with the site, and a test keeps a
bare Path(__file__) root out of the chain. The site name inside the paths is
unchanged: that is the next commit's problem, not this one's.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
image_size_px is the frame the intrinsics were fitted at, half the plate's
source by convention, and every consumer that rescaled boxes into it assumed
the source was 1920x1080 -- a mismatch returned metres, not an error. An FTI
camera commissioned from a 1280x720 stream and another from 3840x2160 both did
(2026-09-11). static_plates now records each clip's probed size beside its
slot, onboard_camera carries it into the calib as source_size_px,
from_onboard_calib keeps it in camera.json (optional: a v3 file reads as
before), validate() refuses a source of another aspect, and demo_video says so
when the clip it is given is not the stream the camera was commissioned from.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
freeze() copied src/, scene_mesh.py, pyproject.toml and uv.lock by name while
capture_inputs also identified rebuild_geometry.py, so the snapshot's identity
never matched its source and stage0_baseline.py failed on every camera before
reaching a scene (FTI-SMT-cam912, 2026-09-16). The copy now walks the identity
record itself -- inputs and code alike -- so the two lists cannot drift apart,
and a test freezes a fixture site and asserts the snapshot identifies as it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pick_daytime_slot kept a shop's 08-18 local window as a constant. A factory on
night shifts is lit at 05:22 local, and that was FTI-SMT-cam912's only capture:
the gate refused it as night and onboarding stopped (2026-09-16). cameras.json
now carries daytime_hours_local [start, end] (08-18 when absent, so every
existing site means what it meant; start > end is a window across midnight),
static_plates records it in index.json and onboard_camera passes it through.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…clip

The chain for a second site lived in a shell script under runs/ of a copied
checkout (FTI batch_one.sh, 2026-09-10), with the bootstrap rule as inline
Python nobody could test and the demo render welded on. The tool runs the
README's steps in the README's order against SYNCAI_ROOT and ends in
stage0_baseline.py's freeze, so the chain and the review candidate share one
entry point. The bootstrap -- scale 1.0 on raw DA-V2 metres when no person box
exists -- needs --allow-bootstrap and is flagged in the calib; no floor plane
is a refusal. --dry-run prints the plan and touches nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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