Skip to content

Navigation Menu

Sign in
Sign up

fix(sim,hal,evidence): repair the four instruments the collision programme measures with, and measure the resolution lever end to end - #251

Closed
AdrianLlopart wants to merge 48 commits into
master from
experiment/voxel-resolution-15mm
Closed

fix(sim,hal,evidence): repair the four instruments the collision programme measures with, and measure the resolution lever end to end #251
AdrianLlopart wants to merge 48 commits into
master from
experiment/voxel-resolution-15mm

Conversation

@AdrianLlopart

@AdrianLlopart AdrianLlopart commented Sep 8, 2026

Copy link
×ばつ**: | term | measured | | --- | --- | | kernel consuming a 15 mm grid | p99 **0.825 ms** (estimated 26.7 ms) | | bridge producing one | **1.60 ms** vs a 100 ms period | | the wire | **+15 ms median / +50–60 ms p99** staleness | | carry-phase arm speed | 0.051 m/s median, 0.265 m/s max | Net positive in three of four corners, and free for the whole start-state class (43 % of stops, arm speed exactly 0). **No manifest edit here** — shipping 15 mm is a conservatism *reduction* and needs its own WG ruling. `_world_voxel_max_cells` now derives the cap from the resolution. That is a **fail-open fix**: a hand-kept 614 125 against a 15 mm grid's 2 803 221 cells makes the kernel reject every grid it is sent, which reads as "no world". ## How tested ``` 109 passed tests/unit/{test_stop_excess,test_adr0101_recovery,test_validation_matrix,test_sim_estop_voxel_backing}.py 5334 passed, 75 skipped tests/unit (full tier) 12 passed openral_octomap_bridge test_octree_to_grid (incl. the new resolution sweep) 1 passed tests/sim/.../test_kernel_fridge_layout_pin_start_state.py -k latency ``` `ruff` clean, `mypy --strict` clean over `tools/` (65 files) and `openral_cli`, `tools/refresh_methods_linenos.py --check` exit 0. Every behavioural fix is mutation-checked: restoring `+ link_link` fails the adjudicator test, forcing the backing sweep off fails the coincident-geometry test, flipping `>` to `>=` fails the contact-boundary test, and pointing the fixture reader at `nearest_link_link_pairs` fails the attribution test. ## Checklist - [x] Conventional commit title; description has "What changed", "Why", "How tested" - [x] Schemas: unchanged - [x] Layer boundary crossed → none; no `packages/openral_safety/` or `cpp/` change - [x] Tests: unit + sim; no new mocks/stubs (CLAUDE.md §1.11 — every fixture is a recorded artifact with provenance in `SOURCE.txt`) - [x] `docs/methods/` updated for `stop_excess`, `adr0101_recovery`, `stop_ee_speed`, `voxel_transport_probe`; `--check` clean - [x] Docs updated in the same PR (`PLAN.md`, `collision-validation-evidence.md`, standing caveats 10 and 11) - [x] Pre-existing errors in separate prior `fix(...)` commits (`e8e0731` batches the hull-overhang query) - [x] Repo state map — no module added, renamed, removed, or status-flipped - [x] `just lint` passes; `mypy --strict` clean; no new bare `type: ignore`; no new `try/except: pass` - [x] No new safety-disabling flag - [x] Performance budgets met; no PII 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg" data-view-component="true"> Copy Markdown
Contributor

What

