Skip to content

test(safety): hold the fridge layout pin with a test, not a comment - #224

Merged
AdrianLlopart merged 1 commit into
masterfrom
test/102-fridge-pin-start-state-regression
Sep 5, 2026
Merged

test(safety): hold the fridge layout pin with a test, not a comment#224
AdrianLlopart merged 1 commit into
masterfrom
test/102-fridge-pin-start-state-regression

Conversation

@AdrianLlopart

Copy link
Copy Markdown
Contributor

What changed

scenes/deploy/robocasa_fridge_drawer.yaml has pinned layout_ids: [47] since 2026-08-25 on the strength of a table in its own comment block — layout 30 at −23.47 mm on panda_link2, layout 47 at +19.34 mm on panda_link1, from live octomap captures. Nothing executable held any of it. The pin could move, panda_link2's OBB could loosen, or the lowered kinematics could shift, and the only artifact that would disagree was a YAML comment.

This is issue #102's third acceptance item — "add a regression for the nominal valid pose and a nearby genuinely colliding fixture pose" — with the pair the start-state census identified.

  • tests/sim/safety/test_kernel_fridge_layout_pin_start_state.py — the regression, 4 tests, 66 s, no GPU
  • scenes/deploy/robocasa_fridge_drawer.yaml — the pin block now names the test, and names its limit
  • docs/reference/collision-validation-evidence.md — the ledger entry
  • docs/reference/robocasa-start-state-census.md — recommendation 4 marked done, with the outcome

How it works

Real kitchen, real manifest, real kernel binary — no mocks (CLAUDE.md §1.11):

  1. RoboCasa composes the scene at a pinned layout, at the scene's own seed, read at reset with zero actions applied.
  2. A 25 mm base-frame occupancy grid is built over the arm cell by cell through the shipped public openral_hal.sim_sensor_bridge.voxel_backing_record — the same probe the E-stop evidence path already uses to ask what backs a cell. A cell is occupied on a solid_world backing.
  3. The real safety_kernel_node, loaded from robots/panda_mobile/robot.yaml, is seeded with that configuration and grid and asked to pass a zero CARTESIAN_DELTA chunk.

No collision arithmetic is ported into the test. The clearance is read by sweeping the kernel's own world_voxel_margin_m, which walks a known distance in from the surface.

What it measures

At world_voxel_margin_m = 0:

layout this grid live octomap (scene file)
47 +44.55 mm panda_link1 +19.34 mm panda_link1
30 +19.09 mm panda_link2 −23.47 mm panda_link2

Same dominant link on both layouts, same ordering, uniformly more generous — the direction world-map-fidelity.md predicts, because the live map also holds non-collidable decoration and the octree→grid bridge dilates whatever it holds.

The limit, stated up front

Layout 30 clears at zero margin here and stops on the live map. The test's docstring, the scene comment and the ledger all say so. It is not evidence for restoring layout 30, and it is not a substitute for a live round.

What is pinned is the part that does not depend on map density: 47 clears, 30 is the tighter of the two at a 20 mm standoff, and each binds on the link the census named. Reproducing the live verdict needs the whole deploy graph — #102's separate end-to-end acceptance item.

Two properties recorded on the way past

  • Counting non-collidable world geometry as occupancy raises the cell count 4 702 → 7 923 (L30) and 5 638 → 9 217 (L47) — bracketing the live captures' 5 435 and 9 112 — and moves the reported minimum by 0.00 mm on both. The binding cells are solid either way.
  • The whole pipeline is deterministic: a cold rebuild reproduces both cell counts and both minima to every digit.

How tested

4 passed in 66.39s on q-laptop (RTX 5070 Laptop), sourced + colcon-built workspace, RoboCasa kitchen backend provisioned.

The all-occupied control is half of it, for the reason #183 established on the Nav2 live tests: a grid that never reached the kernel would pass every configuration and every clearance assertion would hold vacuously. Mutation-checked:

mutation result
separating margin → 0 ordering test fails
grid origin displaced 2 m ordering test and control fail
wrong header.frame_id passes — correct, not a gap: WorldCollision.msg states the kernel applies no TF on the hot path, so the frame id is advisory

just lint clean repo-wide (ruff check + ruff format --check); mypy --strict does not cover tests/.

Notes

No public symbol added, so no docs/methods/ change is owed. No package, schema or layer boundary touched, so the repo state map is unchanged and no ADR is required.

Closes nothing on its own — #102 remains open on its end-to-end gate.

🤖 Generated with Claude Code

https://claude.ai/code/session_014XR5jno1Qm3fiSdtdvYgML

`scenes/deploy/robocasa_fridge_drawer.yaml` has pinned `layout_ids: [47]`
since 2026-08-25 on the strength of a table in its own comment block --
layout 30 at -23.47 mm on `panda_link2`, layout 47 at +19.34 mm on
`panda_link1`, from live octomap captures. Nothing executable held any of
it: the pin could move, `panda_link2`'s OBB could loosen, or the lowered
kinematics could shift, and the only artifact that would disagree was a
YAML comment. Issue #102's third acceptance item, with the pair the
start-state census identified.

Real kitchen, real manifest, real kernel binary (CLAUDE.md 1.11). Each
layout is composed at the scene's own seed and read at reset with zero
actions applied; a 25 mm base-frame occupancy grid is built over the arm
cell by cell through the shipped `voxel_backing_record` -- the probe the
E-stop evidence path already uses to ask what backs a cell -- and put
through `safety_kernel_node` as a zero CARTESIAN_DELTA chunk. 4 tests,
66 s, no GPU.

Measured at `world_voxel_margin_m = 0`, bracketed by sweeping the kernel's
own standoff rather than porting any arithmetic into the test:

  layout   this grid                  live octomap (scene file)
  ------   ------------------------   -------------------------
      47   +44.55 mm  panda_link1     +19.34 mm  panda_link1
      30   +19.09 mm  panda_link2     -23.47 mm  panda_link2

Same dominant link, same ordering, uniformly more generous -- the
direction world-map-fidelity.md predicts, because the live map also holds
decoration and the octree->grid bridge dilates it.

SO LAYOUT 30 CLEARS AT ZERO MARGIN HERE AND STOPS ON THE LIVE MAP, and
the test's docstring, the scene comment and the ledger all say so. It is
not evidence for restoring layout 30. What is pinned is the part that does
not depend on map density: 47 clears, 30 is the tighter of the two at a
20 mm standoff, and each binds on the link the census named. Reproducing
the live verdict needs the whole deploy graph -- #102's separate
end-to-end item.

Two properties recorded on the way past: counting non-collidable world
geometry as occupancy raises the cell count 4702 -> 7923 (L30) and
5638 -> 9217 (L47), bracketing the live captures' 5435 and 9112, and moves
the reported minimum by 0.00 mm on both -- the binding cells are solid
either way. A cold rebuild reproduces both counts and both minima to every
digit.

The all-occupied control is half of it, for the reason #183 established on
the Nav2 live tests: a grid that never reached the kernel would pass every
configuration vacuously. Mutation-checked -- neutralising the separating
margin fails the ordering test, and displacing the grid origin by 2 m
fails both it and the control. A wrong `header.frame_id` changes nothing,
which is correct rather than a gap: WorldCollision.msg states the kernel
applies no TF on the hot path.

Also marks the census's recommendation 4 done, and records that the guess
it ended on ("the live octomap may be sparser at reset") was falsified in
the direction that matters -- the live map refutes two of the four layouts
this census called clear.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014XR5jno1Qm3fiSdtdvYgML
Signed-off-by: Adrian <adrian@qualiastudios.dev>
@AdrianLlopart
AdrianLlopart force-pushed the test/102-fridge-pin-start-state-regression branch from 1e0f8f8 to c7c7fc1 Compare September 5, 2026 14:27
@AdrianLlopart
AdrianLlopart merged commit c92e676 into master Sep 5, 2026
3 checks passed
@AdrianLlopart
AdrianLlopart deleted the test/102-fridge-pin-start-state-regression branch September 5, 2026 15:16
AdrianLlopart added a commit that referenced this pull request Sep 5, 2026
#224 landed the clear half of #102's third acceptance item and stated its
own limit: on a grid built from true surfaces the retired layout *clears*
at zero margin, so its refusal has to be bought by raising
`world_voxel_margin_m` to 20 mm, and the only zero-margin refusal in the
file is the all-occupied control — an artificial grid. Nothing held that
the kernel refuses a real kitchen at its shipped standoff for a real
reason. This is that half.

One more state in the same file, same pinned layout 47: the arm at
`panda_joint1 = -76 deg, panda_joint2 = +4 deg`, then the same 25 mm grid,
the same real `safety_kernel_node`, the same zero `CARTESIAN_DELTA` chunk,
at `world_voxel_margin_m = 0`.

The depth is certified before the kernel is asked, by the shipped
`estop_ground_truth_snapshot` scoped to the kernel-checked links:
`panda_link5` is -145.13 mm inside `fridgesidebyside_main_group_1_fridge_door`,
mesh-to-mesh through `openral_hal.convex_distance` — not `mj_geomDistance`
and not MuJoCo's contact list, which is not a penetration oracle on these
pairs. Corner slop (66.98 mm) plus the cell half-diagonal (21.65 mm) cannot
manufacture mesh interpenetration at any magnitude, so a pass here cannot
be envelope conservatism.

The pose was found by sweeping `panda_joint1` x `panda_joint2` over their
full ranges at 15 deg, one certified snapshot per configuration, and is
pinned rather than re-searched: the search was worth running once, and
re-running it costs ~460 probes to rediscover two constants. `panda_joint1`
alone reaches nothing over its whole 332 deg range from this start pose.

Recorded on the way past: the nearest link-to-link pair reads -34.3 mm at
every configuration including the reset pose, because adjacent links'
collision meshes overlap at the joint and the kernel exempts them by
adjacency. Filtering candidates on it rejects everything; the verdict
asserts `collision_kind == "world"` instead.

How tested: `5 passed in 83.17s` on q-laptop, sourced + colcon-built
workspace, RoboCasa provisioned. Mutation-checked — displacing the grid
origin by 2 m fails the E-stop assertion, so the refusal is this grid's.
`ruff check tests/` and `ruff format --check` clean.

Does not reach #102's end-to-end gate, which needs the whole deploy graph
under a policy and is blocked behind #217.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
AdrianLlopart added a commit that referenced this pull request Sep 7, 2026
The reconstructed layout-47 grid goes 5 638 -> 7 427 occupied cells once the
probe stops blaming decoration for cells whose solid geometry is behind it --
landing between #224's two brackets (5 638 solid-only, 9 217 counting all
decoration) exactly as it should. Every clearance number derived from that
grid was computed against a map ~32% too sparse.

Also records the gap the investigation surfaced: the backing record was
present on only 8 of 91 stops, so the diagnostic that says what the map
contains is absent from 91% of the stops it exists to explain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
AdrianLlopart added a commit that referenced this pull request Sep 7, 2026
…ramme measures with (#238)

* feat(sim): measure the policy's ceiling without the world-voxel gate

After a month of collision work, completion went from 25% (2026-08-26) to
5-10% (2026-09-06) and nobody had ever measured what the policy achieves
WITHOUT the gate. The validation harness refuses to -- `no_enable_octomap_
kernel_check` is on `_SAFETY_KNOB_PATTERNS`, correctly for a validation round,
and that is exactly why the number was never taken. The survey quoted the
external version of this experiment (PACS, arXiv:2511.06385 Table I:
unfiltered 0.70 vs binary-filtered 0.04) and never asked for the in-tree one.

Measured on spark, 88 valid runs, 4 scenes x 2 arms, 10-12 per cell, same
commit and host, both arms running simultaneously so contention loads onto
each equally:

    world-voxel gate OFF   14/45   31.1%
    world-voxel gate ON     1/43    2.3%     Fisher p = 3.5e-04, power 0.97

Per scene: utensil 58% vs 0% (p=0.005), fridge 45% vs 0% (p=0.035),
sink_cup 18% vs 9%, baguette 0% vs 0%. So two scenes are almost entirely
kernel-bound, sink_cup is mixed, and **baguette is policy-bound and cannot
report on collision work at all** -- it should leave the scorecard.

This is a CEILING, not a configuration: it never lands in a scene file, a
launch default or a manifest, the harness's refusal is untouched, and 6 of 91
stops in the #204 battery were real contact. The number says how much headroom
the levers are competing for: up to 29 points, concentrated in the payload
class.

`tools/_ceiling_probe.py` deliberately does not go through the validation
harness; it reuses that harness's own `materialise_scene`, readiness gate and
dispatch tool so the only difference between arms is the gate flag, verified
in the launch argv as `enable_octomap_kernel_check:=false`.

THREE DEFECTS had to be fixed before the number was trustworthy, each of which
would have produced a confidently wrong answer, and all are recorded in
PLAN.md §7:

1. an uncaught `subprocess.TimeoutExpired` killed whole workers rather than
   single rounds, leaving the arms SCENE-CONFOUNDED (gate-off had run mostly
   fridge, which completes; gate-on mostly utensil, which then never did) --
   the interim 4/17 vs 1/20 was an artifact of scene composition;
2. `SidecarClient` reaps the sidecar IT spawned on exit, so the first crashed
   worker took the shared sidecar down and every later run was policy-free
   (30-85 s instead of 600+). Fixed structurally with a keeper process;
3. policy-free runs must be excluded by reading each run's own goal log --
   14 of 102 runs were dropped that way.

Also recorded: `openral deploy sim` cannot run concurrently with itself,
because `_kill_orphan_openral_graph_processes()` matches by argv signature and
cannot tell a concurrent sibling from a crashed orphan. Parallel workers need
`OPENRAL_SKIP_ORPHAN_REAP=1`, which is deliberately NOT committed as a
default.

PLAN.md carries the failure analysis this all came from: median true clearance
at the moment of a kernel stop is 20.1 mm, 85 of 91 stops were of a robot that
was physically clear, and 71% of stops are the carried payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 17fc1450d6a9c904f058342982d9ed47edac349b)

* docs(collision): land the ceiling result and reorder the levers by measurement

The ceiling entry is the durable record: `outputs/` is gitignored, so the 88
runs behind 31.1 % vs 2.3 % would otherwise vanish the way the 2026-08-26
battery's artifacts already have.

Also records two lever findings that change what to build next, both measured
rather than argued:

* **Voxel resolution is struck.** `OccupancyVoxels.occupancy` is a dense
  `uint8[]` and the per-link window is `O(1/res^3)`, so halving the cell is an
  8x check cost. 15 mm is 26.7 ms estimated against a 33 ms budget and a
  2.80 MB message (4.6x the 614,125 cap); 12.5 mm is 46 ms. 20 mm fits but buys
  4.4 mm of a 20.1 mm excess. Poor return.
* **Modeled fixtures is the lever instead.** 51 of 70 payload stops are against
  anonymous `voxel_` cells whose certified nearest body is a static kitchen
  fixture MuJoCo already knows exactly -- `counter_1_right` 25 times,
  `fridgesidebyside_main` 9, `counter_1_left` 8. The robot carries an object
  over a counter and the counter's cubes stop it at 20 mm of air. That is the
  survey's own §9 point 1, and #200 already built the machinery for the
  declared place target; this generalises it to the fixture the payload is near.

And one scene finding: **`baguette` should leave the collision scorecard.** It
is 0 % with the gate off, so it is policy-bound and cannot report on collision
work either way -- despite four of the five completions in this ledger's whole
history being baguette runs, which is what made it look like the bellwether.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 0da3e4d8f44d48f5ee43a5af375d9444afbf208c)

* fix(sim): make the ceiling probe pass mypy --strict

`quality` runs `mypy --strict tools/`, which the new probe failed two ways:
`validation_matrix` is a sibling script imported by path (no stub), and the
two `type: ignore`s written for a typed SceneSpec were unused once that import
resolved to Any.

Types the parameter as `Any` with the reason inline rather than scattering
ignores, per CLAUDE.md §2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 36a9d2be03d097e828b986b5c474410c82f154e0)

* test(safety): give the hull narrow phase a latency surface

`tests/sim/safety/test_kernel_latency_soak.py` is the kernel's only latency
test and it publishes **no** `OccupancyVoxels` at all — it runs a synthetic
`soak_test` envelope with no `collision_geometry`, so the staged 26-DOP -> hull
narrow phase never executes. Its pass is vacuous for any change to that phase,
which is the kernel's dominant cost: the shipped benchmark puts the seven link
windows at 10 475 cells and ~5.8 ms against ~8.8 us on an empty grid. Same
class of hole #183 found in the Nav2 live tests, and it is what made the
`link3`/`link4`/`link6` change in this branch unmeasurable.

The new test goes in the fridge pin file because that is the only place in the
tree with a REAL grid: a real RoboCasa kitchen rasterised cell by cell, the
real manifest (so all seven links lower their tight geometry), the real kernel
binary, at `world_voxel_margin_m = 0.0` — the value `panda_mobile` runs.

Measured on q-laptop, 200 chunks over 5 638 occupied cells:

    median 0.1 ms      p99 2.0 ms      target 30 ms, hard ceiling 33 ms

So the narrow phase with seven hulls sits 15x under the chunk budget, which
answers the latency question this branch's manifest change raises on the
shipped configuration rather than by extrapolating the one-off benchmark table.

It also asserts >=90% of chunks come back: a kernel dropping under a real grid
would be a worse finding than a slow p99, and a p99 over a truncated sample
would hide it.

Mutation-checked by forcing the budget to 0.001 ms, which is how the 2.0 / 0.1
numbers above were read out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit e0e2e0f74e6c80da32f57ed33a3e9ebfda4bb80e)

* docs(plan): record ADR-0101 and hazard-log Entry 026

Both live in OpenRAL/management#33. Entry 026 is the record #235 owes;
ADR-0101 is the remaining lever, proposed before code because it crosses
Layer 2 -> Layer 6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 6406ac1e606fa870d337743c1bc9effa99eb42a3)

* fix(hal): the backing probe stopped at decoration and blamed it for the stop

`voxel_backing_record` is the instrument that answers "what, if anything, is
really in the cell the kernel stopped on". `mj_ray` reports only the NEAREST
strike, and the probe took it — so a non-collidable shell in front of the
collidable surface it wraps was the only thing it ever saw, and the cell was
adjudicated `noncollidable_world`, i.e. "the map disagrees with the world".

Measured, not theorised. On the 2026-09-06 battery, **6 of the 8 stops that
carried a backing record at all** came back `noncollidable_world` naming
`counter_1_right_group_top_visual`, while the certified nearest COLLISION
surface at those same stops was ~16 mm away -- inside the same 25 mm cell. The
map was right and the diagnostic was wrong.

That matters beyond one number. Since #180 the depth cast makes exactly these
geoms transparent, so in sim decoration can no longer become occupancy at all;
a `noncollidable_world` verdict is now a statement about the probe or a stale
cell, not a live map defect. The class docstring still carried the pre-#180
justification ("the depth synth strikes these too, so they CAN become
occupancy"), which is what made the misattribution look plausible. Both the
docstring and the METHODS entry are corrected.

A ray that strikes a non-collidable geom inside the cube is now re-cast from
just past it, up to `_VOXEL_BACKING_MAX_LAYERS` (4) times, and BOTH the shell
and whatever it hides are recorded. The existing precedence then does the rest:
`solid_world` outranks `noncollidable_world`, so the cell reads as explained by
real geometry, while a cell with genuinely nothing solid behind the decoration
still reads `noncollidable_world`. Nothing is filtered away -- dropping the
shell would hide a real map defect where one exists.

Diagnostics only (CLAUDE.md §1.4): no stop is suppressed, delayed or altered.

How tested: `tests/unit/test_sim_estop_voxel_backing.py` gains a RoboCasa-shaped
fixture -- a collidable slab wearing a non-collidable shell, both inside ONE
cell, shell nearer the ray start -- and a test that the cell reads `solid_world`
with both geoms named. Mutation-checked: reverting to first-strike-only fails it
with `noncollidable_world`. 15 pass in that file, 28 across the E-stop evidence
suite; `mypy --strict -p openral_hal` clean; methods markers refreshed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit c7bd2c729f792b2b7f829c4946e5750c981337fa)

* docs(plan): record the ADR-0101 recovery measurement and the probe fix

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 2997c01efad8cbdd9147459403b9b6f1307325ab)

* docs(collision): the backing probe defect, and the 32% it moves

The reconstructed layout-47 grid goes 5 638 -> 7 427 occupied cells once the
probe stops blaming decoration for cells whose solid geometry is behind it --
landing between #224's two brackets (5 638 solid-only, 9 217 counting all
decoration) exactly as it should. Every clearance number derived from that
grid was computed against a map ~32% too sparse.

Also records the gap the investigation surfaced: the backing record was
present on only 8 of 91 stops, so the diagnostic that says what the map
contains is absent from 91% of the stops it exists to explain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit f42d3d3f5e61a11a18685c8e58e7de591070a782)

* docs(collision): correct an '8 of 91' claim before it hardened into evidence

An earlier draft of the 2026-09-07 probe entry said the backing record was
present on only 8 of 91 stops. Wrong: it is present on 82. The 74 extra live
in run_gt_evidence.json, which is #177's LATE path.

But that path is unusable on this battery, and its distribution is a trap --
46 of 74 read 'unbacked', which looks like the kernel stopping on cells nothing
backs. It is instead exactly the defect 10ff989 describes: the late path
omitted grid_orientation_xyzw, took identity, and decoded a cube metres from
the stopping link, reporting unbacked with 27 rays cast and 0 hits. 10ff989
landed 2026-09-05 and NONE of the battery's commits (all 2026-09-04) carry it.

Kept as a visible correction rather than a silent edit, because the wrong
version was a more exciting finding than the right one -- which is the specific
way this ledger has been burned before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 51c8e2e19ab6952cb9ff10a6574fde9291f6e970)

* fix(sim): the validation harness could not see the graph it launched

Every scene of every `tools/validation_matrix.py` round on post-#231 `master`
reported `harness-error` — "action server never appeared" — beside a graph that
was up and healthy the whole time. Two independent defects, each sufficient on
its own, both measured on `q-laptop` against a live `robocasa_drawer_utensil`
round.

1. `_launch_env` did not apply the sim DDS scope. Since #227/#231 `openral
   deploy sim` confines itself with `confine_sim_scope` (`ROS_DOMAIN_ID=77`,
   `ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`) so a simulation and a real robot
   cannot share a graph. The deploy applied that to itself; the harness did not,
   and polled domain 0. `ROS_DOMAIN_ID=77 ros2 action list` showed
   `/openral/execute_rskill` the whole time; unscoped showed nothing.

   `confine_sim_scope` now runs inside `_launch_env` — applied on this side
   rather than left to the child, because it uses `setdefault`, so the deploy
   inherits the harness's value instead of choosing its own and the two agree by
   construction. An operator who exports their own scope still wins on both.

2. `ros2 action list --no-daemon` cannot discover an advertised action. The poll
   passed it for a real hazard: a daemon left over from an unscoped shell
   answers from the environment *it* started with — the false reading that made
   `ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST` look broken in #227. But the
   one-shot node it builds has a discovery window too short to see an action
   that is genuinely up: against a live graph, `ros2 action list` found it and
   `--no-daemon` did not, on the same domain, repeatably. The poll could never
   succeed on any scope.

   Replaced with a one-shot `ros2 daemon stop` under the round's own `env`
   before the loop, which closes the original hazard from the other side: the
   daemon the loop then uses is started by that call, on that scope.

The same round that had reported `harness-error` twice completed with a real
outcome (`utensil`, `deadline-no-grasp`) on the first attempt after both fixes.

`docs/reference/collision-validation-evidence.md` gains the 2026-09-07 entry,
including what it invalidates: any round taken on post-#231 master before today
measures the harness, not the kernel. Rounds on earlier commits — the ceiling
battery's `80027b18` arm among them — predate #231 and are unaffected; checked,
not assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 773560899f80d0a7797a6c4897ab210c408d6fba)

* docs(plan): reconcile the checklist with the measured levers, and record both launch failures

The §7 checklist had drifted from §5's 2026-09-07 reordering and still listed
two levers as open that §5 had already struck by measurement:

- "Lever 1: the payload bounding box" kept the pre-reordering numbering. The
  payload is still 71 % of stops, but its primitives are already tight to
  −1.5 mm beyond the voxel term, so there is no payload-geometry headroom. The
  class is the right target; the mechanism that reaches it is ADR-0101's modeled
  fixtures, not a tighter payload box. Tracked there.
- "Lever 3: voxel resolution 25 → 15 mm" is struck on measured cost: a dense
  `uint8[]` occupancy and an `O(1/res³)` window make halving the cell an 8×
  check cost — 26.7 ms at 15 mm against a 33 ms ceiling.

Both now marked struck with the number that struck them, so the checklist and
§5 say the same thing.

Adds the two launch failures found today, which are distinct and were being
conflated:

- the **harness** could not see the graph it launched, and had not since #231 —
  wrong DDS scope plus a `--no-daemon` poll that cannot discover an advertised
  action at all. This is why post-#231 rounds looked like launch failures, and
  it is a precondition for every open measurement below it.
- the **launch parser** ran under the system interpreter, so `dist-packages`
  shadowed the venv and `import pandas` aborted the whole launch on a Jetson
  AGX Thor. That is the spark-side failure, fixed on its own branch.

Closes the `baguette` scorecard item: 0/11 with the gate off means policy-bound,
so it leaves the completion scorecard while staying in the matrix. Already
recorded in the ceiling entry; the plan now points at it rather than restating
it.

`collision-validation-evidence.md` gains standing caveat 10 — the citation rule
for post-#231 rounds — because that is what the caveats list is for.

Re-derivation of ADR-0101's 94 % against the post-fix live map is unblocked: the
foreign 1.9 GB GPU process is gone (175 MiB of 8151 in use).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit e7956f77120dd02bcb9899f7f0c140e7d8151a16)

* feat(evidence): give ADR-0101's 94 % a producer, and make its denominator honest

ADR-0101 cites "48 of 51 payload-vs-`voxel_` stops (94 %) recovered" as the
measurement that justifies building the first **fail-open** mechanism in the
hazard log. That number was computed offline, by hand, and had no producer in
the repo — which is exactly the shape of claim
`docs/reference/collision-validation-evidence.md` exists to prevent.

`tools/adr0101_recovery.py` is that producer. The counterfactual it evaluates
needs no kernel code and no layer crossing: for a payload stop against an
anonymous cell, "would a modeled fixture have let this through?" is the same
question as "was the payload certifiably clear of the real surface?", and the
battery already records both halves. Pure, offline, stdlib-only, like
`tools/round_power.py`.

Two decisions carry the safety direction, and both are tested:

- **Zero is contact, not clearance.** A payload touching a fixture is stopped by
  the modeled body exactly as it was by the cube. Putting `0.0` on the clearance
  side would count real contacts as recoveries — the one class the mechanism
  must never suppress. Mutation-checked: flipping `>` to `>=` fails the test.
- **Exclusions are reported, never dropped.** A recovery rate is only as honest
  as the set it divides by, and the two errors are not symmetric: silently
  dropping an unadjudicable stop shrinks the denominator and *inflates* the
  rate. `recovery_rate` is `None` over an empty set and `render` refuses to
  print a percentage rather than showing 0 % or 100 %.

Tested against the real `2026-08-23-master-s1` round, which recorded exactly the
stop shape the tool selects but predates the probe's distance attestation
(standing caveat 8) — so it must land in `excluded`, by name and with a reason.
It does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 60dcb2ff992cb7e4c06e5e362d8a000cd82a43e8)

* fix(evidence): attribute a payload stop to the fixture, not to a robot link

The first cut of `tools/adr0101_recovery.py` read the by-fixture breakdown off
`ground_truth.nearest_pair`. That field records the closest probed pair *of any
kind*, and for a carried payload it is routinely two robot links — on the round
this was caught with, `robot0_link3` vs `robot0_link4` at −36.3 mm, while the
payload itself sat 24.9 mm clear of a counter. So the tool put `robot0_link4`
into a table of kitchen fixtures: a robot link presented as a static world body,
inside the record that argues for modelling static world bodies.

The body behind `nearest_tripping_party_m` is recorded in exactly one place —
the raw `sim.estop_ground_truth_snapshot` line's `nearest_payload_world_pairs` —
so `fixture_at_stop` reads it there and verifies the match rather than assuming
it: that list's minimum certified distance must equal the gap the stop was
adjudicated on, both being the same probe call. If they disagree the snapshot
describes some other stop and no attribution is made. The recovery *count* never
depended on this and does not now; only the breakdown did.

Verified on the live round: the fixture is `counter_1_right_group_main`, which
is `counter_1_right` — the body ADR-0101 §3 already names as the top fixture at
25 of 70 payload stops. The premise reproduces independently.

Adds the round as a fixture (one snapshot line plus `verdicts.json`, provenance
in `SOURCE.txt`) and three tests, the first of which is a regression test for
this defect. Mutation-checked: pointing the reader at `nearest_link_link_pairs`
fails it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit a0f5f64bdd9d8d2cdfda75d644e6a3efec2bef45)

* docs(collision): the first post-fix round, and a cell backed only by decoration

`2026-09-07-adr0101-live-1` is the first validation-matrix round since #231 to
reach a real outcome rather than `harness-error`. It confirms ADR-0101's
certified premise directly: the payload was stopped at −4.05 mm reported while
sitting +24.86 mm clear of `counter_1_right_group_main` — the exact fixture the
ADR names as its largest class.

It also turned up something the ADR does not account for. The backing probe —
with the decoration-walking fix verified live in the running process — reports
the tripping cell as `noncollidable_world`, backed solely by
`counter_1_right_group_top_visual`. Within that 25 mm cube there is no
collidable geometry at all; the collision slab is the 24.9 mm away that the
certified probe independently measured.

That is the occupancy grid faithfully recording what a depth sensor sees, which
is the visual shell, not the collision body. It cuts both ways for ADR-0101's
suppression bound: if a modeled fixture publishes collision primitives, a cell
like this is *not* geometrically explained by them and the stop survives — so
the 94 % would be optimistic; if it publishes the visual geometry instead, the
mechanism suppresses against a surface the kernel does not protect. Benign in
sim, where a non-collidable geom cannot be hit; not benign on hardware, where
what the sensor sees is what the robot hits. That is the sim→real seam the ADR
already flags, now with an instance instead of a caveat.

Recorded at n=1 and labelled as such. A 12-round batch is running to measure how
often a payload stop is backed by decoration alone; nothing here revises the
94 % in either direction yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 6bed91758410ac6b3aa9ab12a6e8251313555a42)

* docs(collision): withdraw the 'map is proud of the collision model' reading

The 2026-09-07 entry interpreted a cell backed only by a non-collidable geom as
the occupancy grid mapping the visual surface while the collision body sat
behind it. RoboCasa's asset code does not support that reading.

`robocasa/models/fixtures/counter.py` emits one full-span `<name>_top_visual`
box (`group=1`, `contype=0`) and then chunks the *same* volume into collidable
geoms via `_get_chunks`, which tile it exactly — identical `pos[1]`, `pos[2]`,
identical `size[1]`, `size[2]`, `x` tiling the full span. Visual and collision
are coincident by construction, so there is no offset to be proud by.

What remains is a real three-way tension: the backing probe finds no collidable
geom in the cell after re-casting past decoration; the certified probe puts the
nearest collidable geom of the same body 24.86 mm away; the asset code says they
are coincident. All three cannot hold.

The leading candidate is a residual defect in the re-cast itself: it advances
"just past" a strike, which steps over a *coincident* collidable twin and lands
outside the cube. The fix was built for decoration in front of a slab, not
decoration sharing its surface.

This is left open and labelled, not resolved, because it changes what the
programme is optimising: if the instrument is wrong, the 20.1 mm payload excess
ADR-0101 is sized against is itself suspect. Resolving it needs a direct query
of the live model at the stop, which no current artifact records.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit b76aa0e41239b405364f976eebe1bbd4b2a64003)

* fix(evidence): a permitted link overlap was stamping every stop 'real-contact'

#220 (on master since 2026-09-05) gave the HAL a link-vs-link probe so a self
stop could be scored against the pair the kernel named. That part is right, and
it is what makes standing caveat 9 closeable. But the new pairs were folded into
the adjudicator's `nearest_any`, which drives its first and most decisive rule:
any probed pair at or below 0 m is `real-contact`.

Adjacent robot links overlap permanently. They are in the robot's
allowed-collision matrix and the kernel never checks them. So from #220 onward
`nearest_any <= 0` was vacuously true and every adjudicable stop was stamped
`real-contact`, whatever the tripping party's actual clearance.

Measured on `2026-09-07-adr0101-live-1`: `robot0_link3`/`link4` at -36.3 mm,
`link5`/`link6` at -23.0 mm, `link4`/`link5` at -4.6 mm — all certified, all
permitted, none of them what the kernel stopped for — while the carried payload
it did stop for sat +24.86 mm clear of the counter.

`nearest_any` now excludes `nearest_link_link_pairs` wholesale and adds back
only the pair the kernel named, which `party_pairs` already isolates in the
`self_pair` branch. A genuine link-vs-link self stop in real overlap is still
detected as contact; a permitted overlap two joints away is not.

Re-derived over the four stops recorded today, three move
`real-contact -> within-quantization` and the one true contact (-2.32 mm) is
preserved. Verdicts are pure and offline, so affected rounds re-adjudicate
without re-running.

Two tests, both mutation-checked against restoring `+ link_link`. Standing
caveat 11 records the citation rule: no `real-contact` verdict from 2026-09-05
to 2026-09-07 is safe to cite. The error runs one way — it manufactures real
contacts, never clears one — so nothing was wrongly passed as safe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 483a82e1cdb2d9676b5a7e1ee322e343b25db0d7)

* docs(collision): the adr0101-live battery — 71 % of stops are of a clear robot

Thirteen rounds on q-laptop (utensil and fridge, seeds 1-7), run after both
harness fixes and re-adjudicated offline after the `nearest_any` fix. First
battery on this page where harness and adjudicator were both known-good at
reading time; three rounds changed verdict when re-derived.

Seven stops. Five (71 %) were of a physically clear robot, at +0.67, +11.13,
+22.01, +23.13 and +24.86 mm true clearance. Two were real contact, at -2.32
and -0.11 mm. The 71 % reproduces the #204 battery's 85-of-91 on an independent
battery, a different commit and a repaired instrument — the number has not
moved.

Four stops are the carried payload, splitting evenly clear/contact;
`adr0101_recovery` reports 2 of 4 recovered, median 17.99 mm, minimum 11.13 mm.
Far below the offline 94 %, but n=4 does not contradict it and no revision is
claimed.

Two findings the levers do not cover:

- Three of seven stops are `estop-initial-configuration` — the arm stopped at
  reset by its own start pose, before doing anything. tight_geometry, modeled
  fixtures and voxel resolution all address the carry phase; none addresses a
  base placement that starts the arm inside a counter. One is at +0.67 mm and
  would survive any geometry work.
- Five of thirteen rounds never grasped at all. With the ceiling result, roughly
  half of what reads as collision-programme failure on these scenes is the
  policy not reaching the phase where the kernel matters.

One round (fridge seed 6) completed with the gate on, against a 2.3 % gate-on
rate in the ceiling battery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 9676c931477608c46236e28adaf5d6346f7ecd54)

* docs(plan): record the repaired-instrument rate, and two classes no lever covers

The false-positive rate re-measured on a working harness and a working
adjudicator is 71 % (5 of 7 stops of a physically clear robot), reproducing the
#204 battery on an independent battery and a different commit. The headline
number has not moved.

Also records the #220 adjudicator inversion that made that measurement possible
to get wrong for two days, and adds two open items the §5 levers do not touch:

- start-state collisions, a third of all stops, where the arm is stopped at
  reset by its own pose before doing anything;
- rounds that never grasp at all, five of thirteen, which bound how much of the
  scorecard any collision work can move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit c1e2203a2e3c07383bc7a3db9f1691fa28ed8ab9)

* feat(evidence): give the stop decomposition a producer, and measure the link fix

PLAN.md §5's table is the measurement that struck two collision levers and
promoted a third: per stop class, the kernel's over-approximation with the grid's
21.65 mm half-diagonal subtracted. Like ADR-0101's 94 %, it was computed by hand
and had no producer. `tools/stop_excess.py` is that producer.

Two guards, both tested. Only stops whose probe certified its distances are
counted — an uncertified distance can be wrong by 15-108 mm, larger than the
quantity being measured. And the half-diagonal comes from each round's own
recorded `grid_resolution_m`; a stop without one is skipped rather than
defaulted to 25 mm, since assuming it would not perturb the answer but replace
it. `has_geometry_headroom` is strict at zero: a class exactly at the voxel term
cannot recover a millimetre, so calling that headroom would license wasted work.

Run over the 13-round adr0101-live battery it gives the first live read on
whether the tight_geometry work did what it was designed to do:

  payload  n=4   median excess +13.15 mm   beyond voxel  -8.50 mm
  link     n=3   median excess +25.51 mm   beyond voxel  +3.86 mm

The #204 battery measured the link class at +33.1 mm beyond voxel, with
panda_link6 holding 18 of its 29 link stops. link6 now ships tight geometry and
the class reads +3.86 mm. n=3 and a different link mix, so this is consistent
with the fix working rather than proof of it — but it is the first live evidence
in that direction, and it is what PR #235 was missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 551b7c5932929d39a685038a5c8cb1d0d5918f08)

* fix(hal): the backing probe could not see a slab coincident with its shell

`c7bd2c7` taught the probe to walk past a non-collidable strike and cast again,
which finds a collidable slab BEHIND a decoration shell. It cannot find one
coincident with it, and RoboCasa builds every counter top that way:
`robocasa/models/fixtures/counter.py` emits one full-span `<name>_top_visual`
(`contype=0`) and then tiles the same volume with collidable chunks via
`_get_chunks`. Their surfaces are the same plane, so stepping `distance + eps`
past the shell lands inside the chunk, where the ray reports no further entry
surface and the chunk is never seen.

That is what produced the three-way contradiction recorded this morning. It is
now settled from the round's own certified witness points: the cell spans
z in [0.90005, 0.92505] and the collidable chunk `counter_1_right_group_top_0`
has its surface at z = 0.920. The solid geometry was inside the cell the whole
time; the certified probe and the asset code agreed and only the backing probe
was wrong.

`voxel_backing_record` now falls back to a world-AABB overlap sweep over
collidable geoms when, and only when, the rays found nothing solid. An AABB
overlap can claim a geom whose surface misses the cube, so it is deliberately a
supplement rather than a replacement — for a diagnostic whose failure mode is
calling real geometry "decoration" (#180), erring toward found is the right
direction, and it can never override a ray pass that already found something.

Reproduced with a coincident shell/chunk fixture that yields the same 9-of-27
ray signature as the live round; mutation-checked by forcing the sweep off.

Diagnostics only (CLAUDE.md §1.4) — the certified probe measures geom-to-geom
distance and never used rays, so the 71 % rate and the stop decomposition are
unaffected. What moves is the backing CLASS of cells previously read
`noncollidable_world`, which is what the "32 % too sparse" entry and ADR-0101's
"cells no real body explains" premise rest on. Both should be re-derived from a
post-fix round before being leaned on further.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 10a3469ca9885c93414e280543b2a09fe863d00f)

* docs(plan): the geometry levers are exhausted, and link1's hull is not worth it

Three closures from the 13-round battery and its decomposition.

The probe contradiction was the instrument again: the cell spans
z in [0.90005, 0.92505] and the collidable chunk's surface is at z = 0.920, so
the solid geometry was inside it all along. Fixed; diagnostics only.

panda_link1 is the only link without a stage-2 hull (1588 vertices against a 320
cap) and caused two of three start-state stops, which made it look like the next
obvious manifest edit. Measured, those stops are +3.86 and +8.68 mm beyond the
voxel term, so an exact hull recovers at most ~9 mm of a ~25 mm error while
raising the cap 5x is a hot-path change needing safety-WG review. Struck.

And the conclusion those two produce together: the link class now measures
+3.86 mm beyond voxel, down from +33.1 mm in the #204 battery whose link stops
were 18-of-29 panda_link6 — the link that now ships tight geometry. With the
payload class at -8.50 mm, both are at or below the grid term. Every remaining
millimetre of over-approximation is the 25 mm voxel grid, and refining that was
struck on measured cost. No tighter envelope anywhere can recover anything
further.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 1d116ae9cdef8d25beec9f13cd8c31fa3a5c2aed)

* fix(safety): batch the hull-overhang query so a large link cannot exhaust memory

`hull_overhang_m` fed every barycentric sample to
`trimesh.proximity.closest_point_naive` in one call. That query allocates a
`samples x mesh-faces x 3` array, and the docstring's own premise — "a few
thousand by a few hundred here" — holds only for the small panda links that
have ever reached it.

`panda_link1` is 6260 mesh vertices and ~12k triangles; a 320-vertex hull over
it is ~636 facets at 325 samples each, so the single call asks for 57.8 GiB and
raises `numpy._core._exceptions._ArrayMemoryError`. Latent until something
declared a hull for that link.

Batching at 512 samples per call bounds peak memory by `512 x faces x 3`
independently of link size, and the maximum over batches is the maximum over the
whole set, so the returned number is unchanged. `check` still reports
mesh-outside-DOP +0.000000000 mm on all seven links.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit e8e07315a5be846e2d27002a87c3aa258c1d7a2a)

* feat(tools): give the generator a third option when an exact hull is over budget

`derive_tight_geometry` had two outcomes for a link: ship the exact convex hull,
or -- if it exceeds `MAX_TIGHT_HULL_VERTICES` -- fall back to the 26-DOP alone.
`panda_link1` is the second case (1588 vertices), and its DOP's support gap
against the real mesh is a median 4.52 mm and up to 25.68 mm.

`refine_dop_to_budget` builds a third thing: the DOP intersected with the exact
hull's own face planes, worst-violation first, stopping before the vertex count
would exceed the budget. Every candidate plane is tangent to `conv(mesh)`, so
containment stays *definitional* rather than fitted, and the result is
`mesh ⊆ result ⊆ DOP ⊆ box` at every step. A subset-then-expand construction
cannot promise that: expansion pushes vertices out through the DOP slabs, and
link1's DOP has 0.083 mm of room inside its manifest box. The routine refuses
rather than emit an envelope that cuts its mesh.

On link1's real mesh it reaches a support gap of 0.18 mm median / 0.65 mm max
against the DOP's 4.52 / 25.68 mm, at 320 vertices.

**No manifest declares a refined envelope, and this commit changes none.** The
envelope was generated and put under a live battery on 2026-09-07; the stops it
was predicted to clear moved by 0.0003 mm. The prediction failed because it read
a support-census deficit against the shipped *box* and attributed all of it to
the hull, when the DOP had already collected it. That refutation is recorded in
the docstring, in `docs/methods/10-tools.md` and in the evidence page, so the
next reader does not re-derive the same wrong expectation from a routine that
looks like it was built for a reason nobody wrote down.

Which is also why the test is a direct one rather than a manifest assertion: an
unexercised generator path rots. It pins the three properties a safety-WG
reviewer would otherwise re-derive by hand -- mesh inside the result, result
inside the DOP, and strictly less enclosed volume than the DOP it refines --
against the real robosuite mesh, not a fixture.

Also carries the memory bound `_OVERHANG_BATCH` / `_OVERHANG_MAX_SAMPLES` needs
to document: the single-call `hull_overhang_m` form asks for 57.8 GiB on a
320-vertex envelope over a 12k-triangle mesh.

How tested: `pytest tests/unit/test_collision_tight_geometry.py` (11.5 s for the
new case, inside the 30 s unit budget).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(plan): correct the link1 strike — the reasoning was wrong, not just the call

The earlier entry struck link1's hull because it "recovers at most ~9 mm of a
~25 mm error". That is the wrong question: what matters is whether the recovery
flips the stop, and the start-state census's deficit table says 10 mm clears 14
of 14 link1 states. It also assumed the only options were the DOP or an
over-budget hull, and that a kernel cap change would be needed. Neither held —
the refined envelope fits the existing 320-vertex budget.

Records the measured result (0.18 / 0.65 mm against the DOP's 4.52 / 25.68 mm,
p99 0.5 ms on 9891 cells) and downgrades the start-state item from "no lever
touches it" to "one lever now reaches it, unconfirmed": two of its three stops
were link1, predicted to clear but not yet observed in a post-change battery.
The third is a genuine +0.67 mm near-contact that no geometry work reaches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 7fcc7168f57786846ff51a2df1e97b7cd1ebce5c)

* docs(collision): the link1-envelope confirmation battery is void, and says so

Eight rounds were run to confirm that panda_link1's refined envelope clears the
two panda_link1 start-state stops. All eight died at 16-19 s with the XR-1
sidecar OOMing at boot: a concurrent job on this shared host held 2.0-2.5 GB of
7.53 GiB while the sidecar needs ~3.5 GB alongside the scene.

Recorded rather than discarded because the failure mode is exactly the shape of
the result being looked for. Every round shows `stop: null`, and "seeds 2 and 4
no longer produce estop-initial-configuration" is precisely what the envelope
predicts — but the policy never loaded, so the arm never moved and no kernel
check ran. Reading it as confirmation would be reading a crash as a measurement.

The prediction stands unconfirmed. Confirming it needs an uncontended window
here or a host with headroom; spark is the latter, with the standing caveat that
XR-1 has never completed an end-to-end rollout on GB10, so one smoke round must
succeed before a battery is worth running there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit f4a670f55809e56de5f163d3bf33b75c010fce4e)

* docs(collision): the link1 refined envelope does not move the stops it was built for

Three rounds on spark, utensil seeds 2/3/4, on the commit carrying panda_link1's
refined envelope — also the first end-to-end XR-1 rollout completed on GB10.

The prediction is refuted. Same seed, same scene, same stop, one commit apart:
seed 2 reads -2.37794 mm under the 26-DOP and -2.37825 mm under the refined
envelope; seed 4 reads -8.31 and -8.31495. Tightening link1 from a 25.68 mm
worst-case support gap to 0.65 mm moved the reported depth by 0.0003 mm.

The reasoning error is identifiable. The start-state census's "10 mm clears 14
of 14 link1 states" is computed with box_box_distance against the manifest OBB.
The 26-DOP shipped after that census and already collected that recovery
(53.27 -> 25.69 mm). Treating the census's OBB-relative deficit as still-
available headroom double-counted a tightening that had already landed.

This is therefore the controlled test that the geometry levers are exhausted:
the same stop under two envelopes differing by 25 mm of worst-case looseness,
moving 0.0003 mm.

Notes the untested hypothesis for the residual: the octomap bridge's README
records that a published grid can report a surface up to one full tree
resolution (25 mm) nearer than it is, and stop_excess.py subtracts only the
21.65 mm half-diagonal — so map inflation reads as geometry excess and invites
exactly the hunt this entry closes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit b55f90af3f2ae0a7a6626b0773dfefbd885121fa)

* docs(plan): the link1 envelope was refuted, and the exhaustion claim got a test

Corrects the link1 item twice over. I struck the lever for the wrong reason
(fraction of error recovered rather than whether it flips the stop), then
un-struck it for another wrong one: the census's "10 mm clears 14/14 link1
states" is computed against the manifest OBB, and the 26-DOP shipped later had
already collected that recovery. Three rounds on spark measured the same stops
under both envelopes and they moved 0.0003 mm.

That failure upgrades the exhaustion conclusion from an inference over a
decomposition to a controlled test: the same stop under two envelopes differing
by 25 mm of worst-case support gap does not move. Whatever the residual is, it
is not the collision model.

Start-state goes back to "no lever reaches it" — it is a base-placement question,
and the census shows joints 3-7 cannot change a start-state verdict at all when
link1 or link2 dominates, which is 83.3% of stopping states.

Adds the hypothesis that displaces the geometry hunt: octomap marks the cell
containing the ray endpoint, so the grid can report a surface up to a full 25 mm
tree resolution nearer than it is, and stop_excess.py subtracts only the
21.65 mm half-diagonal. Untested, and the first thing worth measuring next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 7d90a810f98239251c4198a3909c44b35bb3d5bd)

* docs(collision): the map-side reading of all three start-state stops

estop-initial-configuration was three of seven stops in the adr0101-live
battery and the class no lever in PLAN.md §5 addresses. This is the first
map-side reading of it.

Where the evidence lives matters and is counter-intuitive: a start-state
snapshot carries collision_evidence: null and evidence_voxel_backing: null,
because the E-stop fires before the kernel's safety.collision line reaches the
bridge and the freshness gate correctly refuses to attribute a cell. The record
arrives on the deferred sim.estop_ground_truth_evidence path instead
(run_gt_evidence.json, backing_after_snapshot_ns: 0). Reading only the snapshot
shows nothing and invites the conclusion that the class is un-diagnosable.

Three stops, three causes:

- utensil-s2 is textbook quantisation. The cell CONTAINS the true nearest
  surface point of the door backing it (0.00 mm), so the map is where the world
  is; +23.13 mm clear, reported -2.38 mm, excess 25.51 = 21.65 half-diagonal +
  3.86 mm. This also refutes the octomap-inflation hypothesis raised earlier the
  same day: an inflated cell would not contain the surface.
- fridge-s2 has the robot in the cell and no world geometry at all — 15 of 27
  rays struck robot0_link2_collision, the same link the kernel stopped.
- utensil-s4 shows the same signature on weaker evidence: its robot geoms come
  from the conservative AABB sweep, not rays (0/27). The same stop read
  `unbacked` on q-laptop before that fix landed, so the fix turned a blank into
  a diagnosis.

Records that the mechanism is NOT a missing exclusion — the self-filter passes
robot bodies to the synth, matches by prefix plus descendants, and marks
transparent rays so they clear rather than mark — and names the decisive test
that no artifact supports today: re-run the layout with the arm parked
elsewhere and see whether the cell persists. That needs a start-pose override
the harness lacks.

n=3 and self_occupancy_suspect is explicitly not conclusive alone, so this is
logged as a lead, not a finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit d63d6ad608321441655af3dc4bfd6ad535436427)

* fix(hal): sweep for world geometry when a cell's only solid backing is the robot

`self_occupancy_suspect` was resting on 27 rays having missed nothing. A cell
whose only collidable ray hit is a robot body produces an identical record
whether world geometry is absent or merely unsampled, and that distinction is
exactly what separates a self-occupancy stop from ordinary quantisation.

Measured on the 2026-09-07 `fridge-s2` start-state stop: 15 of 27 rays struck
`robot0_link2_collision` — the same link the kernel stopped — and no world geom
appeared. Nothing in the record said whether one was there, so the stop could
not be classified either way.

The AABB overlap sweep now runs when the rays found no collidable **world**
geometry, rather than only when they found nothing collidable at all. That
covers the original coincident-shell case unchanged and adds the robot-only
case. A cell whose world backing the rays already found is still left alone, so
this cannot change a verdict the ray pass got right.

Diagnostics only (CLAUDE.md §1.4). Verified on the fixture: a cell on real world
geometry reports `swept=False`, a cell holding only the robot reports
`swept=True` and `self_occupancy_suspect`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 448818c49a31b55c99a971529e3963eb8e9039ae)

* docs(collision): start-state resolved — all three stops are quantisation

The widened sweep answers the ambiguity the previous entry named. Both
candidate self-occupancy stops were re-run on spark at that commit, and both
resolve against the lead: fridge-s2's cell contains the fridge drawer
(fridgesidebyside_main_group_1_g96 — the same body its near-miss pair named at
+0.673 mm), and utensil-s4's contains the cabinet door. The 27 ray fans had
simply missed them. The robot geoms in both cells are real but incidental — the
arm is beside the surface, not instead of it.

So all three start-state stops are ordinary voxel quantisation against
correctly-mapped world geometry. Not self-occupancy, not map inflation
(utensil-s2's cell contains the true surface point at 0.00 mm), and not link
envelope conservatism — the panda_link1 envelope moved these same stops by
0.0003 mm the same day.

Two of the three are stops of a demonstrably clear robot (+23.13, +22.01 mm).
fridge-s2 is separated out as a genuine near-contact at +0.67 mm that no
reduction in map conservatism should clear.

This settles the last population with an unexplored root cause, and it has the
same one as the payload class: the 25 mm grid. It therefore has the same single
remaining lever, and ADR-0101 is currently scoped to the carried payload only —
extending it to bare links would cover both classes with one mechanism. A scope
observation for the WG, not a decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 115e7b2c0f79f2d6ff1516045f40339b197a7e5f)

* docs(plan): start-state is quantisation — two root causes raised and both refuted

Closes the last population with an unexplored root cause. Three start-state
stops read map-side for the first time; map inflation and self-occupancy were
each raised as causes and each refuted by measurement, as was the link envelope.

The self-occupancy refutation is the substantive one: fridge-s2 looked like the
robot in its own map (15/27 rays on robot0_link2_collision, no world geom), and
widening the backing sweep then re-running on spark found the fridge drawer in
that cell — the same body its near-miss pair named at +0.673 mm.

So the class is ordinary voxel quantisation, with the same single lever as the
payload class. ADR-0101 is scoped to the carried payload; extending it to bare
links would cover both with one mechanism. Recorded as a scope note for the WG,
not a decision.

Separates fridge-s2 as a genuine near-contact at +0.67 mm that no reduction in
map conservatism should clear.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 49b4197c9503d8ea4aa06f25f049ff2931ca741d)

* docs(collision): this week lands in three slices, and one change lands nowhere

33 commits and 4 822 lines is over CLAUDE.md §4.2.5's ceiling, and it stopped
being one logical change some days ago. Split by what gates each piece:

* **A** (this branch) — the four instrument repairs, the two evidence producers,
  `refine_dop_to_budget`, the ceiling probe, the narrow-phase latency surface,
  PLAN.md and the evidence ledger. Touches no manifest and neither
  `packages/openral_safety/` nor `cpp/` (empty `git diff --stat` against both),
  so §1.4 applies and §3 does not.
* **B** `feat/216-tight-geometry-link3-4-6` — the three manifest envelopes,
  safety-WG gated on hazard-log Entry 026.
* **C** — `panda_link1`'s envelope, withdrawn. The tool is in A; the manifest is
  nowhere.

A is the one with a deadline. `master` has carried two of the four defects since
2026-09-05 — a harness that cannot discover the action server it launches, and an
adjudicator that stamps every stop `real-contact` off a permitted adjacent-link
overlap — and both corrupt the programme's primary measurement. Every round taken
on `master` since then is unusable, so nothing downstream can be measured until
this lands. That is the whole argument for splitting rather than waiting for the
WG on one PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(methods): refresh the line markers the probe and tool changes moved

`tools/refresh_methods_linenos.py --check` was reporting 46 stale `(LNN)`
markers across `01-hal.md` and `10-tools.md` -- the mechanical consequence of
the backing-probe repairs and the new generator routine shifting line numbers in
files those pages index. CLAUDE.md §4.4 requires the check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(collision): the voxel-resolution strike was an estimate, and it was wrong by 32x

PLAN.md §5 struck the 25 -> 15 mm lever on cost: 26.7 ms estimated against a
33 ms ceiling. It was the only lever in the programme struck on paper rather
than by test. Measured on the real kernel under a real grid, at four resolutions
over the same volume, 200 chunks each:

| resolution | occupied | p99 | estimate |
| ---: | ---: | ---: | ---: |
| 25 mm (shipped) | 9 891 | 0.517 ms | 5.8 ms |
| 20 mm | 17 321 | 0.597 ms | 11.3 ms |
| 15 mm | 35 828 | 0.825 ms | 26.7 ms |
| 12.5 mm | 59 948 | 0.838 ms | 46.1 ms |

Two errors compounded. The 5.8 ms baseline came from the shipped hull
microbenchmark, not from a round trip under a real grid -- which had no latency
surface until one was built the same day the strike was written; the real
baseline is 0.517 ms. And the cubic factor was applied to the wrong term: the
window loop opens with `if (grid.occupancy[idx] == 0) { continue; }`, so
`O(1/res³)` falls on a branch-not-taken while the real work scales with occupied
cells, which are a surface. Cells x7.65, occupied x6.06, p99 x1.62.

The cap objection fails too: 15 mm is 376 680 cells, under the shipped
`world_voxel_max_cells = 614 125`. §5's 2.8M figure was a whole-kitchen grid,
not the arm-neighbourhood window the kernel scans.

Un-struck, but explicitly **not yet actionable**. This measures the kernel
consuming a grid, not `openral_octomap_bridge` producing one at a finer tree
resolution, which is the other half of the cost and is unmeasured. That is the
next step on this lever, not a manifest edit.

`_RES` in the fridge pin file becomes `OPENRAL_FRIDGE_GRID_RES_M`-overridable so
the sweep reproduces from the shipped test rather than from a probe duplicating
it. The default is unchanged and is what `sim_e2e.launch.py` emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

---------

Signed-off-by: Adrian <adrianllopart@gmail.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Adrian <adrian@qualiastudios.dev>
AdrianLlopart added a commit that referenced this pull request Sep 7, 2026
)

* feat(sim): measure the policy's ceiling without the world-voxel gate

After a month of collision work, completion went from 25% (2026-08-26) to
5-10% (2026-09-06) and nobody had ever measured what the policy achieves
WITHOUT the gate. The validation harness refuses to -- `no_enable_octomap_
kernel_check` is on `_SAFETY_KNOB_PATTERNS`, correctly for a validation round,
and that is exactly why the number was never taken. The survey quoted the
external version of this experiment (PACS, arXiv:2511.06385 Table I:
unfiltered 0.70 vs binary-filtered 0.04) and never asked for the in-tree one.

Measured on spark, 88 valid runs, 4 scenes x 2 arms, 10-12 per cell, same
commit and host, both arms running simultaneously so contention loads onto
each equally:

    world-voxel gate OFF   14/45   31.1%
    world-voxel gate ON     1/43    2.3%     Fisher p = 3.5e-04, power 0.97

Per scene: utensil 58% vs 0% (p=0.005), fridge 45% vs 0% (p=0.035),
sink_cup 18% vs 9%, baguette 0% vs 0%. So two scenes are almost entirely
kernel-bound, sink_cup is mixed, and **baguette is policy-bound and cannot
report on collision work at all** -- it should leave the scorecard.

This is a CEILING, not a configuration: it never lands in a scene file, a
launch default or a manifest, the harness's refusal is untouched, and 6 of 91
stops in the #204 battery were real contact. The number says how much headroom
the levers are competing for: up to 29 points, concentrated in the payload
class.

`tools/_ceiling_probe.py` deliberately does not go through the validation
harness; it reuses that harness's own `materialise_scene`, readiness gate and
dispatch tool so the only difference between arms is the gate flag, verified
in the launch argv as `enable_octomap_kernel_check:=false`.

THREE DEFECTS had to be fixed before the number was trustworthy, each of which
would have produced a confidently wrong answer, and all are recorded in
PLAN.md §7:

1. an uncaught `subprocess.TimeoutExpired` killed whole workers rather than
   single rounds, leaving the arms SCENE-CONFOUNDED (gate-off had run mostly
   fridge, which completes; gate-on mostly utensil, which then never did) --
   the interim 4/17 vs 1/20 was an artifact of scene composition;
2. `SidecarClient` reaps the sidecar IT spawned on exit, so the first crashed
   worker took the shared sidecar down and every later run was policy-free
   (30-85 s instead of 600+). Fixed structurally with a keeper process;
3. policy-free runs must be excluded by reading each run's own goal log --
   14 of 102 runs were dropped that way.

Also recorded: `openral deploy sim` cannot run concurrently with itself,
because `_kill_orphan_openral_graph_processes()` matches by argv signature and
cannot tell a concurrent sibling from a crashed orphan. Parallel workers need
`OPENRAL_SKIP_ORPHAN_REAP=1`, which is deliberately NOT committed as a
default.

PLAN.md carries the failure analysis this all came from: median true clearance
at the moment of a kernel stop is 20.1 mm, 85 of 91 stops were of a robot that
was physically clear, and 71% of stops are the carried payload.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 17fc1450d6a9c904f058342982d9ed47edac349b)

* docs(collision): land the ceiling result and reorder the levers by measurement

The ceiling entry is the durable record: `outputs/` is gitignored, so the 88
runs behind 31.1 % vs 2.3 % would otherwise vanish the way the 2026-08-26
battery's artifacts already have.

Also records two lever findings that change what to build next, both measured
rather than argued:

* **Voxel resolution is struck.** `OccupancyVoxels.occupancy` is a dense
  `uint8[]` and the per-link window is `O(1/res^3)`, so halving the cell is an
  8x check cost. 15 mm is 26.7 ms estimated against a 33 ms budget and a
  2.80 MB message (4.6x the 614,125 cap); 12.5 mm is 46 ms. 20 mm fits but buys
  4.4 mm of a 20.1 mm excess. Poor return.
* **Modeled fixtures is the lever instead.** 51 of 70 payload stops are against
  anonymous `voxel_` cells whose certified nearest body is a static kitchen
  fixture MuJoCo already knows exactly -- `counter_1_right` 25 times,
  `fridgesidebyside_main` 9, `counter_1_left` 8. The robot carries an object
  over a counter and the counter's cubes stop it at 20 mm of air. That is the
  survey's own §9 point 1, and #200 already built the machinery for the
  declared place target; this generalises it to the fixture the payload is near.

And one scene finding: **`baguette` should leave the collision scorecard.** It
is 0 % with the gate off, so it is policy-bound and cannot report on collision
work either way -- despite four of the five completions in this ledger's whole
history being baguette runs, which is what made it look like the bellwether.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 0da3e4d8f44d48f5ee43a5af375d9444afbf208c)

* fix(sim): make the ceiling probe pass mypy --strict

`quality` runs `mypy --strict tools/`, which the new probe failed two ways:
`validation_matrix` is a sibling script imported by path (no stub), and the
two `type: ignore`s written for a typed SceneSpec were unused once that import
resolved to Any.

Types the parameter as `Any` with the reason inline rather than scattering
ignores, per CLAUDE.md §2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 36a9d2be03d097e828b986b5c474410c82f154e0)

* test(safety): give the hull narrow phase a latency surface

`tests/sim/safety/test_kernel_latency_soak.py` is the kernel's only latency
test and it publishes **no** `OccupancyVoxels` at all — it runs a synthetic
`soak_test` envelope with no `collision_geometry`, so the staged 26-DOP -> hull
narrow phase never executes. Its pass is vacuous for any change to that phase,
which is the kernel's dominant cost: the shipped benchmark puts the seven link
windows at 10 475 cells and ~5.8 ms against ~8.8 us on an empty grid. Same
class of hole #183 found in the Nav2 live tests, and it is what made the
`link3`/`link4`/`link6` change in this branch unmeasurable.

The new test goes in the fridge pin file because that is the only place in the
tree with a REAL grid: a real RoboCasa kitchen rasterised cell by cell, the
real manifest (so all seven links lower their tight geometry), the real kernel
binary, at `world_voxel_margin_m = 0.0` — the value `panda_mobile` runs.

Measured on q-laptop, 200 chunks over 5 638 occupied cells:

    median 0.1 ms      p99 2.0 ms      target 30 ms, hard ceiling 33 ms

So the narrow phase with seven hulls sits 15x under the chunk budget, which
answers the latency question this branch's manifest change raises on the
shipped configuration rather than by extrapolating the one-off benchmark table.

It also asserts >=90% of chunks come back: a kernel dropping under a real grid
would be a worse finding than a slow p99, and a p99 over a truncated sample
would hide it.

Mutation-checked by forcing the budget to 0.001 ms, which is how the 2.0 / 0.1
numbers above were read out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit e0e2e0f74e6c80da32f57ed33a3e9ebfda4bb80e)

* docs(plan): record ADR-0101 and hazard-log Entry 026

Both live in OpenRAL/management#33. Entry 026 is the record #235 owes;
ADR-0101 is the remaining lever, proposed before code because it crosses
Layer 2 -> Layer 6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 6406ac1e606fa870d337743c1bc9effa99eb42a3)

* fix(hal): the backing probe stopped at decoration and blamed it for the stop

`voxel_backing_record` is the instrument that answers "what, if anything, is
really in the cell the kernel stopped on". `mj_ray` reports only the NEAREST
strike, and the probe took it — so a non-collidable shell in front of the
collidable surface it wraps was the only thing it ever saw, and the cell was
adjudicated `noncollidable_world`, i.e. "the map disagrees with the world".

Measured, not theorised. On the 2026-09-06 battery, **6 of the 8 stops that
carried a backing record at all** came back `noncollidable_world` naming
`counter_1_right_group_top_visual`, while the certified nearest COLLISION
surface at those same stops was ~16 mm away -- inside the same 25 mm cell. The
map was right and the diagnostic was wrong.

That matters beyond one number. Since #180 the depth cast makes exactly these
geoms transparent, so in sim decoration can no longer become occupancy at all;
a `noncollidable_world` verdict is now a statement about the probe or a stale
cell, not a live map defect. The class docstring still carried the pre-#180
justification ("the depth synth strikes these too, so they CAN become
occupancy"), which is what made the misattribution look plausible. Both the
docstring and the METHODS entry are corrected.

A ray that strikes a non-collidable geom inside the cube is now re-cast from
just past it, up to `_VOXEL_BACKING_MAX_LAYERS` (4) times, and BOTH the shell
and whatever it hides are recorded. The existing precedence then does the rest:
`solid_world` outranks `noncollidable_world`, so the cell reads as explained by
real geometry, while a cell with genuinely nothing solid behind the decoration
still reads `noncollidable_world`. Nothing is filtered away -- dropping the
shell would hide a real map defect where one exists.

Diagnostics only (CLAUDE.md §1.4): no stop is suppressed, delayed or altered.

How tested: `tests/unit/test_sim_estop_voxel_backing.py` gains a RoboCasa-shaped
fixture -- a collidable slab wearing a non-collidable shell, both inside ONE
cell, shell nearer the ray start -- and a test that the cell reads `solid_world`
with both geoms named. Mutation-checked: reverting to first-strike-only fails it
with `noncollidable_world`. 15 pass in that file, 28 across the E-stop evidence
suite; `mypy --strict -p openral_hal` clean; methods markers refreshed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit c7bd2c729f792b2b7f829c4946e5750c981337fa)

* docs(plan): record the ADR-0101 recovery measurement and the probe fix

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 2997c01efad8cbdd9147459403b9b6f1307325ab)

* docs(collision): the backing probe defect, and the 32% it moves

The reconstructed layout-47 grid goes 5 638 -> 7 427 occupied cells once the
probe stops blaming decoration for cells whose solid geometry is behind it --
landing between #224's two brackets (5 638 solid-only, 9 217 counting all
decoration) exactly as it should. Every clearance number derived from that
grid was computed against a map ~32% too sparse.

Also records the gap the investigation surfaced: the backing record was
present on only 8 of 91 stops, so the diagnostic that says what the map
contains is absent from 91% of the stops it exists to explain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit f42d3d3f5e61a11a18685c8e58e7de591070a782)

* docs(collision): correct an '8 of 91' claim before it hardened into evidence

An earlier draft of the 2026-09-07 probe entry said the backing record was
present on only 8 of 91 stops. Wrong: it is present on 82. The 74 extra live
in run_gt_evidence.json, which is #177's LATE path.

But that path is unusable on this battery, and its distribution is a trap --
46 of 74 read 'unbacked', which looks like the kernel stopping on cells nothing
backs. It is instead exactly the defect 10ff989 describes: the late path
omitted grid_orientation_xyzw, took identity, and decoded a cube metres from
the stopping link, reporting unbacked with 27 rays cast and 0 hits. 10ff989
landed 2026-09-05 and NONE of the battery's commits (all 2026-09-04) carry it.

Kept as a visible correction rather than a silent edit, because the wrong
version was a more exciting finding than the right one -- which is the specific
way this ledger has been burned before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit 51c8e2e19ab6952cb9ff10a6574fde9291f6e970)

* fix(sim): the validation harness could not see the graph it launched

Every scene of every `tools/validation_matrix.py` round on post-#231 `master`
reported `harness-error` — "action server never appeared" — beside a graph that
was up and healthy the whole time. Two independent defects, each sufficient on
its own, both measured on `q-laptop` against a live `robocasa_drawer_utensil`
round.

1. `_launch_env` did not apply the sim DDS scope. Since #227/#231 `openral
   deploy sim` confines itself with `confine_sim_scope` (`ROS_DOMAIN_ID=77`,
   `ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST`) so a simulation and a real robot
   cannot share a graph. The deploy applied that to itself; the harness did not,
   and polled domain 0. `ROS_DOMAIN_ID=77 ros2 action list` showed
   `/openral/execute_rskill` the whole time; unscoped showed nothing.

   `confine_sim_scope` now runs inside `_launch_env` — applied on this side
   rather than left to the child, because it uses `setdefault`, so the deploy
   inherits the harness's value instead of choosing its own and the two agree by
   construction. An operator who exports their own scope still wins on both.

2. `ros2 action list --no-daemon` cannot discover an advertised action. The poll
   passed it for a real hazard: a daemon left over from an unscoped shell
   answers from the environment *it* started with — the false reading that made
   `ROS_AUTOMATIC_DISCOVERY_RANGE=LOCALHOST` look broken in #227. But the
   one-shot node it builds has a discovery window too short to see an action
   that is genuinely up: against a live graph, `ros2 action list` found it and
   `--no-daemon` did not, on the same domain, repeatably. The poll could never
   succeed on any scope.

   Replaced with a one-shot `ros2 daemon stop` under the round's own `env`
   before the loop, which closes the original hazard from the other side: the
   daemon the loop then uses is started by that call, on that scope.

The same round that had reported `harness-error` twice completed with a real
outcome (`utensil`, `deadline-no-grasp`) on the first attempt after both fixes.

`docs/reference/collision-validation-evidence.md` gains the 2026-09-07 entry,
including what it invalidates: any round taken on post-#231 master before today
measures the harness, not the kernel. Rounds on earlier commits — the ceiling
battery's `80027b18` arm among them — predate #231 and are unaffected; checked,
not assumed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 773560899f80d0a7797a6c4897ab210c408d6fba)

* docs(plan): reconcile the checklist with the measured levers, and record both launch failures

The §7 checklist had drifted from §5's 2026-09-07 reordering and still listed
two levers as open that §5 had already struck by measurement:

- "Lever 1: the payload bounding box" kept the pre-reordering numbering. The
  payload is still 71 % of stops, but its primitives are already tight to
  −1.5 mm beyond the voxel term, so there is no payload-geometry headroom. The
  class is the right target; the mechanism that reaches it is ADR-0101's modeled
  fixtures, not a tighter payload box. Tracked there.
- "Lever 3: voxel resolution 25 → 15 mm" is struck on measured cost: a dense
  `uint8[]` occupancy and an `O(1/res³)` window make halving the cell an 8×
  check cost — 26.7 ms at 15 mm against a 33 ms ceiling.

Both now marked struck with the number that struck them, so the checklist and
§5 say the same thing.

Adds the two launch failures found today, which are distinct and were being
conflated:

- the **harness** could not see the graph it launched, and had not since #231 —
  wrong DDS scope plus a `--no-daemon` poll that cannot discover an advertised
  action at all. This is why post-#231 rounds looked like launch failures, and
  it is a precondition for every open measurement below it.
- the **launch parser** ran under the system interpreter, so `dist-packages`
  shadowed the venv and `import pandas` aborted the whole launch on a Jetson
  AGX Thor. That is the spark-side failure, fixed on its own branch.

Closes the `baguette` scorecard item: 0/11 with the gate off means policy-bound,
so it leaves the completion scorecard while staying in the matrix. Already
recorded in the ceiling entry; the plan now points at it rather than restating
it.

`collision-validation-evidence.md` gains standing caveat 10 — the citation rule
for post-#231 rounds — because that is what the caveats list is for.

Re-derivation of ADR-0101's 94 % against the post-fix live map is unblocked: the
foreign 1.9 GB GPU process is gone (175 MiB of 8151 in use).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit e7956f77120dd02bcb9899f7f0c140e7d8151a16)

* feat(evidence): give ADR-0101's 94 % a producer, and make its denominator honest

ADR-0101 cites "48 of 51 payload-vs-`voxel_` stops (94 %) recovered" as the
measurement that justifies building the first **fail-open** mechanism in the
hazard log. That number was computed offline, by hand, and had no producer in
the repo — which is exactly the shape of claim
`docs/reference/collision-validation-evidence.md` exists to prevent.

`tools/adr0101_recovery.py` is that producer. The counterfactual it evaluates
needs no kernel code and no layer crossing: for a payload stop against an
anonymous cell, "would a modeled fixture have let this through?" is the same
question as "was the payload certifiably clear of the real surface?", and the
battery already records both halves. Pure, offline, stdlib-only, like
`tools/round_power.py`.

Two decisions carry the safety direction, and both are tested:

- **Zero is contact, not clearance.** A payload touching a fixture is stopped by
  the modeled body exactly as it was by the cube. Putting `0.0` on the clearance
  side would count real contacts as recoveries — the one class the mechanism
  must never suppress. Mutation-checked: flipping `>` to `>=` fails the test.
- **Exclusions are reported, never dropped.** A recovery rate is only as honest
  as the set it divides by, and the two errors are not symmetric: silently
  dropping an unadjudicable stop shrinks the denominator and *inflates* the
  rate. `recovery_rate` is `None` over an empty set and `render` refuses to
  print a percentage rather than showing 0 % or 100 %.

Tested against the real `2026-08-23-master-s1` round, which recorded exactly the
stop shape the tool selects but predates the probe's distance attestation
(standing caveat 8) — so it must land in `excluded`, by name and with a reason.
It does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 60dcb2ff992cb7e4c06e5e362d8a000cd82a43e8)

* fix(evidence): attribute a payload stop to the fixture, not to a robot link

The first cut of `tools/adr0101_recovery.py` read the by-fixture breakdown off
`ground_truth.nearest_pair`. That field records the closest probed pair *of any
kind*, and for a carried payload it is routinely two robot links — on the round
this was caught with, `robot0_link3` vs `robot0_link4` at −36.3 mm, while the
payload itself sat 24.9 mm clear of a counter. So the tool put `robot0_link4`
into a table of kitchen fixtures: a robot link presented as a static world body,
inside the record that argues for modelling static world bodies.

The body behind `nearest_tripping_party_m` is recorded in exactly one place —
the raw `sim.estop_ground_truth_snapshot` line's `nearest_payload_world_pairs` —
so `fixture_at_stop` reads it there and verifies the match rather than assuming
it: that list's minimum certified distance must equal the gap the stop was
adjudicated on, both being the same probe call. If they disagree the snapshot
describes some other stop and no attribution is made. The recovery *count* never
depended on this and does not now; only the breakdown did.

Verified on the live round: the fixture is `counter_1_right_group_main`, which
is `counter_1_right` — the body ADR-0101 §3 already names as the top fixture at
25 of 70 payload stops. The premise reproduces independently.

Adds the round as a fixture (one snapshot line plus `verdicts.json`, provenance
in `SOURCE.txt`) and three tests, the first of which is a regression test for
this defect. Mutation-checked: pointing the reader at `nearest_link_link_pairs`
fails it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit a0f5f64bdd9d8d2cdfda75d644e6a3efec2bef45)

* docs(collision): the first post-fix round, and a cell backed only by decoration

`2026-09-07-adr0101-live-1` is the first validation-matrix round since #231 to
reach a real outcome rather than `harness-error`. It confirms ADR-0101's
certified premise directly: the payload was stopped at −4.05 mm reported while
sitting +24.86 mm clear of `counter_1_right_group_main` — the exact fixture the
ADR names as its largest class.

It also turned up something the ADR does not account for. The backing probe —
with the decoration-walking fix verified live in the running process — reports
the tripping cell as `noncollidable_world`, backed solely by
`counter_1_right_group_top_visual`. Within that 25 mm cube there is no
collidable geometry at all; the collision slab is the 24.9 mm away that the
certified probe independently measured.

That is the occupancy grid faithfully recording what a depth sensor sees, which
is the visual shell, not the collision body. It cuts both ways for ADR-0101's
suppression bound: if a modeled fixture publishes collision primitives, a cell
like this is *not* geometrically explained by them and the stop survives — so
the 94 % would be optimistic; if it publishes the visual geometry instead, the
mechanism suppresses against a surface the kernel does not protect. Benign in
sim, where a non-collidable geom cannot be hit; not benign on hardware, where
what the sensor sees is what the robot hits. That is the sim→real seam the ADR
already flags, now with an instance instead of a caveat.

Recorded at n=1 and labelled as such. A 12-round batch is running to measure how
often a payload stop is backed by decoration alone; nothing here revises the
94 % in either direction yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 6bed91758410ac6b3aa9ab12a6e8251313555a42)

* docs(collision): withdraw the 'map is proud of the collision model' reading

The 2026-09-07 entry interpreted a cell backed only by a non-collidable geom as
the occupancy grid mapping the visual surface while the collision body sat
behind it. RoboCasa's asset code does not support that reading.

`robocasa/models/fixtures/counter.py` emits one full-span `<name>_top_visual`
box (`group=1`, `contype=0`) and then chunks the *same* volume into collidable
geoms via `_get_chunks`, which tile it exactly — identical `pos[1]`, `pos[2]`,
identical `size[1]`, `size[2]`, `x` tiling the full span. Visual and collision
are coincident by construction, so there is no offset to be proud by.

What remains is a real three-way tension: the backing probe finds no collidable
geom in the cell after re-casting past decoration; the certified probe puts the
nearest collidable geom of the same body 24.86 mm away; the asset code says they
are coincident. All three cannot hold.

The leading candidate is a residual defect in the re-cast itself: it advances
"just past" a strike, which steps over a *coincident* collidable twin and lands
outside the cube. The fix was built for decoration in front of a slab, not
decoration sharing its surface.

This is left open and labelled, not resolved, because it changes what the
programme is optimising: if the instrument is wrong, the 20.1 mm payload excess
ADR-0101 is sized against is itself suspect. Resolving it needs a direct query
of the live model at the stop, which no current artifact records.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit b76aa0e41239b405364f976eebe1bbd4b2a64003)

* fix(evidence): a permitted link overlap was stamping every stop 'real-contact'

#220 (on master since 2026-09-05) gave the HAL a link-vs-link probe so a self
stop could be scored against the pair the kernel named. That part is right, and
it is what makes standing caveat 9 closeable. But the new pairs were folded into
the adjudicator's `nearest_any`, which drives its first and most decisive rule:
any probed pair at or below 0 m is `real-contact`.

Adjacent robot links overlap permanently. They are in the robot's
allowed-collision matrix and the kernel never checks them. So from #220 onward
`nearest_any <= 0` was vacuously true and every adjudicable stop was stamped
`real-contact`, whatever the tripping party's actual clearance.

Measured on `2026-09-07-adr0101-live-1`: `robot0_link3`/`link4` at -36.3 mm,
`link5`/`link6` at -23.0 mm, `link4`/`link5` at -4.6 mm — all certified, all
permitted, none of them what the kernel stopped for — while the carried payload
it did stop for sat +24.86 mm clear of the counter.

`nearest_any` now excludes `nearest_link_link_pairs` wholesale and adds back
only the pair the kernel named, which `party_pairs` already isolates in the
`self_pair` branch. A genuine link-vs-link self stop in real overlap is still
detected as contact; a permitted overlap two joints away is not.

Re-derived over the four stops recorded today, three move
`real-contact -> within-quantization` and the one true contact (-2.32 mm) is
preserved. Verdicts are pure and offline, so affected rounds re-adjudicate
without re-running.

Two tests, both mutation-checked against restoring `+ link_link`. Standing
caveat 11 records the citation rule: no `real-contact` verdict from 2026-09-05
to 2026-09-07 is safe to cite. The error runs one way — it manufactures real
contacts, never clears one — so nothing was wrongly passed as safe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 483a82e1cdb2d9676b5a7e1ee322e343b25db0d7)

* docs(collision): the adr0101-live battery — 71 % of stops are of a clear robot

Thirteen rounds on q-laptop (utensil and fridge, seeds 1-7), run after both
harness fixes and re-adjudicated offline after the `nearest_any` fix. First
battery on this page where harness and adjudicator were both known-good at
reading time; three rounds changed verdict when re-derived.

Seven stops. Five (71 %) were of a physically clear robot, at +0.67, +11.13,
+22.01, +23.13 and +24.86 mm true clearance. Two were real contact, at -2.32
and -0.11 mm. The 71 % reproduces the #204 battery's 85-of-91 on an independent
battery, a different commit and a repaired instrument — the number has not
moved.

Four stops are the carried payload, splitting evenly clear/contact;
`adr0101_recovery` reports 2 of 4 recovered, median 17.99 mm, minimum 11.13 mm.
Far below the offline 94 %, but n=4 does not contradict it and no revision is
claimed.

Two findings the levers do not cover:

- Three of seven stops are `estop-initial-configuration` — the arm stopped at
  reset by its own start pose, before doing anything. tight_geometry, modeled
  fixtures and voxel resolution all address the carry phase; none addresses a
  base placement that starts the arm inside a counter. One is at +0.67 mm and
  would survive any geometry work.
- Five of thirteen rounds never grasped at all. With the ceiling result, roughly
  half of what reads as collision-programme failure on these scenes is the
  policy not reaching the phase where the kernel matters.

One round (fridge seed 6) completed with the gate on, against a 2.3 % gate-on
rate in the ceiling battery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 9676c931477608c46236e28adaf5d6346f7ecd54)

* docs(plan): record the repaired-instrument rate, and two classes no lever covers

The false-positive rate re-measured on a working harness and a working
adjudicator is 71 % (5 of 7 stops of a physically clear robot), reproducing the
#204 battery on an independent battery and a different commit. The headline
number has not moved.

Also records the #220 adjudicator inversion that made that measurement possible
to get wrong for two days, and adds two open items the §5 levers do not touch:

- start-state collisions, a third of all stops, where the arm is stopped at
  reset by its own pose before doing anything;
- rounds that never grasp at all, five of thirteen, which bound how much of the
  scorecard any collision work can move.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit c1e2203a2e3c07383bc7a3db9f1691fa28ed8ab9)

* feat(evidence): give the stop decomposition a producer, and measure the link fix

PLAN.md §5's table is the measurement that struck two collision levers and
promoted a third: per stop class, the kernel's over-approximation with the grid's
21.65 mm half-diagonal subtracted. Like ADR-0101's 94 %, it was computed by hand
and had no producer. `tools/stop_excess.py` is that producer.

Two guards, both tested. Only stops whose probe certified its distances are
counted — an uncertified distance can be wrong by 15-108 mm, larger than the
quantity being measured. And the half-diagonal comes from each round's own
recorded `grid_resolution_m`; a stop without one is skipped rather than
defaulted to 25 mm, since assuming it would not perturb the answer but replace
it. `has_geometry_headroom` is strict at zero: a class exactly at the voxel term
cannot recover a millimetre, so calling that headroom would license wasted work.

Run over the 13-round adr0101-live battery it gives the first live read on
whether the tight_geometry work did what it was designed to do:

  payload  n=4   median excess +13.15 mm   beyond voxel  -8.50 mm
  link     n=3   median excess +25.51 mm   beyond voxel  +3.86 mm

The #204 battery measured the link class at +33.1 mm beyond voxel, with
panda_link6 holding 18 of its 29 link stops. link6 now ships tight geometry and
the class reads +3.86 mm. n=3 and a different link mix, so this is consistent
with the fix working rather than proof of it — but it is the first live evidence
in that direction, and it is what PR #235 was missing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 551b7c5932929d39a685038a5c8cb1d0d5918f08)

* fix(hal): the backing probe could not see a slab coincident with its shell

`c7bd2c7` taught the probe to walk past a non-collidable strike and cast again,
which finds a collidable slab BEHIND a decoration shell. It cannot find one
coincident with it, and RoboCasa builds every counter top that way:
`robocasa/models/fixtures/counter.py` emits one full-span `<name>_top_visual`
(`contype=0`) and then tiles the same volume with collidable chunks via
`_get_chunks`. Their surfaces are the same plane, so stepping `distance + eps`
past the shell lands inside the chunk, where the ray reports no further entry
surface and the chunk is never seen.

That is what produced the three-way contradiction recorded this morning. It is
now settled from the round's own certified witness points: the cell spans
z in [0.90005, 0.92505] and the collidable chunk `counter_1_right_group_top_0`
has its surface at z = 0.920. The solid geometry was inside the cell the whole
time; the certified probe and the asset code agreed and only the backing probe
was wrong.

`voxel_backing_record` now falls back to a world-AABB overlap sweep over
collidable geoms when, and only when, the rays found nothing solid. An AABB
overlap can claim a geom whose surface misses the cube, so it is deliberately a
supplement rather than a replacement — for a diagnostic whose failure mode is
calling real geometry "decoration" (#180), erring toward found is the right
direction, and it can never override a ray pass that already found something.

Reproduced with a coincident shell/chunk fixture that yields the same 9-of-27
ray signature as the live round; mutation-checked by forcing the sweep off.

Diagnostics only (CLAUDE.md §1.4) — the certified probe measures geom-to-geom
distance and never used rays, so the 71 % rate and the stop decomposition are
unaffected. What moves is the backing CLASS of cells previously read
`noncollidable_world`, which is what the "32 % too sparse" entry and ADR-0101's
"cells no real body explains" premise rest on. Both should be re-derived from a
post-fix round before being leaned on further.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 10a3469ca9885c93414e280543b2a09fe863d00f)

* docs(plan): the geometry levers are exhausted, and link1's hull is not worth it

Three closures from the 13-round battery and its decomposition.

The probe contradiction was the instrument again: the cell spans
z in [0.90005, 0.92505] and the collidable chunk's surface is at z = 0.920, so
the solid geometry was inside it all along. Fixed; diagnostics only.

panda_link1 is the only link without a stage-2 hull (1588 vertices against a 320
cap) and caused two of three start-state stops, which made it look like the next
obvious manifest edit. Measured, those stops are +3.86 and +8.68 mm beyond the
voxel term, so an exact hull recovers at most ~9 mm of a ~25 mm error while
raising the cap 5x is a hot-path change needing safety-WG review. Struck.

And the conclusion those two produce together: the link class now measures
+3.86 mm beyond voxel, down from +33.1 mm in the #204 battery whose link stops
were 18-of-29 panda_link6 — the link that now ships tight geometry. With the
payload class at -8.50 mm, both are at or below the grid term. Every remaining
millimetre of over-approximation is the 25 mm voxel grid, and refining that was
struck on measured cost. No tighter envelope anywhere can recover anything
further.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 1d116ae9cdef8d25beec9f13cd8c31fa3a5c2aed)

* fix(safety): batch the hull-overhang query so a large link cannot exhaust memory

`hull_overhang_m` fed every barycentric sample to
`trimesh.proximity.closest_point_naive` in one call. That query allocates a
`samples x mesh-faces x 3` array, and the docstring's own premise — "a few
thousand by a few hundred here" — holds only for the small panda links that
have ever reached it.

`panda_link1` is 6260 mesh vertices and ~12k triangles; a 320-vertex hull over
it is ~636 facets at 325 samples each, so the single call asks for 57.8 GiB and
raises `numpy._core._exceptions._ArrayMemoryError`. Latent until something
declared a hull for that link.

Batching at 512 samples per call bounds peak memory by `512 x faces x 3`
independently of link size, and the maximum over batches is the maximum over the
whole set, so the returned number is unchanged. `check` still reports
mesh-outside-DOP +0.000000000 mm on all seven links.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit e8e07315a5be846e2d27002a87c3aa258c1d7a2a)

* feat(tools): give the generator a third option when an exact hull is over budget

`derive_tight_geometry` had two outcomes for a link: ship the exact convex hull,
or -- if it exceeds `MAX_TIGHT_HULL_VERTICES` -- fall back to the 26-DOP alone.
`panda_link1` is the second case (1588 vertices), and its DOP's support gap
against the real mesh is a median 4.52 mm and up to 25.68 mm.

`refine_dop_to_budget` builds a third thing: the DOP intersected with the exact
hull's own face planes, worst-violation first, stopping before the vertex count
would exceed the budget. Every candidate plane is tangent to `conv(mesh)`, so
containment stays *definitional* rather than fitted, and the result is
`mesh ⊆ result ⊆ DOP ⊆ box` at every step. A subset-then-expand construction
cannot promise that: expansion pushes vertices out through the DOP slabs, and
link1's DOP has 0.083 mm of room inside its manifest box. The routine refuses
rather than emit an envelope that cuts its mesh.

On link1's real mesh it reaches a support gap of 0.18 mm median / 0.65 mm max
against the DOP's 4.52 / 25.68 mm, at 320 vertices.

**No manifest declares a refined envelope, and this commit changes none.** The
envelope was generated and put under a live battery on 2026-09-07; the stops it
was predicted to clear moved by 0.0003 mm. The prediction failed because it read
a support-census deficit against the shipped *box* and attributed all of it to
the hull, when the DOP had already collected it. That refutation is recorded in
the docstring, in `docs/methods/10-tools.md` and in the evidence page, so the
next reader does not re-derive the same wrong expectation from a routine that
looks like it was built for a reason nobody wrote down.

Which is also why the test is a direct one rather than a manifest assertion: an
unexercised generator path rots. It pins the three properties a safety-WG
reviewer would otherwise re-derive by hand -- mesh inside the result, result
inside the DOP, and strictly less enclosed volume than the DOP it refines --
against the real robosuite mesh, not a fixture.

Also carries the memory bound `_OVERHANG_BATCH` / `_OVERHANG_MAX_SAMPLES` needs
to document: the single-call `hull_overhang_m` form asks for 57.8 GiB on a
320-vertex envelope over a 12k-triangle mesh.

How tested: `pytest tests/unit/test_collision_tight_geometry.py` (11.5 s for the
new case, inside the 30 s unit budget).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(plan): correct the link1 strike — the reasoning was wrong, not just the call

The earlier entry struck link1's hull because it "recovers at most ~9 mm of a
~25 mm error". That is the wrong question: what matters is whether the recovery
flips the stop, and the start-state census's deficit table says 10 mm clears 14
of 14 link1 states. It also assumed the only options were the DOP or an
over-budget hull, and that a kernel cap change would be needed. Neither held —
the refined envelope fits the existing 320-vertex budget.

Records the measured result (0.18 / 0.65 mm against the DOP's 4.52 / 25.68 mm,
p99 0.5 ms on 9891 cells) and downgrades the start-state item from "no lever
touches it" to "one lever now reaches it, unconfirmed": two of its three stops
were link1, predicted to clear but not yet observed in a post-change battery.
The third is a genuine +0.67 mm near-contact that no geometry work reaches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 7fcc7168f57786846ff51a2df1e97b7cd1ebce5c)

* docs(collision): the link1-envelope confirmation battery is void, and says so

Eight rounds were run to confirm that panda_link1's refined envelope clears the
two panda_link1 start-state stops. All eight died at 16-19 s with the XR-1
sidecar OOMing at boot: a concurrent job on this shared host held 2.0-2.5 GB of
7.53 GiB while the sidecar needs ~3.5 GB alongside the scene.

Recorded rather than discarded because the failure mode is exactly the shape of
the result being looked for. Every round shows `stop: null`, and "seeds 2 and 4
no longer produce estop-initial-configuration" is precisely what the envelope
predicts — but the policy never loaded, so the arm never moved and no kernel
check ran. Reading it as confirmation would be reading a crash as a measurement.

The prediction stands unconfirmed. Confirming it needs an uncontended window
here or a host with headroom; spark is the latter, with the standing caveat that
XR-1 has never completed an end-to-end rollout on GB10, so one smoke round must
succeed before a battery is worth running there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit f4a670f55809e56de5f163d3bf33b75c010fce4e)

* docs(collision): the link1 refined envelope does not move the stops it was built for

Three rounds on spark, utensil seeds 2/3/4, on the commit carrying panda_link1's
refined envelope — also the first end-to-end XR-1 rollout completed on GB10.

The prediction is refuted. Same seed, same scene, same stop, one commit apart:
seed 2 reads -2.37794 mm under the 26-DOP and -2.37825 mm under the refined
envelope; seed 4 reads -8.31 and -8.31495. Tightening link1 from a 25.68 mm
worst-case support gap to 0.65 mm moved the reported depth by 0.0003 mm.

The reasoning error is identifiable. The start-state census's "10 mm clears 14
of 14 link1 states" is computed with box_box_distance against the manifest OBB.
The 26-DOP shipped after that census and already collected that recovery
(53.27 -> 25.69 mm). Treating the census's OBB-relative deficit as still-
available headroom double-counted a tightening that had already landed.

This is therefore the controlled test that the geometry levers are exhausted:
the same stop under two envelopes differing by 25 mm of worst-case looseness,
moving 0.0003 mm.

Notes the untested hypothesis for the residual: the octomap bridge's README
records that a published grid can report a surface up to one full tree
resolution (25 mm) nearer than it is, and stop_excess.py subtracts only the
21.65 mm half-diagonal — so map inflation reads as geometry excess and invites
exactly the hunt this entry closes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit b55f90af3f2ae0a7a6626b0773dfefbd885121fa)

* docs(plan): the link1 envelope was refuted, and the exhaustion claim got a test

Corrects the link1 item twice over. I struck the lever for the wrong reason
(fraction of error recovered rather than whether it flips the stop), then
un-struck it for another wrong one: the census's "10 mm clears 14/14 link1
states" is computed against the manifest OBB, and the 26-DOP shipped later had
already collected that recovery. Three rounds on spark measured the same stops
under both envelopes and they moved 0.0003 mm.

That failure upgrades the exhaustion conclusion from an inference over a
decomposition to a controlled test: the same stop under two envelopes differing
by 25 mm of worst-case support gap does not move. Whatever the residual is, it
is not the collision model.

Start-state goes back to "no lever reaches it" — it is a base-placement question,
and the census shows joints 3-7 cannot change a start-state verdict at all when
link1 or link2 dominates, which is 83.3% of stopping states.

Adds the hypothesis that displaces the geometry hunt: octomap marks the cell
containing the ray endpoint, so the grid can report a surface up to a full 25 mm
tree resolution nearer than it is, and stop_excess.py subtracts only the
21.65 mm half-diagonal. Untested, and the first thing worth measuring next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 7d90a810f98239251c4198a3909c44b35bb3d5bd)

* docs(collision): the map-side reading of all three start-state stops

estop-initial-configuration was three of seven stops in the adr0101-live
battery and the class no lever in PLAN.md §5 addresses. This is the first
map-side reading of it.

Where the evidence lives matters and is counter-intuitive: a start-state
snapshot carries collision_evidence: null and evidence_voxel_backing: null,
because the E-stop fires before the kernel's safety.collision line reaches the
bridge and the freshness gate correctly refuses to attribute a cell. The record
arrives on the deferred sim.estop_ground_truth_evidence path instead
(run_gt_evidence.json, backing_after_snapshot_ns: 0). Reading only the snapshot
shows nothing and invites the conclusion that the class is un-diagnosable.

Three stops, three causes:

- utensil-s2 is textbook quantisation. The cell CONTAINS the true nearest
  surface point of the door backing it (0.00 mm), so the map is where the world
  is; +23.13 mm clear, reported -2.38 mm, excess 25.51 = 21.65 half-diagonal +
  3.86 mm. This also refutes the octomap-inflation hypothesis raised earlier the
  same day: an inflated cell would not contain the surface.
- fridge-s2 has the robot in the cell and no world geometry at all — 15 of 27
  rays struck robot0_link2_collision, the same link the kernel stopped.
- utensil-s4 shows the same signature on weaker evidence: its robot geoms come
  from the conservative AABB sweep, not rays (0/27). The same stop read
  `unbacked` on q-laptop before that fix landed, so the fix turned a blank into
  a diagnosis.

Records that the mechanism is NOT a missing exclusion — the self-filter passes
robot bodies to the synth, matches by prefix plus descendants, and marks
transparent rays so they clear rather than mark — and names the decisive test
that no artifact supports today: re-run the layout with the arm parked
elsewhere and see whether the cell persists. That needs a start-pose override
the harness lacks.

n=3 and self_occupancy_suspect is explicitly not conclusive alone, so this is
logged as a lead, not a finding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit d63d6ad608321441655af3dc4bfd6ad535436427)

* fix(hal): sweep for world geometry when a cell's only solid backing is the robot

`self_occupancy_suspect` was resting on 27 rays having missed nothing. A cell
whose only collidable ray hit is a robot body produces an identical record
whether world geometry is absent or merely unsampled, and that distinction is
exactly what separates a self-occupancy stop from ordinary quantisation.

Measured on the 2026-09-07 `fridge-s2` start-state stop: 15 of 27 rays struck
`robot0_link2_collision` — the same link the kernel stopped — and no world geom
appeared. Nothing in the record said whether one was there, so the stop could
not be classified either way.

The AABB overlap sweep now runs when the rays found no collidable **world**
geometry, rather than only when they found nothing collidable at all. That
covers the original coincident-shell case unchanged and adds the robot-only
case. A cell whose world backing the rays already found is still left alone, so
this cannot change a verdict the ray pass got right.

Diagnostics only (CLAUDE.md §1.4). Verified on the fixture: a cell on real world
geometry reports `swept=False`, a cell holding only the robot reports
`swept=True` and `self_occupancy_suspect`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 448818c49a31b55c99a971529e3963eb8e9039ae)

* docs(collision): start-state resolved — all three stops are quantisation

The widened sweep answers the ambiguity the previous entry named. Both
candidate self-occupancy stops were re-run on spark at that commit, and both
resolve against the lead: fridge-s2's cell contains the fridge drawer
(fridgesidebyside_main_group_1_g96 — the same body its near-miss pair named at
+0.673 mm), and utensil-s4's contains the cabinet door. The 27 ray fans had
simply missed them. The robot geoms in both cells are real but incidental — the
arm is beside the surface, not instead of it.

So all three start-state stops are ordinary voxel quantisation against
correctly-mapped world geometry. Not self-occupancy, not map inflation
(utensil-s2's cell contains the true surface point at 0.00 mm), and not link
envelope conservatism — the panda_link1 envelope moved these same stops by
0.0003 mm the same day.

Two of the three are stops of a demonstrably clear robot (+23.13, +22.01 mm).
fridge-s2 is separated out as a genuine near-contact at +0.67 mm that no
reduction in map conservatism should clear.

This settles the last population with an unexplored root cause, and it has the
same one as the payload class: the 25 mm grid. It therefore has the same single
remaining lever, and ADR-0101 is currently scoped to the carried payload only —
extending it to bare links would cover both classes with one mechanism. A scope
observation for the WG, not a decision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 115e7b2c0f79f2d6ff1516045f40339b197a7e5f)

* docs(plan): start-state is quantisation — two root causes raised and both refuted

Closes the last population with an unexplored root cause. Three start-state
stops read map-side for the first time; map inflation and self-occupancy were
each raised as causes and each refuted by measurement, as was the link envelope.

The self-occupancy refutation is the substantive one: fridge-s2 looked like the
robot in its own map (15/27 rays on robot0_link2_collision, no world geom), and
widening the backing sweep then re-running on spark found the fridge drawer in
that cell — the same body its near-miss pair named at +0.673 mm.

So the class is ordinary voxel quantisation, with the same single lever as the
payload class. ADR-0101 is scoped to the carried payload; extending it to bare
links would cover both with one mechanism. Recorded as a scope note for the WG,
not a decision.

Separates fridge-s2 as a genuine near-contact at +0.67 mm that no reduction in
map conservatism should clear.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
(cherry picked from commit 49b4197c9503d8ea4aa06f25f049ff2931ca741d)

* docs(collision): this week lands in three slices, and one change lands nowhere

33 commits and 4 822 lines is over CLAUDE.md §4.2.5's ceiling, and it stopped
being one logical change some days ago. Split by what gates each piece:

* **A** (this branch) — the four instrument repairs, the two evidence producers,
  `refine_dop_to_budget`, the ceiling probe, the narrow-phase latency surface,
  PLAN.md and the evidence ledger. Touches no manifest and neither
  `packages/openral_safety/` nor `cpp/` (empty `git diff --stat` against both),
  so §1.4 applies and §3 does not.
* **B** `feat/216-tight-geometry-link3-4-6` — the three manifest envelopes,
  safety-WG gated on hazard-log Entry 026.
* **C** — `panda_link1`'s envelope, withdrawn. The tool is in A; the manifest is
  nowhere.

A is the one with a deadline. `master` has carried two of the four defects since
2026-09-05 — a harness that cannot discover the action server it launches, and an
adjudicator that stamps every stop `real-contact` off a permitted adjacent-link
overlap — and both corrupt the programme's primary measurement. Every round taken
on `master` since then is unusable, so nothing downstream can be measured until
this lands. That is the whole argument for splitting rather than waiting for the
WG on one PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(methods): refresh the line markers the probe and tool changes moved

`tools/refresh_methods_linenos.py --check` was reporting 46 stale `(LNN)`
markers across `01-hal.md` and `10-tools.md` -- the mechanical consequence of
the backing-probe repairs and the new generator routine shifting line numbers in
files those pages index. CLAUDE.md §4.4 requires the check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* docs(collision): the voxel-resolution strike was an estimate, and it was wrong by 32x

PLAN.md §5 struck the 25 -> 15 mm lever on cost: 26.7 ms estimated against a
33 ms ceiling. It was the only lever in the programme struck on paper rather
than by test. Measured on the real kernel under a real grid, at four resolutions
over the same volume, 200 chunks each:

| resolution | occupied | p99 | estimate |
| ---: | ---: | ---: | ---: |
| 25 mm (shipped) | 9 891 | 0.517 ms | 5.8 ms |
| 20 mm | 17 321 | 0.597 ms | 11.3 ms |
| 15 mm | 35 828 | 0.825 ms | 26.7 ms |
| 12.5 mm | 59 948 | 0.838 ms | 46.1 ms |

Two errors compounded. The 5.8 ms baseline came from the shipped hull
microbenchmark, not from a round trip under a real grid -- which had no latency
surface until one was built the same day the strike was written; the real
baseline is 0.517 ms. And the cubic factor was applied to the wrong term: the
window loop opens with `if (grid.occupancy[idx] == 0) { continue; }`, so
`O(1/res³)` falls on a branch-not-taken while the real work scales with occupied
cells, which are a surface. Cells x7.65, occupied x6.06, p99 x1.62.

The cap objection fails too: 15 mm is 376 680 cells, under the shipped
`world_voxel_max_cells = 614 125`. §5's 2.8M figure was a whole-kitchen grid,
not the arm-neighbourhood window the kernel scans.

Un-struck, but explicitly **not yet actionable**. This measures the kernel
consuming a grid, not `openral_octomap_bridge` producing one at a finer tree
resolution, which is the other half of the cost and is unmeasured. That is the
next step on this lever, not a manifest edit.

`_RES` in the fridge pin file becomes `OPENRAL_FRIDGE_GRID_RES_M`-overridable so
the sweep reproduces from the shipped test rather than from a probe duplicating
it. The default is unchanged and is what `sim_e2e.launch.py` emits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>

* feat(safety): ship tight geometry for panda_link3, link4 and link6

The 120-run #204 battery decomposed every stop into what the kernel
reported and what was certifiably there. Split by class, with the 25 mm
cell half-diagonal (21.65 mm) subtracted:

    payload stops (n=62): median excess 20.1 mm -> -1.5 mm beyond the voxel
    link stops    (n=29): median excess 54.8 mm -> +33.1 mm beyond the voxel

So the payload primitives are already tight and the LINK ENVELOPES are not.
`panda_link6` alone dominates 18 of the 29 link-class stops, including the
seven stops on one fridge cell (`voxel_352030`) at 37-63 mm of certified
clearance. It shipped no `tight_geometry` at all.

This adds the DOPs and hulls for the three links that had none:

    link      box excess -> DOP excess     recovered
    link3        75.57 mm -> 23.84 mm       51.7 mm
    link4        76.12 mm -> 23.15 mm       53.0 mm
    link6        52.70 mm -> 21.53 mm       31.2 mm

link6's 31.2 mm is almost exactly the 33.1 mm of measured link-class excess,
which is the corroboration that the OBB slop IS that excess.

WHY THIS REVERSES A RECORDED DECISION. `collision-hull-narrow-phase.md` §5.2
excluded exactly these three because #159 found they "hold zero of the 72
census stops". That census measured START STATES, and it was right about
them. The #204 battery measured the CARRY phase, which is where 71% of stops
happen, and there link6 dominates. The doc row is struck and rewritten rather
than deleted, so the reasoning that was correct-for-its-data stays visible.

SAFETY. This makes the envelope TIGHTER, so it is not conservative-by-default
and needs its containment argued rather than assumed:

* containment is definitional, not fitted -- the DOP is the intersection of 26
  tangent halfspaces `u.x <= h_mesh(u)` and the hull is `conv(mesh vertices)`;
* `generate_tight_geometry.py check` reports **mesh-outside-DOP
  +0.000000000 mm** on all seven links of both manifests;
* hulls are 152/152/102 vertices, the same class as the shipped
  link2/link5/link7, well inside `kMaxTightHullVertices` -- unlike link1's
  1588, which stays stage-1 only;
* both manifests are `hal.real: null`, so the sim-margin (0.0 m) benchmark
  applies, where the staged path measures 1.04-1.09x FASTER than the box path.
  The 0.02 m real-HAL table would need re-measuring before either manifest
  gains a real HAL.

`panda_mobile_vslam` gets the same blocks: the two manifests' arm geometry is
contract-tested identical (`test_panda_mobile_arms_use_matching_mesh_enclosing_obbs`).

STILL OWED, and not faked here: a safety-WG reviewer and a hazard-log entry,
which `test_the_manifest_actually_declares_tight_geometry` names as the price
of widening this set. This commit provides the measurement half of that.

How tested: `test_collision_tight_geometry.py` (15), `test_collision_params.py`
(9), `test_collision_geometry_contracts.py`, `test_all_manifests_validate.py`
(126) all pass, with the declared set, stage-2 count, CSR vertex counts, hull
overhangs and box/DOP excesses re-pinned to measured values. The DOP-excess
method was validated by reproducing all four previously pinned links exactly
(53.27->25.69, 46.83->23.01, 45.20->18.99, 28.25->12.97).
`test_kernel_fridge_layout_pin_start_state.py` passes 5/5 against a real
kitchen and a real kernel, so the layout-47 pin, the layout-30 ordering, the
genuinely-colliding pose and the all-occupied control all still hold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg
Signed-off-by: Adrian <adrianllopart@gmail.com>
(cherry picked from commit e8a1e72cb7b8b0519a0d06b932a7f16d3eebec22)

---------

Signed-off-by: Adrian <adrianllopart@gmail.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Adrian <adrian@qualiastudios.dev>
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