Slice A of the three-way split recorded in 0c2202d: the instrument repairs, the evidence producers, and the resolution-lever measurements. Touches no manifest, and neither packages/openral_safety/ nor cpp/ — verified by an empty git diff --stat against both — so CLAUDE.md §1.4 applies and §3 does not. Slice B (feat/216-tight-geometry-link3-4-6, the three manifest envelopes) stays safety-WG gated on hazard-log Entry 026; slice C (panda_link1's envelope) was withdrawn and lands nowhere.

Why

master has carried two measurement-corrupting defects since 2026年09月05日, and every validation round taken on it since is unusable.

  1. The harness could not see the graph it launched. _launch_env never applied the sim DDS scope (fix(sim): deploy sim sets no DDS scope — a sim round joined a live robot's ROS graph #227 /fix(sim): keep a simulation and a real robot off each other's ROS graph #231 confined the deploy to domain 77; the harness polled domain 0), and ros2 action list --no-daemon cannot discover an advertised action at all. Every scene of every round reported harness-error beside a healthy graph.
  2. The adjudicator stamped every stop real-contact. fix(evidence): probe the link-vs-link pair a self stop names, and budget it #220 folded the new link-vs-link probe into nearest_any, whose first rule is "any probed pair ≤ 0 m → real-contact". Adjacent robot links overlap permanently and are ACM-permitted, so that rule became vacuously true. Three of four stops in the day's battery flip to within-quantization on re-derivation. The error runs one way — it manufactures real contacts, never clears one — so nothing was wrongly passed as safe.

Two more, both diagnostics-only: the backing probe could not see a slab coincident with its visual shell (RoboCasa builds every counter top that way), nor world geometry in a cell whose only solid backing is the robot.

Nothing downstream can be measured until this lands. That is the whole argument for splitting rather than waiting for the WG on one PR.

What it establishes

With repaired instruments, 71 % of stops are of a physically clear robot (5 of 7; +0.67 ... +24.86 mm true clearance) — reproducing the #204 battery's 85-of-91 on an independent battery, a different commit, and a fixed adjudicator. The headline number has not moved.

It also measures the 25 → 15 mm resolution lever end to end, after correcting a strike that was mine and wrong by ×ばつ:

term measured
kernel consuming a 15 mm grid p99 0.825 ms (estimated 26.7 ms)
bridge producing one 1.60 ms vs a 100 ms period
the wire +15 ms median / +50–60 ms p99 staleness
carry-phase arm speed 0.051 m/s median, 0.265 m/s max

Net positive in three of four corners, and free for the whole start-state class (43 % of stops, arm speed exactly 0). No manifest edit here — shipping 15 mm is a conservatism reduction and needs its own WG ruling.

_world_voxel_max_cells now derives the cap from the resolution. That is a fail-open fix: a hand-kept 614 125 against a 15 mm grid's 2 803 221 cells makes the kernel reject every grid it is sent, which reads as "no world".

How tested

109 passed tests/unit/{test_stop_excess,test_adr0101_recovery,test_validation_matrix,test_sim_estop_voxel_backing}.py
5334 passed, 75 skipped tests/unit (full tier)
12 passed openral_octomap_bridge test_octree_to_grid (incl. the new resolution sweep)
1 passed tests/sim/.../test_kernel_fridge_layout_pin_start_state.py -k latency

ruff clean, mypy --strict clean over tools/ (65 files) and openral_cli, tools/refresh_methods_linenos.py --check exit 0.

Every behavioural fix is mutation-checked: restoring + link_link fails the adjudicator test, forcing the backing sweep off fails the coincident-geometry test, flipping > to >= fails the contact-boundary test, and pointing the fixture reader at nearest_link_link_pairs fails the attribution test.

Checklist

  • Conventional commit title; description has "What changed", "Why", "How tested"
  • Schemas: unchanged
  • Layer boundary crossed → none; no packages/openral_safety/ or cpp/ change
  • Tests: unit + sim; no new mocks/stubs (CLAUDE.md §1.11 — every fixture is a recorded artifact with provenance in SOURCE.txt)
  • docs/methods/ updated for stop_excess, adr0101_recovery, stop_ee_speed, voxel_transport_probe; --check clean
  • Docs updated in the same PR (PLAN.md, collision-validation-evidence.md, standing caveats 10 and 11)
  • Pre-existing errors in separate prior fix(...) commits (e8e0731 batches the hull-overhang query)
  • Repo state map — no module added, renamed, removed, or status-flipped
  • just lint passes; mypy --strict clean; no new bare type: ignore; no new try/except: pass
  • No new safety-disabling flag
  • Performance budgets met; no PII

🤖 Generated with Claude Code

https://claude.ai/code/session_01MTE4eEfxW8FvvBnPZL6otg

AdrianLlopart and others added 30 commits September 7, 2026 14:39
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 17fc145)
...asurement
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 0da3e4d)
`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 36a9d2b)
`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 e0e2e0f)
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 6406ac1)
...he 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 c7bd2c7)
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 2997c01)
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 f42d3d3)
...vidence
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 51c8e2e)
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 7735608)
Adrian @claude
×ばつ 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)" data-pjax="true" href="/index.cgi/contrast/https://github.com/OpenRAL/openral/pull/251/commits/37550d610a157a490a957e921b4fa32bfb2e455c">docs(plan): reconcile the checklist with the measured levers, and rec...
...ord 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/res3)` window make halving the cell an ×ばつ
 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 e7956f7)
...ator 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 60dcb2f)
...t 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 a0f5f64)
...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 6bed917)
...eading
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 b76aa0e)
...-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 483a82e)
...ear 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 9676c93)
...ever 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 c1e2203)
...he 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 551b7c5)
...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 10a3469)
...t 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 1d116ae)
...aust 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 e8e0731)
...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>
...ust 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 7fcc716)
... 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 f4a670f)
...t 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 b55f90a)
...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 7d90a81)
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 d63d6ad)
...s 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 448818c)
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 115e7b2)
Adrian and others added 17 commits September 7, 2026 14:41
...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 49b4197)
...s 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>
`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>
...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/res3)` 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>
CLAUDE.md §1.13 requires every public symbol to have a `docs/methods/` entry.
`tools/validation_matrix.py:collision_scale_env` has had none since it landed
with the #188 graded band. Pre-existing, so it gets its own commit ahead of the
change that adds its sibling (§1.15).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
... cap from it
PLAN.md §5's resolution lever needs a round run at 15 mm to answer whether a
finer grid clears stops. Two things were in the way.
**The resolution was hardcoded.** `_octomap_resolution` now honours
`OPENRAL_OCTOMAP_RESOLUTION_M` in `[0.001, 0.5]`, falling back to the shipped
0.025/0.05 on anything else — a typo must not become a silently empty grid,
which is what `build_lattice`'s refusal and `kMaxCellsPerAxis` both produce.
Same mechanism as the #188 graded band's `OPENRAL_COLLISION_SCALE_*`, and
`validation_matrix.py` records it the same way, because
`assert_no_safety_overrides` inspects argv and cannot see an env var.
Recorded for the opposite reason to the band's, and this is the part worth
stating plainly: **a finer grid is LESS conservative.** The cell half-diagonal
is the kernel's quantisation term, so shrinking it makes the kernel stop later
and nearer. Nothing here changes a default, and nothing should ship at another
value without the measurement and a safety-WG ruling.
**The cap was a hand-kept derived constant.** `world_voxel_max_cells` was the
literal `614125` with a comment explaining it was 85^3 — the coverage ball at
25 mm plus the lattice snap's one cell per axis. It is now computed by
`_world_voxel_max_cells` from the same two inputs. The failure mode when that
constant drifts is the wrong direction: a kernel reserving 614 125 cells rejects
every 2 803 221-cell grid it is sent, which reads as an empty world and is a
fail-**open** on the world check. Pinned at 25 mm -> exactly 614 125, so this
refactor provably does not move the shipped graph.
How tested: `tests/unit/test_sim_e2e_voxel_resolution.py` (5 cases, skipping
cleanly where the launch file's own ROS imports are absent) pins the 25 mm
constant, the 141^3 growth at 15 mm, and both directions of the override; two
new cases in `test_validation_matrix.py` pin that an honoured override is
recorded and an ignored one is not. `just lint` clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Adrian <adrian@qualiastudios.dev>
PLAN.md §5 un-struck the 25 -> 15 mm lever after measuring the kernel consuming
a finer grid, and named the other half unmeasured: the bridge rebuilds the grid
on every publish, and since the published lattice IS the octree's, a finer
kernel grid means a finer tree.
Measured on the real rasterizer over the existing kitchen octree, which is now
parameterised by resolution rather than pinned to 25 mm:
 25 mm 512 000 cells, 825 occupied, 0.88 ms
 20 mm 1 000 000 cells, 1 380 occupied, 1.29 ms
 15 mm 2 406 104 cells, 3 936 occupied, 1.60 ms
 12.5 mm refused
The producer is not the obstacle: 1.60 ms at 15 mm against a 100 ms publish
period, and nearly flat — cells x4.7 for time x1.8, because the marking loop
iterates occupied leaves, which are a surface, while only the dense buffer's
allocation scales with volume.
12.5 mm is refused by `kMaxCells = 4e6` at 4 096 000 cells. Fail-closed is
correct; it also means 12.5 mm needs that guard raised, which is a decision
rather than a manifest edit. Pinned so the boundary is recorded rather than
discovered.
Also corrects the un-strike's cell count, which was mine. It claimed 15 mm needs
376 680 cells against the shipped 614 125 cap and dismissed §5's 2.8 M as a
whole-kitchen grid. Both halves were wrong: the coverage ball is sized by the
arm's 1016 mm reach and ships at 1.05 m, so 15 mm needs 141^3 = 2 803 221 cells
and §5's original figure was right. The cap consequence is real — a kernel still
reserving 614 125 rejects every grid, reading as "no world", a fail-open on the
world check — which is why `_world_voxel_max_cells` now derives the cap from the
resolution.
Leaves message size as the only unquantified term on this lever: 2.8 MB per
publish at 10 Hz is 28 MB/s over DDS against 6 MB/s today, and neither the
transport cost nor its effect on the kernel deadline has been measured.
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>
1.60 ms to rasterize a 15 mm grid against a 100 ms publish period, from 0.88 ms
at 25 mm — nearly flat, because the marking loop iterates occupied leaves, a
surface, while only the dense buffer scales with volume. 12.5 mm is refused by
the bridge's own 4e6-cell allocation guard and needs it raised; 15 mm does not.
Also corrects this item's own cell count, which I got wrong when un-striking it:
15 mm needs 141^3 = 2 803 221 cells, not 376 680, because the coverage ball is
sized by the arm's 1016 mm reach. §5's original 2.8 M figure was right, the cap
consequence is real, and it is why the cap is now derived from the resolution
rather than hand-kept.
Message size is now the only unquantified term on the lever: 2.8 MB per publish
at 10 Hz is 28 MB/s over DDS against 6 MB/s today.
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>
The last unquantified term on the 25 -> 15 mm lever. Both compute halves are
measured and cheap — kernel p99 0.825 ms, rasterize 1.60 ms — and neither
touches the message, which is a dense uint8[] going from 0.61 MB to 2.80 MB per
publish at 10 Hz.
`tools/voxel_transport_probe.py`: two processes over real DDS, real
openral_msgs, at the deployed 10 Hz, under the kernel's own QoS for
/openral/world_voxels, grid sizes from the shipped 1.05 m coverage radius.
Separate processes deliberately — intra-process publish/subscribe short-circuits
the transport being measured.
 25 mm 0.61 MB p50 ~14 ms p99 19-23 ms
 20 mm 1.19 MB p50 ~22 ms p99 36-43 ms
 15 mm 2.80 MB p50 ~29 ms p99 68-83 ms
Nothing is dropped and the rate holds: RELIABLE delivered every message at every
size and the achieved rate is ~8.6-8.9 Hz throughout, the shortfall being the
probe's own sleep loop. The expected failure mode is not there.
What is there is staleness. That latency is the age of the world when the kernel
reads it, and it triples at 15 mm: +15 ms median, +50-60 ms p99. Age is also
millimetres, so it is directly comparable to the 8.66 mm of quantisation the
lever buys — break-even end-effector speed is 0.58 m/s at the median and
0.16 m/s at p99, both inside the arm's kinematic range.
This does NOT re-strike the lever and must not be read as doing so. It converts
a free win into a trade, settled by one unmeasured number: actual carry-phase
end-effector speed. At 0.1 m/s the lever is still worth pulling; at 0.5 m/s it
is not. Measuring it from the battery's recorded joint states is the next step,
and on this lever every number reasoned about rather than measured has been
wrong.
Fast-DDS, localhost, one shared laptop — the probe reports its RMW because the
result does not carry across implementations.
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>
The wire measurement turned the 25 -> 15 mm lever into a trade: +8.66 mm of
static quantisation against +15 ms median / +50-60 ms p99 of map age. Age is
millimetres too, so the trade is settled by how fast the arm actually is when the
kernel stops it.
`tools/stop_ee_speed.py` reads each round's own `robot_joint_state` from its
ground-truth snapshot and pushes it through the real Panda body Jacobian at
link7, the body the payload attaches to:
 carry phase (attached_payload) n=5 median 0.051 m/s max 0.265 m/s
 start state (robot_world) n=7 median 0.000 max 0.000
The mobile base contributes at most 0.0013 m/s, so arm speed is world speed here.
Start-state stops are free: all seven are at exactly zero, so they take the full
8.66 mm with no offset. That is 43% of the battery's stops.
Carry-phase is net positive in three of four corners — +7.89 mm at the median
stop and median staleness, +4.68 and +5.84 mm at the single-worst corners, and
-5.93 mm only when the fastest observed stop meets a p99-latency grid.
So the lever is worth pulling, and this is the first time that has been said
about it on evidence rather than an estimate: all three cost terms are now
measured and the arm is slow enough that the last one does not eat the gain.
Two honest limits recorded with it: n=5 carry-phase stops is thin, and these are
the speeds THIS policy produces — a policy carrying at 0.3 m/s would move the p99
corner from marginal to routine.
The joint matcher raises rather than returning zero when nothing matches; the
first cut of this tool matched no joints and reported a perfectly stationary arm,
which is the one failure this measurement must not have.
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>
The wire term is the one that bites: publish->receive latency triples from p99
19-23 ms at 25 mm to 68-83 ms at 15 mm, with nothing dropped and the rate held.
That is map staleness, and staleness is millimetres.
Settled by measuring the arm rather than assuming it. Carry-phase stops are
0.051 m/s median and 0.265 m/s max; start-state stops are exactly zero, because
the arm has not moved yet. Net is positive in three of four corners, and the
whole start-state class — 43% of stops — takes the full 8.66 mm with no
staleness cost at all.
All three cost terms are now measured (kernel 0.825 ms, rasterize 1.60 ms, wire
+15/+55 ms), which is what makes this the first evidence-based verdict on a
lever that has been struck and un-struck three times on estimates.
Carries two caveats into the change: n=5 carry-phase stops is thin, and these are
this policy's speeds — a faster one moves the p99 corner from marginal to
routine.
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>
...run yet
The item read as merely unstarted. Two things make it precise.
First, scope: the offline 94% rests on certified mesh truth, which no
backing-probe defect ever touched, so it stands. What needs re-deriving is the
live-map half — the ADR's "cells no real body explains" premise is a
backing-CLASS claim, and both backing fixes changed how that class is computed.
Second, why existing rounds cannot serve. Only rounds at or after 448818c carry
both the coincident-geometry sweep and the robot-only-backing sweep. Exactly two
exist and both are robot_world start-state stops. No post-fix round on disk has
a payload-vs-voxel stop, which is the only class the 94% is about. Records what
a fresh battery would need to look like.
And why it was not run today: q-laptop at load 19.1 with a live deploy graph and
another session's 2.1 GB on the GPU. The GPU would fit; CPU contention at that
load pushes rollouts into the 420 s deadline and manufactures deadline-no-grasp,
which corrupts the measurement rather than producing it. Waiting is the correct
call, not --force-shared-gpu.
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>
...atteries by it
Counted across all 26 rounds by where the round actually ends: utensil reaches
the carry phase in 27% of rounds (6/15 never grasp, 5/15 stop at reset), fridge
in 18% (7/11 never grasp). The carry phase holds 71% of stops and every stop
ADR-0101 targets, so only one round in four says anything about the class the
programme is about.
This is a measurement-cost finding rather than a collision one, and it explains
why n has stayed thin through every battery on this page. It also sizes them:
utensil yields 4 payload stops per 15 rounds, so ADR-0101's live-map
re-derivation needs about 38 rounds and 6 hours of wall clock. Budgeting by round
count rather than by yield is how a battery ends up underpowered, which has
happened here before.
Two consequences that are not collision work. The start-state class is cheaper to
measure than the carry class, so anything testable there should be measured there
first. And deadline-no-grasp is the largest lever on measurement throughput while
being a policy property — 40-64% of rounds spend seven minutes each to say
nothing about collision, and no collision change will ever be observed in them.
Also marks #217 done: it closed COMPLETED on 2026年09月06日 with the #204 A/B null at
0.85 power (5.0% vs 10.0%, Fisher p = 0.491), before the item was written here.
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>
The passage naming the consumer measurement's limits listed two terms it could
not see — the bridge producing a grid, and the message crossing DDS. Both were
measured on 2026年09月08日, so the plan contradicted its own checklist.
Replaces them with the results (rasterize 1.60 ms at 15 mm, wire p99 19-23 ->
68-83 ms, staleness settled against measured arm speed) and keeps the two limits
that genuinely stand: one pose in one layout, and n=5 carry-phase stops from this
policy.
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>
...s exact
Launched utensil-weighted at seeds 10-45. The XR-1 sidecar crashed at boot on
every round with torch.OutOfMemoryError, producing 20-second policy-free rounds;
both were deleted rather than kept, since a policy-free round measures nothing.
From the sidecar's own traceback: GPU capacity 7.53 GiB with 24 MiB free.
Another project's job (workspace/RAL-1) holds 2.41 GiB, the XR-1 checkpoint needs
~3.66 GiB, and MuJoCo's EGL context plus the deploy graph take the rest.
--force-shared-gpu does not create memory.
Records the reversal too, because it should not happen a third time: the first
read of this correctly said wait, the second overturned it on free-VRAM
arithmetic that omitted the renderer and the graph, and the measurement proved
the first read right. The battery needs the other GPU job stopped, or spark.
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>
... clearances halve
Thirty-six utensil rounds on spark at 448818c, the sha carrying both
backing-probe fixes — the condition the re-derivation required, and one no
q-laptop round had ever met.
The rate survives: 24/28 (86%) against the offline 48/51 (94%) is Fisher
two-sided p = 0.237, no evidence they differ. ADR-0101's headline stands up to
its own re-derivation.
The clearance distribution does not, and that is the finding. Median recovered
clearance is 8.56 mm against the offline 16.2 mm — half. The mechanism would
suppress cells whose true surface is twice as close as the offline analysis
implied, so its own modelling error eats a correspondingly larger share of the
budget. The minimum moved the other way (1.84 mm against 0.1 mm). Two of the four
that correctly still stop are inside a tenth of a millimetre.
The stop decomposition now rests on n=28 rather than n=4: payload -9.93 mm beyond
the voxel term (no geometry headroom), link +4.28 mm. The exhaustion conclusion
holds harder than when it was drawn.
Also corrects yesterday's yield entry, which was mine and wrong. It measured
18-27% carry-phase yield and 40-64% deadline-no-grasp across 26 q-laptop rounds
and read that as a property of the policy. Spark produced ZERO deadline-no-grasp
in 36 rounds and 78% payload stops. Same policy, different host: that figure was
a 420 s deadline meeting a machine at load 19 with a shared GPU. It follows that
deadline-no-grasp should be read as a load symptom first, and that the ceiling
battery's policy-free exclusions deserve re-reading in the same light.
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>
...efuted
The re-derivation ran on spark at 448818c, the sha with both backing-probe
fixes: 24 of 28 payload stops recovered (86%) against the offline 48/51 (94%),
Fisher p = 0.237. The rate survives.
The clearance distribution does not: median 8.56 mm against 16.2 mm. The WG
should rule on 8.56 mm, which argues harder for the ADR's own suppression-off
first landing. The decomposition also moves from n=4 to n=28 and the exhaustion
conclusion holds harder.
And the carry-phase yield item is refuted by the same battery. It measured
18-27% yield and 40-64% deadline-no-grasp on q-laptop and read that as a policy
property; spark produced zero deadline-no-grasp in 36 rounds. There is no
scene-selection problem to fix. What stands is that deadline-no-grasp is a load
symptom first.
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>

Copy link
Copy Markdown
Contributor Author

The ADR-0101 re-derivation this PR's instruments made possible has now run

Thirty-six utensil rounds on spark at 448818c4 — the sha carrying both backing-probe fixes, a condition no q-laptop round had ever met, which is why this was blocked until the repairs in this PR existed.

offline (certified truth) live map, re-derived
payload-vs-voxel_ stops 51 28
recovered 48 (94 %) 24 (86 %)
median recovered clearance 16.2 mm 8.56 mm
minimum recovered clearance 0.1 mm 1.84 mm

The rate survives — Fisher two-sided p = 0.237, no evidence the two differ. The clearance distribution does not: the median recovered stop sits at half the real air the offline analysis implied, which shrinks the margin ADR-0101's own modelling error has to fit inside. That is a number the safety-WG should have before ruling.

Two side effects that bear on this PR directly:

  • The stop decomposition now rests on n = 28, not n = 4 — payload −9.93 mm beyond the voxel term, link +4.28 mm. The exhaustion conclusion in PLAN.md §5 holds harder than when it was drawn.
  • The carry-phase yield entry in this PR is wrong, and I have corrected it in-branch. It measured 18–27 % yield and 40–64 % deadline-no-grasp across 26 q-laptop rounds and read that as a property of the policy. Spark produced zero deadline-no-grasp in 36 rounds and 78 % payload stops. Same policy, different host — it was a 420 s deadline meeting a machine at load 19 with a shared GPU. deadline-no-grasp should be read as a load symptom first, which also means the ceiling battery's own policy-free exclusions deserve re-reading in that light.

Nothing here changes the diff under review; both corrections are documentation commits on this branch.

...o rule on
Neither item was actionable without a decision record, and neither had one.
management#36 supplies both: hazard-log Entry 027 for the 25 -> 15 mm resolution
reduction, and ADR-0101 updated with the live-map figures its premise rests on.
Entry 027 names the asymmetry the ruling turns on — a deterministic 8.66 mm gain
against a probabilistic staleness cost — and states what does not exist: no live
A/B of completion rate at 15 mm, which the WG may reasonably require first.
ADR-0101 now carries 86% on n=28 (Fisher p=0.237 against the offline 94%, so the
rate holds) alongside the number that did move: median recovered clearance 8.56 mm
against 16.2 mm. The WG should rule on 8.56 mm.
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>

Copy link
Copy Markdown
Contributor Author

Superseded by #252.

This branch was cut before #238 and #239 landed, and had drifted into a 45-commit / 25-file diff that mostly duplicates work already merged through review. Rather than force-push a rebase over it, #252 rebuilds the same work on current master as one commit touching 10 files.

Nothing is lost. I compared every file against master (AST-compared where only prose differed) and kept master's reviewed version wherever the two agreed functionally. What was genuinely new here — the resolution-lever measurements, voxel_transport_probe.py, stop_ee_speed.py, the octree sweep test, octomap_resolution_env, _world_voxel_max_cells, and the PLAN/evidence entries — all carried over to #252 and is verified present there.

The pre-rebase tip is preserved locally at 556cb02 if anything needs recovering.

AdrianLlopart deleted the experiment/voxel-resolution-15mm branch September 10, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /