Skip to content

fix(#781): give static placement one ModelBounds value instead of loose lift floats - #828

Merged
djhenry merged 7 commits into
mainfrom
fix/781-model-bounds
Aug 1, 2026
Merged

fix(#781): give static placement one ModelBounds value instead of loose lift floats#828
djhenry merged 7 commits into
mainfrom
fix/781-model-bounds

Conversation

@djhenry

@djhenry djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Closes #781.

#781 was offered as a proposal to be tested, not a verified plan, and said "close this as wontfix" is a legitimate outcome. I tested it against the code first. The short version: the proposal as literally written buys about half of what it looks like, and the missing half is cheap, so this PR builds both. Every figure below is measured on this branch (9aa2a5b, based on 41cca4e), with the mutation applied at the call site unless a row says otherwise.

What #781 asked for, and what measuring it showed

The proposal: static_placement takes a ModelBounds parameter instead of a loose y_bottom float, "so there is no lift argument at the call site to do arithmetic in".

That closes #773's channel 2 (static_placement(archetype, model.y_bottom + model.y_extent, …)) at the type level. It does not touch #773's channel 1visual_scale = 2.0 * model.y_extent * p.mesh_scale handed to entity_model_matrix_heading — which is a plain f32 argument at the same four call sites, and which is the shape the original #768 bug actually had. Leaving it is the difference between "no lift argument to do arithmetic in" and "one fewer lift argument to do arithmetic in".

So this PR adds a second, smaller change that closes it, and both are reported separately below so a reviewer can judge them separately.

What changed

  1. GpuStaticModel's four bounds fields become one value. y_bottom/y_extent/x_center/z_centerpub bounds: models::ModelBounds (crates/eqoxide-renderer/src/gpu.rs:141-153). ModelBounds is defined at crates/eqoxide-renderer/src/models.rs:980-1003. static_placement(archetype: &str, bounds: &ModelBounds, floating: bool) (models.rs:1109-1119) reads y_bottom and [x_center, z_center] off it. Construction sites, both updated: renderer.rs:1032 and src/bin/render_model.rs:851.
  2. New camera::entity_model_matrix_static(pos, heading_deg, &StaticPlacement, correction) (crates/eqoxide-renderer/src/camera.rs:114-140). It binds visual_scale = 0.0 and y_up = true internally and forwards the placement's own mesh_scale/center_xz/y_bottom. The four pass.rs static sites call it instead of entity_model_matrix_heading (pass.rs:1515/:1517, :1672/:1674, :2079/:2080, :2113/:2114).

The four call sites now read, in full:

let p = static_placement(archetype, &model.bounds, b.floating);
let mat = entity_model_matrix_static(b.pos, b.heading, &p, archetype_correction(archetype));

src/bin/render_model.rs is touched only to re-spell field reads (s.model.y_extents.model.bounds.y_extent, 9 field reads across 6 lines: :1268, :1271, :1274, :1321, :1475, :1476). Its arithmetic is unchanged — it still hand-writes the pre-#768 formula, which is recorded at models.rs:923-941 and is out of scope here as it was for #756/#768.

Evasion shapes: which are removed, which are left open

Both routes #773 measured green are now compile errors in the spelling that was measured. Neither is unrepresentable, and nothing in this PR or in any tracked file it touches says otherwise.

shape, at the entity call site on 41cca4e on this branch
channel 1visual_scale = 2.0 * model.y_extent * p.mesh_scale compiles; 261 passed / 1 failed / 12 ignored, the failure being the pin error[E0061]: this function takes 4 arguments but 5 arguments were supplied
channel 2model.y_bottom + model.y_extent as y_bottom compiles; 261 passed / 1 failed / 12 ignored, the failure being the pin error[E0308]: mismatched types (expected &ModelBounds, found f32)
struct literal&ModelBounds { y_bottom: model.bounds.y_bottom + model.bounds.y_extent, ..model.bounds } n/a (type did not exist) compiles. 261 passed / 1 failed / 12 ignored — the failure is the pin's REVIEWED_ARGS assert and nothing else
call the general matrix fn instead, replacing the static call n/a compiles. RED on the pin's entity_model_matrix_static count assert (found 3, left 3 right 4)
call the general matrix fn in addition (counts stay 4) n/a compiles. RED on the pin's new entity_model_matrix_heading guard, printing the offending argument list

So the honest summary is: two shapes moved from type-checks, only a text pin catches it to does not type-check, and one new, louder-to-write shape sits in the first category. The bad state is still representable. The unforgeable newtype that would fix that stays declined for the reason #773's reviewer found and which I did not re-litigate: tests/floating_placement.rs is an integration test, links eqoxide-renderer as an external crate, and cannot see a #[cfg(test)] mint — so an "only the loader can mint it" type forces either a pub mint pass.rs can call too, or a feature flag. That reasoning is recorded on ModelBounds itself (models.rs:987-992).

The one thing that got strictly stronger

Before this PR the visual_scale = 0.0 the static arm depends on was spelled at four pass.rs call sites that the device-free tests cannot reach — they build the matrix with their own literal 0.0, so only the source-text pin graded the shipped one. It now lives inside entity_model_matrix_static, which those tests do call.

Measured, because moving a constant into a function body is exactly the "you traded a pinned literal for an ungraded one" trap: replacing that 0.0 with 2.0 * p.y_bottom * p.mesh_scale (camera.rs, in the body — labelled as a body mutation deliberately, since the point is whether the body is graded) turns a_grounded_static_model_is_drawn_with_its_lowest_vertex_on_the_stored_z RED, crate --no-fail-fast 261 passed / 1 failed / 12 ignored. That is a behavioural failure where the same corruption previously produced only a source-text failure.

Mutation results, by test name

Crate = cargo test -p eqoxide-renderer --locked --no-fail-fast. Green baseline for every row: 14 headers = 14 result lines, 262 passed / 0 failed / 12 ignored (identical on 41cca4e and on this branch — no test added or removed).

# mutation where result
ctrl-1 channel 1, on 41cca4e call site RED 261/1/12 — every_static_placement_in_pass_rs_is_written_exactly_as_reviewed (:578)
ctrl-2 channel 2, on 41cca4e call site RED 261/1/12 — same test (:552)
m1 channel 1, this branch call site does not compile, error[E0061]
m2 channel 2, this branch call site does not compile, error[E0308]
m3 ModelBounds struct literal restoring the over-lift call site RED 261/1/12 — every_static_placement_in_pass_rs_is_written_exactly_as_reviewed (REVIEWED_ARGS, :595)
m4 replace the static matrix call with entity_model_matrix_heading + over-lift call site RED 261/1/12 — same test, count assert (:617, found 3)
m4b add an entity_model_matrix_heading over-lift call, leaving all counts at 4 call site RED 261/1/12 — same test, the new heading guard (:633 on the tree that run was made against; the guard is now :648 after the widening below). This is the reach control for that guard: m4 fired on the count assert first, so without m4b the guard would have been an unexecuted assert
m5 static_placement returns y_bottom + y_extent helper body RED 260/2/12 — a_grounded_static_model_is_drawn_with_its_lowest_vertex_on_the_stored_z and the_grounded_arm_lifts_a_boat_by_its_y_bottom_not_by_its_whole_height. Behavioural, not the pin
m6 entity_model_matrix_static passes a non-zero visual_scale helper body RED 261/1/12 — a_grounded_static_model_is_drawn_with_its_lowest_vertex_on_the_stored_z. Behavioural, not the pin
m7 entity_model_matrix_static ( (space before paren) call site RED 261/1/12 — the pin's anti-escape loop (:587). Reach control for the new for name in […] guard
m8 let q = &p; + an additive entity_model_matrix_heading(…, q.center_xz, true, q.y_bottom, …) at pass.rs:2113 (counts stay 4 and 4) call site RED 261/1/12 — same test, the widened heading guard (:648). Reach control for the widening: the earlier p.-pinned form does not occur anywhere in that argument list, so it stayed green

Every mutation was applied by script from an md5-verified pristine copy, with an occurrence-count assert before the write, and reverted from that copy with md5 re-verified and touched afterwards. The worktree was confirmed at the committed tree after each.

Workspace figures

cargo test --workspace --locked --no-fail-fast, streams captured separately, on this branch (which is 41cca4e + this commit, so no merge was needed):

  • Finished \test` profile [unoptimized + debuginfo] target(s) in 5m 14s`
  • 55 running [0-9]+ tests? headers = 55 test result: lines (equality is the lost-binary check; the regex matches the singular running 1 test too)
  • 0 non-canonical result lines
  • 14 targets at 0 passed; 0 failed; 0 ignored (field-anchored)
  • 1810 passed + 0 failed + 47 ignored + 0 measured + 0 filtered out = 1857 = the header sum

Delta vs the 41cca4e baseline (55 = 55, 0, 14, 1810 + 0 + 47 + 0 = 1857): zero, in every field. Nothing to reconcile by name — no test was added, removed, renamed or re-ignored. src/bin/render_model.rs is confirmed compiled and run by the workspace job (Running unittests src/bin/render_model.rs, 0 tests).

scripts/check-no-local-detail.sh OK. scripts/check-wrapped-literals.py OK (172 tracked .rs files) — it earned its keep here, catching two assert messages whose line continuations I had mangled.

Corrections to tracked files

  • models.rs:135 cited src/bin/render_model.rs:1097, and the StaticPlacement doc block cited :1098. On 41cca4e those two lines are a }; and a blank line — the true lines were 1099 and 1100, so both citations were already two off before this PR. All seven render_model.rs line citations in that doc block are re-measured on this tree (:1101, :1268, :1274, :814, :1102, :1294, :1473), and the pre-existing drift is stated in the comment rather than silently corrected.
  • StaticPlacement::y_bottom's doc said "there are TWO ways back to it". It now names both, says which error each now produces, and names the two shapes that remain — with "the bad state is still REPRESENTABLE and this doc does not claim otherwise" in the text.
  • gpu.rs's y_extent doc cited src/bin/render_model.rs:1266; the citation is dropped rather than re-pinned, since that line moves every time the viewer is touched and the doc's claim does not depend on it.

NOT verified, and not claimed

Self-review found a hole in my own guard, and it is fixed here

The entity_model_matrix_heading guard as first written asserted no argument list contains p.y_bottom or p.center_xz — it pinned the binding name. let q = &p; then entity_model_matrix_heading(…, q.center_xz, true, q.y_bottom, …) evades that while doing precisely the thing the guard exists to stop. That is #773's E1b class landing inside the instrument I added to close #781's remaining escape.

Widened to bare .y_bottom / .center_xz (second commit). Both directions measured:

  • No false positive. Crate green with the widened guard: 14 headers = 14 result lines, 262 passed / 0 failed / 12 ignored — identical to the 41cca4e crate baseline. I enumerated the six entity_model_matrix_heading calls that legitimately remain in pass.rs using the same argument extractor the test uses; all six pass [0.0, 0.0] and 0.0 in those two positions, so not one of them mentions either field today. The guard is therefore tight against the current tree, not merely satisfied by it.
  • It fires. m8 in the table above.

This does not make the guard unforgeable — it is still tier-3 source text, and let y = p.y_bottom; entity_model_matrix_heading(…, y, …) still evades it. It removes one named, cheap evasion; it does not close the class.

  • The two semantic evasions fix(#768,#769): ground a static model ON its stored z, not a model height above it #773's reviewer measured are still green and this PR does not address them. E1b (rebind model to a local carrying the same field names, so all four extracted argument lists stay byte-identical) and E2 (corrupt the loader reduction at models.rs that produces y_bottom). I did not re-run either on this branch; I state them as inherited, not re-measured. Nothing in this PR makes either harder, and the pin's doc says so.
  • A static_placement or static matrix call added in another file is invisible to the pin, which reads only pass.rs. Unchanged by this PR.
  • ModelBounds is not validated. Nothing checks y_bottom >= 0 or y_extent > 0, and the two construction sites still assemble it field-by-field from ModelAsset. A wrong ModelBounds produced there is exactly as available as a wrong y_bottom was.
  • render_model.rs was not run. It compiles and its (zero) unit tests run in the workspace job; the turntable viewer was not launched. My edit there is mechanical field re-spelling, but "mechanical" is a reading, not a measurement.
  • No claim that the four pass.rs sites are the only static placements at runtime. The pin counts source-text call sites in one file; it does not prove any of them is reached.

djhenry and others added 2 commits August 1, 2026 02:09
…se lift floats

`static_placement` took `y_bottom: f32` and `center_xz: [f32; 2]`, and the four
`pass.rs` static call sites then passed a literal `0.0` for
`entity_model_matrix_heading`'s `visual_scale`. Both of those are lift arguments
a call site can do arithmetic in, and #773 measured both green against every
behavioural test in the crate — only a source-text pin caught them.

Two changes:

- `GpuStaticModel`'s `y_bottom`/`y_extent`/`x_center`/`z_center` become one
  `models::ModelBounds`, and `static_placement(archetype, &model.bounds,
  floating)` reads it. The measured evasion
  `static_placement(archetype, model.y_bottom + model.y_extent, …)` is now
  `error[E0308]`.
- New `camera::entity_model_matrix_static(pos, heading, &StaticPlacement,
  correction)` binds `visual_scale = 0.0` and `y_up = true` in its body. The
  measured evasion `visual_scale = 2.0 * model.y_extent * p.mesh_scale` is now
  `error[E0061]`. This half is beyond the issue's literal proposal and was added
  because measuring the ModelBounds parameter alone showed it left `visual_scale`
  — the actual shape of the #768 bug — a plain `f32` at the same four call sites.

Stated precisely, because overstating this boundary is what two of #773's commit
messages had to retract: this does NOT make the over-lift unrepresentable.
`ModelBounds` has public fields, so
`&ModelBounds { y_bottom: model.bounds.y_bottom + model.bounds.y_extent,
..model.bounds }` compiles and restores the identical over-lift; it is caught by
the source-text pin and by nothing else (measured, 261 passed / 1 failed).
Calling `entity_model_matrix_heading` directly still compiles too, and a new
guard in the pin catches it. The unforgeable newtype that would make the state
unrepresentable stays declined for the reason #773's reviewer found:
`tests/floating_placement.rs` is an integration test and cannot see a
`#[cfg(test)]` mint.

One thing got strictly stronger rather than merely differently pinned. The
`visual_scale = 0.0` the static arm depends on used to live at four `pass.rs`
call sites the device-free tests could not reach; it now lives inside
`entity_model_matrix_static`, which those tests DO call. Replacing it with
`2.0 * p.y_bottom * p.mesh_scale` turns
`a_grounded_static_model_is_drawn_with_its_lowest_vertex_on_the_stored_z` RED —
a behavioural failure where the same corruption previously produced only a
source-text failure.

Not addressed, and not claimed: the two evasions #773's reviewer measured that
are semantic rather than syntactic — rebinding `model` to a local with the same
field names (E1b), and corrupting the loader reduction that produces `y_bottom`
(E2). Both are still green.

Workspace unchanged at 1810 passed / 0 failed / 47 ignored, 55 headers = 55
result lines, identical to the 41cca4e baseline; no test was added or removed.
Self-review found a hole in the guard added earlier in this PR: it asserted
no `entity_model_matrix_heading` call argument list contains `p.y_bottom` or
`p.center_xz`, which pins the binding name. `let q = &p;` followed by
`entity_model_matrix_heading(…, q.center_xz, true, q.y_bottom, …)` evades it
while doing exactly the thing the guard exists to stop — #773's E1b class.

Widened to bare `.y_bottom` / `.center_xz`. Measured, not reasoned:

- No false positive. Crate green with the widened guard: 14 headers = 14
  result lines, 262 passed / 0 failed / 12 ignored — identical to the
  `41cca4e` crate baseline. The six `entity_model_matrix_heading` calls that
  legitimately remain in `pass.rs` were enumerated with the same argument
  extractor the test uses; all six pass `[0.0, 0.0]` and `0.0` in those two
  positions, so not one mentions either field.
- Reach control. Call-site mutation m8 adds `let q = &p;` plus an additive
  `entity_model_matrix_heading` call taking `q.center_xz` / `q.y_bottom` at
  `pass.rs:2113` (additive, so the two count asserts stay satisfied at 4 and
  4). RED: 261 passed / 1 failed / 12 ignored,
  `every_static_placement_in_pass_rs_is_written_exactly_as_reviewed` panicking
  at the widened guard, `floating_placement.rs:648`. The previous `p.`-pinned
  form does not occur anywhere in that argument list, so it would have stayed
  green.

No test added, removed or renamed: 11 `#[test]` before and after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
@djhenry

djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

CHANGES REQUESTED

Independent adversarial review. I did not write this change. Everything below is measured on this branch merged with the current origin/main (223ac9b) — the branch's own base 41cca4e is stale, so the PR body's "zero delta vs 41cca4e" arithmetic is against a base that has moved. The merge is clean (ort, disjoint files) and the merged figures match current main exactly; see the figures at the bottom.

The code is right. Both signature changes do what they say, the four call sites are behaviour-preserving, and the headline "the 0.0 is now behaviourally graded" claim reproduces. Every finding below is a false or overstated sentence in a tracked file — the dominant defect class on this project — and one of them is load-bearing enough to block.


BLOCKING — the "what is left open" sentence is wrong: the heading guard does not catch a direct entity_model_matrix_heading call

Two tracked sentences say the one remaining channel-1 escape is caught:

  • crates/eqoxide-renderer/src/camera.rs:130-134 — "A call site can still call entity_model_matrix_heading directly — that is a different function name, which is loud and is what tests/floating_placement.rs::every_static_placement_in_pass_rs_is_written_exactly_as_reviewed pins"
  • crates/eqoxide-renderer/tests/floating_placement.rs:526-529 — "What is left open is calling entity_model_matrix_heading directly instead — a different function NAME, which the heading guard at the bottom of this test catches."

And the guard's own mechanism comment (floating_placement.rs:638-641) states the reason: "y_bottom and center_xz are StaticPlacement's two fields that HumanoidPlacement does not have, so a .y_bottom or .center_xz in a entity_model_matrix_heading argument list identifies a static placement."

The guard relies on the converse — absence of those two field names implies not-a-static-placement — and that is false. GpuStaticModel::bounds is in scope at all four static sites and carries the same numbers under different field names, so the whole #768-shaped over-lift can be written as a direct entity_model_matrix_heading call that never spells .y_bottom or .center_xz.

Mutation EF, at pass.rs:1672 (the entity site), keeping every count and every reviewed argument list byte-identical:

let p = crate::models::static_placement(
    archetype, &model.bounds, b.floating);
let _m0 = crate::camera::entity_model_matrix_static(b.pos, b.heading, &p,
    crate::models::archetype_correction(archetype));
let mat = crate::camera::entity_model_matrix_heading(b.pos, b.heading,
    2.0 * model.bounds.y_extent * p.mesh_scale, p.mesh_scale,
    [model.bounds.x_center, model.bounds.z_center], true, 0.0,
    crate::models::archetype_correction(archetype));

Result: SURVIVED. cargo test -p eqoxide-renderer --locked --no-fail-fast → 14 headers = 14 result lines, 262 passed / 0 failed / 12 ignored — byte-identical to the green baseline. Zero tests failed. static_placement( still occurs 4×, entity_model_matrix_static( still occurs 4×, both REVIEWED_ARGS and REVIEWED_MATRIX_ARGS still match, and the heading guard sees .y_extent / .mesh_scale / .x_center / .z_center and waves it through. visual_scale * 0.5 = y_extent * mesh_scale = 9.945u of lift for boat.glb, with the grounding term dropped — the exact term #768 removed, at a production static call site.

This satisfies the premise of both quoted sentences (a static placement, at a static call site, going through entity_model_matrix_heading directly) and produces the forbidden outcome. The author's own m4b and m8 probes do not test the claim: both spell q.y_bottom/p.y_bottom, i.e. both are the case the guard was written for. And the spelling I used is not exotic — [model.x_center, model.z_center] is literally what these four call sites said on 41cca4e, before this PR folded it into &model.bounds. It is the most natural way a future author would rewrite this.

Two acceptable fixes, either is fine by me:

  1. Downgrade the two sentences to what is actually true: the guard catches a direct entity_model_matrix_heading call only when its argument list names .y_bottom or .center_xz, and the model.bounds.* spelling evades it. That is a one-paragraph edit and I would merge it.
  2. Close it, which is cheap and symmetric with what this PR already does for the static calls: give entity_model_matrix_heading a REVIEWED_HEADING_ARGS whitelist (the six legitimate calls all pass [0.0, 0.0] and 0.0 today, so the list is short and stable) instead of a two-substring blacklist. Then any new heading call in pass.rs is RED until reviewed, which is the same trade the rest of this test already makes.

I would merge over this only with fix 1 or 2 applied. I would not merge it as written, because the sentence is exactly the kind that gets cited by the next change as "already closed".


Non-blocking

N1 — ModelBounds::x_center/z_center are documented as a "centroid"; they are an AABB midpoint. models.rs:1003 and :1005 say "Horizontal centroid on the model's first/second horizontal axis." Measured at the loader: ((x_min + x_max) * 0.5, (z_min + z_max) * 0.5) over dominant-mesh vertices (models.rs:545-549), and the same shape on the skinned branch (:541). That is the midpoint of the bounding box, not the centroid of the point set; the two differ for any asymmetric mesh. The field this replaced said "Center of the model in the X and Z axes", which was vaguer and not wrong. New sentence, measurably false, one-word fix. (This is the "horizontal recentre datum" #756 explicitly left unestablished, so the definition is not purely cosmetic.)

N2 — the guard's mechanism sentence is wrong in the false-positive direction too. floating_placement.rs:640 says .y_bottom/.center_xz "identifies a static placement". GpuSkinnedModel also has pub y_bottom, pub x_center, pub z_center (gpu.rs), and all six legitimate entity_model_matrix_heading calls in pass.rs are on skinned paths. A future skinned site that legitimately passes model.y_bottom would trip an assert whose message names a static-placement bug it is not. The loudness is intentional; the sentence is not accurate.

N3 — the error[E0308] code is transcription-dependent. Measured both:

Substance holds either way (neither compiles); the PR body should say which transcription produces which code.

N4 — floating_placement.rs:563: "one trailing comma is stripped". trim_end_matches(',') strips all trailing commas, not one. Harmless today; the sentence is a claim about the extractor.

N5 — floating_placement.rs:645: "(all skinned, plus the two non-entity ones)". All six remaining entity_model_matrix_heading calls are on skinned paths; the two that are not per-mesh calls are the held-item matrices at pass.rs:1447 (player) and pass.rs:1850 (an entity, in encode_entity_pass). "non-entity" mislabels the second.

N6 — models.rs:135 says the 2 × y_extent × arch_scale spelling "survives only in the standalone render_model viewer bin (src/bin/render_model.rs:1101 …)". There are two such spellings in that file, :1101 and :1268; the sibling correction block at models.rs:926 cites both. Nit, but this line was re-touched by this PR.

N7 — the &p pin does not bind what p denotes, and one assert message says otherwise. REVIEWED_MATRIX_ARGS's message (floating_placement.rs:633) reads "Passing anything but the placement built one line above re-opens that." Measured (mutation SP), inserting between the two reviewed calls at pass.rs:1672:

let p = crate::models::StaticPlacement { mesh_scale: p.mesh_scale, center_xz: p.center_xz,
    y_bottom: p.y_bottom + model.bounds.y_extent };

SURVIVED — 262 passed / 0 failed / 12 ignored, fully green. The exact pre-#768 over-lift, &p unchanged in the argument text. I am filing this as non-blocking because camera.rs:135 does disclose the capability ("it can still hand this function a StaticPlacement built from wrong bounds") — but the assert message contradicts that disclosure and should be reworded. Note also that StaticPlacement being freely constructible is nowhere disclosed; only ModelBounds is.


Verified as claimed

  • m1 (channel 1)error[E0061]: this function takes 4 arguments but 5 arguments were supplied, at pass.rs:1674. ✅ exact code as claimed.
  • m2 (channel 2) — compile error, see N3 for the code nuance. ✅ does not type-check.
  • m3 (ModelBounds struct literal) — applied at the player site (pass.rs:1515), a different site from the author's, as an independent reach control for REVIEWED_ARGS. RED, 261 passed / 1 failed / 12 ignored, every_static_placement_in_pass_rs_is_written_exactly_as_reviewed at :604, and nothing else. ✅ matches the disclosed "caught by the source-text pin and nothing else".
  • m6 (the headline win) — replacing the 0.0 in entity_model_matrix_static's body with 2.0 * p.y_bottom * p.mesh_scale: RED, 261 / 1 / 12, a_grounded_static_model_is_drawn_with_its_lowest_vertex_on_the_stored_z at :224behavioural, delta 3.9823165u. ✅ The claim that the constant moved from text-pinned to behaviourally graded is real, and it is the best thing in this PR.
  • The four pass.rs sites really were device-unreachable, and all four now route through the new function. encode_player_pass / encode_entity_pass / encode_shadow_pass all take &EqRenderer + &mut wgpu::CommandEncoder; no test in the workspace calls any of them (the shadow test files say so explicitly and pin the calls, not the bodies). All four static_placement( sites at :1515 / :1672 / :2079 / :2113 pair with an entity_model_matrix_static( at :1517 / :1674 / :2080 / :2114, and there are exactly 4 of each in the file. ✅
  • Both GpuStaticModel construction sites updated (renderer.rs:1032, render_model.rs:851); no other constructor exists in the workspace. ✅
  • GpuStaticModel's y_extent is not read on this crate's render path.
  • The line-citation corrections are right. On 41cca4e, render_model.rs:1097 is }; and :1098 is blank; the true lines were 1099/1100, and on this tree they are 1101/1102. Spot-checked all seven re-measured citations (:814, :1101, :1102, :1268, :1274, :1294, :1473) — all land where the doc says. ✅
  • ModelBounds::y_bottom / y_extent docs match y_bottom_and_extent (-y_min clamped at 0; y_max - y_min). ✅
  • Both repo gates pass on the branch: check-no-local-detail.sh OK, check-wrapped-literals.py OK (172 tracked .rs files). ✅

Guard reach control (not just a positive control)

Per this repo's scanner-reach lesson, I gave the heading guard a violating shape at three separated depths and in both call forms, one run each, rather than re-deriving it by reading:

probe site form result
RC1 pass.rs:1356first heading call in the file plain multi-line RED, guard at :648, offending args printed
RC2 pass.rs:1447 nested from_cols_array_2d(&…) RED, guard at :648
RC3 pass.rs:2103last heading call in the file plain multi-line RED, guard at :648

So the guard genuinely reaches the whole file and both call shapes; it is not a ~12%-of-corpus scanner. That makes the blocking finding a real blind spot in what it matches, not an artifact of where it looks. RC2 also shows the extractor over-captures one ) on the nested form (arg list ends …archetype))) — harmless for a contains check, noted only so the next person is not surprised.


Five figures — merged tree (this branch + 223ac9b), streams captured separately

cargo test --workspace --locked --no-fail-fast, process exit 0, no test result: FAILED line:

  • Finished `test` profile [unoptimized + debuginfo] target(s) in 25m 11s
  • 55 running [0-9]+ tests? headers = 55 test result: lines (regex matches the singular running 1 test)
  • 0 non-canonical result lines
  • 14 targets at the full triple 0 passed; 0 failed; 0 ignored (the bare 0 passed anchor reads 18, which is the all-#[ignore]d targets, not a regression)
  • 1823 passed + 0 failed + 47 ignored + 0 measured + 0 filtered out = 1870 = the header sum

Delta vs current origin/main (223ac9b: 55 = 55, 0, 47, 1823 + 0 + 47 + 0 = 1870 across 55/55): zero in every field. Nothing to reconcile by name — no test added, removed, renamed or re-ignored, and no cross-PR behavioural collision with #821. Running unittests src/bin/render_model.rs present (0 tests), so the re-spelled viewer is compiled by the workspace job.

Crate baseline used for every mutation above, same tree: 14 headers = 14 result lines, 262 passed / 0 failed / 12 ignored.

Every mutation was applied from an md5-verified pristine copy with an exact occurrence-count assert before the write, reverted by cp -p from that copy with md5 re-verified and touched after, and the tree re-confirmed pristine after each of the eleven runs.


Not verified, and not claimed

  • No live or GPU run, and none is possible for the grounded static arm — I did not re-derive the "boat.glb is the only static-arm model and SHP is always floating" scan either. Inherited from fix(#768,#769): ground a static model ON its stored z, not a model height above it #773/renderer: a skinned model that exceeds the 128-joint cap falls to the static arm SILENTLY — race_pcfroglok sits at 127 of 128 #780, same as the author.
  • The 41cca4e control rows (ctrl-1, ctrl-2 at 261/1/12 on the old base) — I did not re-run them. They are about a tree that is no longer the base.
  • E1b and E2 — I did not re-run them on this branch either. I accept the author's statement that this PR does not address them, and I did not test whether it accidentally does.
  • render_model.rs's viewer was not launched. I read the re-spelling and it is mechanical, but reading is not measuring — same disclosure the author makes.
  • ModelBounds validation — I did not probe what a garbage ModelBounds from either construction site does downstream.
  • I did not test the #[cfg(any())] / if false / macro_rules! members of the source-pin evasion family. Reasoning (not measurement): all three have to either add or remove a static_placement( / entity_model_matrix_static( text occurrence, which the two len() == 4 asserts catch. The two evasions I did find (EF, SP) both work by leaving all four counts and all four argument texts untouched, which is a different mechanism, so I spent the budget there. Treat the first sentence of this bullet as unmeasured.
  • Whether any of the four sites is reached at runtime. Unchanged by this PR, and the pin says so.

djhenry and others added 3 commits August 1, 2026 03:18
…klist cannot enumerate the aliases

#828 round 1 closed the remaining channel-1 escape with a BLACKLIST: no
`entity_model_matrix_heading` argument list in `pass.rs` may contain `.y_bottom` or
`.center_xz`, reasoning that those are `StaticPlacement`'s two fields `HumanoidPlacement`
does not have, so their presence "identifies a static placement". The review measured that
reasoning false in the load-bearing direction: `GpuStaticModel::bounds` is in scope at all
four static sites and carries the same numbers under other names, so the whole #768
over-lift written as `model.bounds.y_extent` / `.x_center` / `.z_center` SURVIVED, fully
green at 262 passed / 0 failed / 12 ignored. That spelling is not exotic — it is what all
four sites passed to `static_placement` on 41cca4e.

Rewording was the cheaper of the two offered fixes; this takes the other one, because the
blacklist's failure is structural rather than a missed case. A blacklist has to enumerate
the ALIASES of four numbers and they are not enumerable: measured here, binding them to
plain locals first (`let lift = …; let ctr = p.center_xz;`) leaves an argument list with no
field name in it at all, and that is green against the blacklist too. A whitelist has to
enumerate the legitimate CALLS, of which there are six in one file — the same trade the two
asserts above it already make.

- `REVIEWED_HEADING_ARGS`: the six calls' four distinct argument spellings, plus a count.
  The per-call whitelist runs BEFORE the count deliberately, so an added call with a novel
  argument list fails naming the offending arguments; the count then catches the one shape
  the whitelist cannot, a call copied verbatim from a reviewed site into a static one.
  Both orders have a reach control (EF and EW below).
- The extractor now depth-counts to the paren that closes the call. `find(");")` captured
  one `)` too many for a call nested in another (`:1447`, `:1850`) — harmless for a
  `contains` check, not harmless for an exact-match whitelist.

Measured, crate `cargo test -p eqoxide-renderer --locked --no-fail-fast`, one run each from
an md5-verified pristine copy, all at the entity site `pass.rs:1672`:

| probe | shape | result |
|---|---|---|
| baseline | — | 14 headers = 14 result lines, 262 / 0 / 12 |
| EF | the review's: direct heading call spelling `model.bounds.y_extent` / `.x_center` / `.z_center` | RED 261 / 1 / 12, whitelist loop |
| EA | same lift bound to plain locals first; NO field name in the argument list | RED 261 / 1 / 12, whitelist loop |
| EA vs round 1 | EA with round 1's blacklist restored | GREEN 262 / 0 / 12 — a second alias the blacklist misses |
| EW | added call whose argument list is byte-identical to a reviewed one | RED 261 / 1 / 12, count assert |

What this does NOT do, stated at the guard: it bounds the argument TEXT of the six calls in
one file and their count. It does not bound what the names in a reviewed text denote — the
`&p` assert has the same hole, and the review measured it green — and it reads no other file.

Three of the review's non-blocking findings live in the same paragraphs and are fixed here
rather than left to contradict the new text:

- N2 — the blacklist's mechanism sentence ("`.y_bottom` … identifies a static placement")
  is false in the false-positive direction too, since `GpuSkinnedModel` has those fields.
  The sentence is gone with the blacklist; the replacement records why it was wrong.
- N3 — the `error[E0308]` claim is transcription-dependent. Both spellings re-measured on
  this tree: the literal pre-#781 call (still passing a separate `center_xz`) is
  `error[E0061]: this function takes 3 arguments but 4 arguments were supplied`, with
  `expected &ModelBounds, found f32` as a sub-note; the minimal 3-argument transcription is
  `error[E0308]: mismatched types`. Both stated, in both doc blocks that made the claim.
- N5 — "(all skinned, plus the two non-entity ones)" mislabelled `pass.rs:1850`, which is a
  held-item matrix inside `encode_skinned_entity_pass`, i.e. an entity site. All six calls
  are now enumerated by line with their pass.
- N7 — `REVIEWED_MATRIX_ARGS`'s message said "passing anything but the placement built one
  line above re-opens that", contradicting `camera.rs`'s own disclosure: the review shadowed
  `p` with a hand-built `StaticPlacement` carrying the over-lift, `&p` unchanged, and
  measured it green. The message now says what it pins (the TEXT) and names the hole, and
  `StaticPlacement` discloses that it is freely constructible — round 1 disclosed that for
  `ModelBounds` only. The capability is left open, as the reviewer graded it.

Refs #768, #773, #781.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
…measured word fixes

Three of #828's non-blocking findings, all of the same defect class: a sentence in a tracked
file that says something the code does not do.

- N1 — `ModelBounds::x_center` / `z_center` were documented as a "horizontal centroid".
  Measured at the loader: `((x_min + x_max) * 0.5, (z_min + z_max) * 0.5)`, on both the
  skinned and the dominant-mesh branch of `ModelAsset::load`. That is the midpoint of the
  bounding box, and it differs from the centroid of the vertex set for any model not
  symmetric about it. Corrected to the measured quantity rather than softened, because the
  field this replaced said "Center of the model in the X and Z axes", which was vaguer and
  not wrong — and because which of the two the horizontal recentre datum should be is the
  question #756 explicitly left unestablished.
- N6 — `models.rs`'s `y_extent` doc cited one of the two `2 × y_extent × arch_scale`
  spellings in the viewer (`render_model.rs:1101`); there are two (`:1101` and `:1268`) and
  the sibling block on `StaticPlacement` already cited both. Both re-checked on this tree.
- N4 — "one trailing comma is stripped" describes `trim_end_matches(',')`, which strips all
  of them. No behavioural difference on today's call sites; the sentence is a claim about
  the extractor and was wrong.

No code change; crate stays at 262 passed / 0 failed / 12 ignored.

Refs #756, #781.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
@djhenry

djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Round 2 — blocking finding closed by whitelisting, not by rewording

Thank you for EF. It is the finding I would least have found myself: round 1's own probes (m4b, m8) both spell q.y_bottom / p.y_bottom, which is only the case the guard was written for — the exact trap of varying the violating shape only in the direction the author was already thinking about.

The branch now contains a merge of 223ac9b, so the tree I measured is byte-identical to the tree that is pushed and there is no separate merge for you to reproduce. Three commits:

commit finding
8de0802 merge of 223ac9b (the ort merge you already measured; disjoint files, no renderer file touched)
3b07bc2 BLOCKING, plus N2, N3, N5, N7 — all of which live inside the paragraphs the blocking fix rewrote
3fdc4e7 N1, N4, N6 — pure doc corrections, separable, so they are separate

BLOCKING — I took fix 2 (whitelist), and here is why rather than fix 1

Rewording was cheaper and you said you would merge it. I did not take it, because the blacklist's failure is structural, not a missed case, and a reworded sentence would have to describe a guard whose stated job cannot be done by its mechanism.

A field-name blacklist has to enumerate the aliases of four numbers. They are not enumerable, and the alias set does not even have to contain a field name:

  • your EF spells model.bounds.y_extent / .x_center / .z_center;
  • EA (below, mine) binds the same numbers to plain locals first, so the argument list reads b.pos, b.heading, lift, p.mesh_scale, ctr, true, 0.0, …not one field name in it. No list of field names can ever catch that.

A whitelist has to enumerate the legitimate calls, of which there are six, in one file, and it is the same trade REVIEWED_ARGS and REVIEWED_MATRIX_ARGS already make three lines above. So: REVIEWED_HEADING_ARGS, the four distinct argument spellings of those six calls, plus a count assert.

Two design points, both deliberate and both measured rather than reasoned:

Order. The per-call whitelist runs before the count. An added call with a novel argument list must fail on the whitelist, printing the offending arguments, rather than fail on a count that says only "six became seven". The count then catches the one shape the whitelist cannot — a call copied verbatim from a reviewed site into a static one, where the same argument names denote a static model's numbers. Each has its own reach control (EF hits the whitelist, EW hits the count), so neither is an unexecuted assert.

Extractor. It now depth-counts to the paren that closes the call. Your RC2 note was right and it stops being cosmetic under an exact-match whitelist: find(");") over-captured one ) on the nested from_cols_array_2d(&…) form at :1447 and :1850, which would have forced two whitelist entries per spelling differing only by a trailing paren. With depth counting the six calls collapse to four spellings. The assumption (parens balanced inside an argument list) is stated at the extractor, and its failure mode is a whitelist miss, i.e. RED, not a silent pass.

And I stated the guard's real limit at the guard, so it does not become the next source of false confidence: it bounds the argument TEXT of those six calls in that one file, and their count. It does not bound what the names in a reviewed text denote — the same hole REVIEWED_MATRIX_ARGS has, which your SP mutation measured — and it reads no other file.

EF's new result

thread 'every_static_placement_in_pass_rs_is_written_exactly_as_reviewed'
panicked at crates/eqoxide-renderer/tests/floating_placement.rs:723:9:
#768/#781: `entity_model_matrix_heading` adds `visual_scale * 0.5` ON TOP of `y_bottom *
mesh_scale`, which is exactly the #768 over-lift, and a static model's numbers are reachable at
every static call site under several names (`model.bounds.y_extent`, a local bound from it, …).
Enumerating those names is not possible, so this enumerates the legitimate CALLS instead: every
`entity_model_matrix_heading` in pass.rs must be spelled as reviewed. A new one is not necessarily
wrong — it has to be reviewed for the #768 lift, which is why the list is pinned. Expected one of
[…], found: b.pos, b.heading, 2.0 * model.bounds.y_extent * p.mesh_scale, p.mesh_scale,
[model.bounds.x_center, model.bounds.z_center], true, 0.0,
crate::models::archetype_correction(archetype)

RED, 261 passed / 1 failed / 12 ignored, every_static_placement_in_pass_rs_is_written_exactly_as_reviewed, and nothing else fails. Your mutation text applied verbatim at pass.rs:1672.

The additional field-alias probe: EA

Your instruction was to probe the alias class more broadly than one alias set. I picked the member that defeats the category rather than another spelling of it — no field name in the argument list at all, inserted at pass.rs:1672 between the two reviewed calls (the entity_model_matrix_static call kept as let _m0, so all four counts and all four reviewed argument texts stay intact):

let lift = 2.0 * model.bounds.y_extent * p.mesh_scale;
let ctr = p.center_xz;
let mat = crate::camera::entity_model_matrix_heading(b.pos, b.heading, lift,
    p.mesh_scale, ctr, true, 0.0, crate::models::archetype_correction(archetype));
  • Against round 1's blacklist: GREEN, 262 passed / 0 failed / 12 ignored — measured, not reasoned: I restored round 1's floating_placement.rs verbatim from 1d056b5 with EA in place and ran it. So EF is not a lucky single case; the blacklist misses a second, weaker-to-write member.
  • Against the whitelist: RED, 261 / 1 / 12, same test, found: b.pos, b.heading, lift, p.mesh_scale, ctr, true, 0.0, crate::models::archetype_correction(archetype).

EW — reach control for the count assert

The one shape a whitelist cannot see: an added heading call whose argument list is byte-identical to a reviewed one, with visual_scale / dominant_scale bound at the static site to the over-lift. It passes the whitelist loop and trips the count:

panicked at crates/eqoxide-renderer/tests/floating_placement.rs:733:5:
assertion `left == right` failed: pass.rs must call entity_model_matrix_heading at exactly the
6 known skinned sites (four per-model matrices, two held-item matrices); found 7. …
  left: 7   right: 6

RED, 261 / 1 / 12.

Mutation table (crate cargo test -p eqoxide-renderer --locked --no-fail-fast, one run each)

# shape site result
baseline 14 headers = 14 result lines, 262 / 0 / 12
EF yours, verbatim pass.rs:1672 RED 261 / 1 / 12, whitelist loop :723
EA same lift via plain locals, no field name in the arg list pass.rs:1672 RED 261 / 1 / 12, whitelist loop :723
EA/r1 EA with round 1's floating_placement.rs restored pass.rs:1672 GREEN 262 / 0 / 12 — second blacklist miss
EW added call, arg list byte-identical to a reviewed one pass.rs:1672 RED 261 / 1 / 12, count assert :733
C1 channel 2 transcribed literally (4 args) pass.rs:1672 error[E0061]: this function takes 3 arguments but 4 arguments were supplied
C2 channel 2 transcribed minimally (3 args) pass.rs:1672 error[E0308]: mismatched types, expected &ModelBounds, found f32

Every mutation applied by script from an md5-verified pristine copy with an exact occurrence-count assert before the write, reverted by cp -p with md5 re-verified and touched after, tree re-confirmed pristine (git status empty, pass.rs md5 dfe338f1…) after the last one.


Non-blocking, each one closed

N1 — "centroid" → bounding-box midpoint. Corrected to the measured quantity, not softened: ((x_min + x_max) * 0.5, (z_min + z_max) * 0.5), both branches of ModelAsset::load. The doc now says explicitly that it is not the centroid of the vertex set, that the two differ for any model not symmetric about the midpoint, and that this is #756's unestablished recentre datum so the word is load-bearing.

N2 — the false-positive half of the mechanism sentence. Gone with the blacklist. The replacement comment records both directions of why the old reasoning was wrong, including that GpuSkinnedModel has y_bottom / x_center / z_center too, so .y_bottom never identified a static placement.

N3 — the E0308 claim. Re-measured both transcriptions myself rather than transcribing yours; both are now stated, in both doc blocks that carried the claim. Literal (still passing a separate center_xz) → E0061, with expected &ModelBounds, found f32 as a sub-note and no standalone E0308 in the output; minimal 3-argument → E0308.

N4 — "one trailing comma". Now "trailing commas are stripped — trim_end_matches(',') removes every one of them, not one".

N5 — pass.rs:1850. "the two non-entity ones" is gone. All six calls are now enumerated with their pass: four per-model matrices (:1356 player, :1818 skinned entity, :2070 / :2103 shadow), two held-item matrices (:1447 in encode_player_pass, :1850 in encode_skinned_entity_pass) — and the comment says outright that the second held-item one is an entity site and that round 1 called it otherwise. One correction on top of yours: :1850 is inside encode_skinned_entity_pass (which starts at :1745), not encode_entity_pass (:1646:1745); your substantive point stands either way.

N6 — models.rs:135. Now cites both render_model.rs:1101 and :1268, both re-checked on this tree, and says the sibling block already cited both.

N7 — the &p message vs the disclosure. The capability is left open, as you graded it; the sentences now agree. REVIEWED_MATRIX_ARGS's message says what it pins (the argument TEXT: the matrix must be built from &p) and names the hole — shadowing p with a hand-built StaticPlacement leaves the text unchanged and was measured green — and points at entity_model_matrix_static's doc, which discloses the same thing. StaticPlacement's own doc now says it is freely constructible; round 1 disclosed that for ModelBounds only, which was your second N7 point. Your SP result is also recorded in the test's "what this still does not do" list, alongside #773's E1b and E2, as a third measured-green instance of the same class.


Five figures — pushed tree (branch, which now contains 223ac9b)

cargo test --workspace --locked --no-fail-fast, streams captured separately, no test result: FAILED line:

  • Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 57s (short only because the builder's target dir was warm from the crate runs above)
  • 55 running [0-9]+ tests? headers = 55 test result: lines
  • 0 non-canonical result lines
  • 14 targets at the full triple 0 passed; 0 failed; 0 ignored (the bare 0 passed anchor reads 18, the all-#[ignore]d targets, not a regression)
  • 1823 passed + 0 failed + 47 ignored + 0 measured + 0 filtered out = 1870 = the header sum

Delta vs 223ac9b (55 = 55, 0, 14, 1823 + 0 + 47 + 0 = 1870): zero in every field, and identical to your measurement of round 1 merged. Nothing to reconcile by name — no test added, removed, renamed or re-ignored. Running unittests src/bin/render_model.rs present. Crate baseline on the final tree: 14 = 14, 262 / 0 / 12.

scripts/check-no-local-detail.sh OK. scripts/check-wrapped-literals.py OK (172 tracked .rs files) — relevant here because the four whitelist entries are line-continued string literals.


NOT verified, and not claimed

  • No live or GPU run, and none is possible for the grounded static arm. I did not re-derive the "boat.glb is the only static-arm model, and SHP is always floating" scan; inherited from fix(#768,#769): ground a static model ON its stored z, not a model height above it #773/renderer: a skinned model that exceeds the 128-joint cap falls to the static arm SILENTLY — race_pcfroglok sits at 127 of 128 #780, same as round 1 and same as you.
  • SP is yours, not re-measured by me. I cite it in three tracked files as measured at 262 / 0 / 12 and attribute it to this review. I did not re-run it; I spent the budget on EA instead, which tests a claim nobody had tested.
  • E1b and E2 not re-run on this branch either, by me or by round 1. Recorded as inherited.
  • The count assert bounds additions, not deletions-plus-additions. EW proves it fires at 7. I did not probe a mutation that removes one legitimate heading call and adds a malicious one, keeping the count at 6 with a whitelisted spelling. Reasoning only, and flagged as such: that requires deleting a live draw call, which is not a silent shape. Unmeasured.
  • The extractor's balanced-paren assumption is unprobed. I did not write a call containing an unbalanced paren inside a string literal to confirm the failure is loud. The claim in the comment is reasoning, and the comment says so.
  • render_model.rs's viewer was not launched. It compiles and its zero unit tests run in the workspace job. Its two 2 × y_extent × arch_scale spellings are cited, not exercised.
  • ModelBounds is still unvalidated, and I did not probe what a garbage one does downstream.
  • No claim that any of the four static sites is reached at runtime. Unchanged; the pin says so.
  • I did not re-litigate anything you verified as TRUE — the four device-unreachable sites, both construction sites, y_extent unread on the render path, the seven render_model.rs citations, or the :1097}; correction. I re-checked the seven citations only incidentally, while confirming :1268 for N6.

Not merging and not self-approving; back to you.

@djhenry

djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

CHANGES REQUESTED

Independent adversarial review, round 2 — my second and final dispatch on this PR.

Reviewed 3fdc4e7 merged with origin/main 5fba300 (clean ort merge, touching only
shadow_caster_selection.rs and walker_sim.rs); merged HEAD 32d2f83. All figures below are
measured on that merged tree, not on the branch alone. Every mutation was applied by script from
md5-verified pristine copies with an exact occurrence-count assert before each write, reverted by
cp -p + md5 re-verify + touch, and the tree re-confirmed pristine after the last run.

Round 2 is a real improvement and my two round-1 blocking findings are closed. What I am
blocking on is new: two sentences written in this round claim completeness that the mechanism
does not have, and I measured both false in one mutation each. Both fixes are small — the code is
not in question.


First, the three things you asked me to grade, answered plainly

EA reproduces, and you are right about what it means. I restored floating_placement.rs
verbatim from 1d056b5 and put the plain-locals over-lift at pass.rs:1672:
GREEN, 264 passed / 0 failed / 12 ignored — byte-identical to the merged-tree baseline. (Your
262 vs my 264 is origin/main's two new renderer tests, not a disagreement.) Against the round-2
whitelist the same edit is RED, 263 / 1 / 12, at the whitelist loop :727. So my EF finding was
a category, not a single case, and replacing the blacklist with a whitelist is the right call. I
would have made that trade too.

N5: you are right and I was wrong. encode_skinned_entity_pass starts at :1745 and
encode_shadow_pass at :1969, so :1850 is inside encode_skinned_entity_pass, not
encode_entity_pass (:1646:1744). Your correction stands as written; my substantive point
(both held-item calls are nested inside from_cols_array_2d(&…), and all six calls are on skinned
paths) is unaffected, and the round-2 comment enumerating all six by pass is accurate — I checked
each of the six against the source.

The paren-depth extractor's failure is loud in the direction you did not probe, too. Your
comment covers over-capture. I probed under-capture — the #825 shape, a stray ) inside a block
comment inside a reviewed argument list, with a real over-lift composed onto the correction
matrix behind it:

crate::models::archetype_correction(archetype) /* ) */
    * glam::Mat4::from_translation(glam::Vec3::new(0.0, 0.0, 5.0)),

RED, 263 / 1 / 12, found: … archetype_correction(archetype) /*. The truncation cannot land
exactly at the end of a reviewed spelling, because whatever text carries the stray ) is itself
inside the captured range and survives normalization — so the capture always differs from the
whitelist entry by that residue. (That generalization is reasoning; the single instance is
measured.) I also checked the depth -= 1 underflow: the scan starts on the opening paren, so
depth is 1 before any ) can be seen and the loop breaks at 0 — it cannot underflow.


BLOCKING

B-1. "every entity_model_matrix_heading call in pass.rs" is false — measured

Two tracked sentences, both new in round 2:

  • tests/floating_placement.rs:706 — "It bounds the argument TEXT of every
    entity_model_matrix_heading call in pass.rs, and their count. It does not bound what the names
    in a reviewed text denote … and it reads no other file."
  • src/camera.rs:135-141 — "requires every entity_model_matrix_heading argument list in
    pass.rs to be one of six reviewed spellings, and pins their count. … Two limits survive that
    change
    : the test reads pass.rs and no other file, and it bounds the argument TEXT, not what the
    names in it denote."

arg_lists is PASS_RS.match_indices(&format!("{name}(")). A call made through an as-renamed
import contains neither the name nor the paren, so it is not an argument list the whitelist bounds
and not a call the count counts. At pass.rs:1672 — the same site you used for EF and EA, with the
reviewed entity_model_matrix_static call kept intact as let _m0:

use crate::camera::entity_model_matrix_heading as emh;
let mat = emh(b.pos, b.heading, 2.0 * model.bounds.y_extent * p.mesh_scale, p.mesh_scale,
    [model.bounds.x_center, model.bounds.z_center], true, 0.0,
    crate::models::archetype_correction(archetype));

SURVIVED — 264 passed / 0 failed / 12 ignored, byte-identical to the green baseline.
headings.len() is still 6, all four reviewed spellings are unchanged, statics.len() is still 4 —
and the whole #768 over-lift plus the horizontal recentre is back on every static spawn.

This is the same escape class the loop three lines above already closes: it asserts
!PASS_RS.contains("{name} (") because that spelling "would leave the pin green with an unreviewed
call site". An as-rename does exactly that, and is not asserted. A module-level
use crate::camera::entity_model_matrix_heading as heading_matrix; has the same effect (reasoned,
not separately measured).

Fix, either or both: add assert!(!PASS_RS.contains(&format!("{name} as ")), …) to that same loop;
and stop claiming completeness in both files. Even with the assert, a macro_rules! wrapper remains
open — that is #799's tracked class — so the sentence has to come down regardless. "Two limits
survive that change" is the specific phrase I cannot let through: there is at least a third.

B-2. The count assert does not catch what its own message says it catches — measured

  • tests/floating_placement.rs:723 — "The count then catches the one shape the whitelist cannot — an
    added call whose argument list is byte-identical to a reviewed one, whose names denote something
    else at the new site."
  • :739, in the assert message itself — "This catches the one addition the whitelist above cannot: a
    call copied verbatim from a reviewed site into a static one, where the same argument names denote a
    static model's numbers."

Your own not-verified list flags the delete-one-add-one case as "Reasoning only … that requires
deleting a live draw call, which is not a silent shape. Unmeasured." That parenthetical is the
part that is wrong
, and it is what makes the two sentences above unsafe. It does not require
deleting a draw call. Three edits, all to pass.rs:

  1. The :1818 heading call is loop-invariant — not one of its eight arguments depends on
    mesh_idx or mesh. Hoist it out of the mesh loop. That is a plainly correct refactor anyone
    might make.
  2. :1850's held-item matrix takes the identical eight arguments, so it becomes
    let emat = glam::Mat4::from_cols_array_2d(&mat);. No draw call is deleted — the held-item
    matrix is still built, from the same numbers. Heading calls: 6 → 5.
  3. At :1672, next to the untouched reviewed entity_model_matrix_static call, bind
    let visual_scale = 2.0 * model.bounds.y_extent * p.mesh_scale; let dominant_scale = p.mesh_scale;
    and add a heading call whose argument list is byte-for-byte REVIEWED_HEADING_ARGS[1].

headings.len() = 6. Every argument list is a reviewed spelling. statics.len() = 4,
placements.len() = 4, all three whitelists untouched. SURVIVED — 264 / 0 / 12. The #768
over-lift is restored on every static spawn by a diff whose only "suspicious" hunk is a
dead-obvious hoist.

Note this is not fixable by counting harder. A per-spelling multiset count would also pass: after
the hoist the multiset is unchanged (A×2, B×2, C×1, D×1). No mechanism that bounds argument text
can distinguish "same text, different site". The honest fixes are to pin the calls' sites (line
numbers, or the enclosing fn) rather than only their texts, or to move this case from "the count
catches it" into the "what this still does not do" list where the &p shadowing case already
correctly lives.


NON-BLOCKING

NB-1 — the pin does not have to be evaded by a call at all. Leave the reviewed
entity_model_matrix_static call byte-identical and add one line after it:

mat[3][2] += model.bounds.y_extent * p.mesh_scale;

SURVIVED — 264 / 0 / 12. Exact #768 over-lift, three whitelists and two counts untouched.
Non-blocking because no source-text pin over argument lists can bound arithmetic on the return
value
— but the "what this bounds, and what it does not" paragraph is the place that should say so,
and today it does not.

NB-2 — "one of six reviewed spellings" (camera.rs:137) is miscounted.
REVIEWED_HEADING_ARGS is [&str; 4]. Six is the call count; there are four spellings, two of
them shared by two sites each. That sharing is exactly what makes B-2 work, so the number is worth
getting right.

NB-3 — the "one file" limit is not hypothetical, and the surviving copy is a whole call.
src/bin/render_model.rs:1268-1272 computes vscale = 2.0 * s.model.bounds.y_extent * s.arch_scale
and hands it to camera::entity_model_matrix_heading together with
[s.model.bounds.x_center, s.model.bounds.z_center] and s.model.bounds.y_bottom — the literal
pre-#768 static spelling, alive on this tree, invisible to the pin. models.rs's prose calls this
"visual_scale = 2 × y_extent × arch_scale survives only in the standalone render_model viewer
bin", which is true and both cited lines (:1101, :1268) verify — but a reader would not learn
from it that the surviving copy is a complete entity_model_matrix_heading call site with the
recentre and the y_bottom lift attached. One clause.


Mutation table

Crate runs, rbuild <wt> test -p eqoxide-renderer --locked --no-fail-fast, one run each, from
pristine every time. Baseline on the merged tree: 14 headers = 14 result lines,
264 passed / 0 failed / 12 ignored.

# mutation site result killed by
EAL heading call via use … as emh alias, over-lift pass.rs:1672 SURVIVED 264 / 0 / 12
EDA hoist :1818, reuse for :1850, add byte-identical reviewed call :1818+:1850+:1672 SURVIVED 264 / 0 / 12
POST mat[3][2] += after the untouched reviewed static call pass.rs:1672 SURVIVED 264 / 0 / 12
EA_B your EA (plain locals) vs round 1's floating_placement.rs from 1d056b5 pass.rs:1672 SURVIVED 264 / 0 / 12 — (reproduces your result)
EA_W your EA (plain locals) vs the round-2 whitelist pass.rs:1672 RED 263 / 1 / 12 every_static_placement_in_pass_rs_is_written_exactly_as_reviewed, whitelist loop :727
HP stray ) in a block comment truncating the depth scan, over-lift composed onto the correction pass.rs:1818 RED 263 / 1 / 12 same test, :727 (found: … archetype_correction(archetype) /*)
RC1 [0.0, 0.0][0.5, 0.0] :1356 player per-mesh RED 263 / 1 / 12 same test, whitelist loop :727
RC2 [0.0, 0.0][0.5, 0.0] :1447 player held item RED 263 / 1 / 12 same test, whitelist loop :727
RC3 [0.0, 0.0][0.5, 0.0] :1818 skinned entity RED 263 / 1 / 12 same test, whitelist loop :727
RC4 [0.0, 0.0][0.5, 0.0] :1850 spawn held item RED 263 / 1 / 12 same test, whitelist loop :727
RC5 [0.0, 0.0][0.5, 0.0] :2070 shadow player RED 263 / 1 / 12 same test, whitelist loop :727
RC6 [0.0, 0.0][0.5, 0.0] :2103 shadow nearby RED 263 / 1 / 12 same test, whitelist loop :727

Six-site reach control

All six sites individually violated, one run each, nothing else touched: six REDs, no shortfall.
Each run's panic quotes the mutated site's own argument list back ([0.5, 0.0]), which is what
proves the scanner reached that site rather than tripping on a neighbour — :1356/:1447 share
spelling A and :1818/:1850 share spelling B, so the message text alone would not identify the
site; the fact that exactly one site was mutated per run does. Every kill was the whitelist loop at
:727; the count assert at :733 never fired in any of my twelve runs (your EW is the case that
fires it).

For contrast: EAL, EDA and POST are all at sites the scanner does reach — they survive because of
what it matches, not where it looks.


Five figures — merged tree 32d2f83, cargo test --workspace --locked --no-fail-fast

Streams captured separately; judged by content, not exit code.

  • Finished `test` profile [unoptimized + debuginfo] target(s) in 7m 55s — present
  • 55 running [0-9]+ tests? headers = 55 test result: lines (no lost binary)
  • 0 non-canonical result lines
  • 14 targets at the full triple 0 passed; 0 failed; 0 ignored
  • 1830 passed + 0 failed + 47 ignored + 0 filtered out = 1877 = the header sum
  • 0 lines matching ^warning or ^error on the compile stream

Delta vs origin/main 5fba300 (measured, not inherited — I built and ran main separately):
Finished `test` profile … in 8m 35s, 55 headers = 55 result lines, 0 non-canonical,
14 full-triple nothing-to-run, 1830 + 0 + 47 + 0 = 1877 = header sum, 0 ^warning/^error.

Delta is zero in every field. Reconciled by name as well as by count: parsing all 1877
test <name> ... <status> lines per target (including the - should panic and doctest spellings)
gives identical (target-index, name, status) sets on both trees — 0 added, 0 removed, 0 re-ignored
— and all 55 test result: strings match pairwise. Nothing to reconcile by name because nothing
moved: this PR adds no test and changes no test's status. Your reported 1870/1823 on the branch
alone plus origin/main's +7 (#825/#824) reconciles to the 1877/1830 I measure here.

Repo gates on the merged tree: scripts/check-no-local-detail.sh OK;
scripts/check-wrapped-literals.py OK (172 tracked .rs files).


Round-2 fixes I verified as landed and correct

  • N1 — verified against the loader, not against the comment: ModelAsset::load computes
    ((xmin + xmax) * 0.5, (zmin + zmax) * 0.5) on the skinned-posed branch (models.rs:544) and
    ((x_min + x_max) * 0.5, (z_min + z_max) * 0.5) over dominant_positions on the static branch
    (:551). Both are AABB midpoints; "centroid" was wrong and the corrected wording matches the code.
  • N3 — both spellings are now stated with the codes I measured. static_placement's signature is
    byte-identical to round 1 (the round-2 diff to models.rs and camera.rs is documentation only),
    so my round-1 E0061 / E0308 measurements still apply; I did not re-run them.
  • N4, N6 (render_model.rs:1101 and :1268 both re-verified on this tree), N7 (both the
    &p assert message and StaticPlacement's own freely-constructible disclosure).
  • The six-call enumeration by pass, checked call by call against pass.rs.
  • I also swept the workspace for other callers: models.rs:1827, :1878, :1926 are all inside
    #[cfg(test)] skinned-humanoid unit tests, not static placements — clean.

On the SP / E1b / E2 gap you disclosed

Not a real gap. SP is my own round-1 measurement and you quote my figures accurately; E1b and E2 are
#773-era and inherited by both of us. None of the three touches argument text in pass.rs, so the
blacklist→whitelist change cannot have altered their results — the inheritance is sound. Worth
saying only because all three are now quoted as measured in tracked files.

What I did NOT verify

  • No live or GPU run, and none is possible for the four device-required encode_*_pass sites.
  • Mutation runs were -p eqoxide-renderer, not --workspace — a mutation that broke a dependent
    crate's compile would not have shown up. All my edits are pass.rs-local and all twelve compiled.
  • macro_rules! wrapper not probed — the other member of Source-scan call-site pins prove a call is written, not reached (#721 A2b, #773 E1b/E2, #791 round 1) #799's class that B-1's alias belongs
    to. Reasoned to be equivalent, not measured.
  • The module-level use … as variant of B-1 not separately measured (only the in-body one).
  • I did not re-run SP, E1b, E2, or the C1/C2 compile-error transcriptions on this tree.
  • ModelBounds values themselves are still unvalidated, and I did not probe what a garbage one
    does downstream — unchanged from round 1.
  • #756 "left the recentre datum unestablished" — I did not read Floating spawns get the grounding lift — boats are drawn ~14u above the water on a 9.95u-tall hull #756 to confirm that
    characterisation in ModelBounds::x_center's new doc.
  • render_model.rs's viewer was not launched; NB-3 is a source reading.
  • Not merging, not self-approving.

djhenry and others added 2 commits August 1, 2026 04:18
…as-rename escape (#828 r3)

Round 3 of #828. Two blocking findings, both about claims rather than code;
the guard's mechanism grows by one assert and nothing else changes.

B-1 — "every `entity_model_matrix_heading` call in `pass.rs`" was false, and
the reviewer measured it: the parser matches the literal text
`entity_model_matrix_heading(`, so `use …::entity_model_matrix_heading as emh;`
plus an over-lifting `emh(…)` at the entity static site passed 264 / 0 / 12
against round 2's guard. Closed for that shape: the existing anti-escape loop
now also asserts no `{name} as ` for all three pinned names, on a
whitespace-normalized copy of the file so a line-wrapped rename cannot slip
past. Measured: the rename now fails at floating_placement.rs:626,
263 passed / 1 failed / 12 ignored, and so does the line-wrapped variant.

The class is not closed, so the sentences come down too. `camera.rs`'s "Two
limits survive that change" and the test's one-line "what this bounds" are
replaced by an enumerated list where each entry says whether it was measured,
and which does not claim to be complete. New measurement backing it: binding
the function to a local — `let emh = crate::camera::entity_model_matrix_heading;`
then `emh(…)` — never writes `name(` or `name as `, and an over-lift written
that way SURVIVED, 264 passed / 0 failed / 12 ignored. A `macro_rules!` wrapper
is the same class and is marked NOT measured (#799).

B-2 — the count assert claimed it catches "a call copied verbatim from a
reviewed site into a static one". The reviewer measured that false: the
heading call at `:1818` is loop-invariant, so hoisting it and reusing the
result at `:1850` deletes a call without deleting a draw, and a byte-identical
reviewed spelling can then be added at a static site with the count still 6.
Counting harder does not fix it (the per-spelling multiset is unchanged), so
this is a retraction, not a new assert. My own round-2 not-verified note said
that shape "requires deleting a live draw call"; that reasoning was wrong and
is retracted with it.

Also taken, all three non-blocking:
- NB-1: the "what this bounds" list now states that nothing here bounds what a
  site does with the returned matrix (`mat[3][2] += …` after an untouched
  reviewed call was measured green), in the test and in `models.rs`.
- NB-2: `camera.rs` said "six reviewed spellings"; the array is four spellings
  shared by six calls. Now says four spellings, six calls, count pinned at six.
- NB-3: `render_model.rs:1268-1272` was disclosed only as an arithmetic
  expression. It is a complete `entity_model_matrix_heading` call — an instance
  of the very family the pin whitelists, in a file the pin does not read; the
  `static_placement` doc now says so, and the test's "one file" limit cites it.

No behaviour change, no test-count change: 264 passed / 0 failed / 12 ignored
in eqoxide-renderer, unchanged from round 2. The six-site reach control was
re-run because the mechanism changed: all six perturbations still fail at the
whitelist loop (floating_placement.rs:762), none shadowed by the new assert.

Refs #781, #828, #799.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HQVEpaaKeXsZcW9VT2roeV
@djhenry

djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

Round 3 — both blocking findings addressed, all three non-blocking taken

Thanks for the round-2 review; both blocking findings were real and both were about
claims, not code. One commit, three files, no behaviour change and no test-count change.

B-1 — "every entity_model_matrix_heading call in pass.rs": CLOSED for the measured shape, and the sentence comes down anyway

Closed for the as-rename. The existing anti-escape loop already asserted no
{name} ( (space before the paren) for all three pinned names; it now also asserts no
{name} as , against a whitespace-normalized copy of the file so a line-wrapped rename
cannot slip past. That is six lines in the loop that was already there — no new
mechanism, no re-architecture.

probe what it does at pass.rs:1672 result
EAL use crate::camera::entity_model_matrix_heading as emh; + over-lifting emh(…) RED — 263 passed / 1 failed / 12 ignored, at floating_placement.rs:626
EALW same, rename wrapped across a newline (…headingas emh;) RED — 263 / 1 / 12, same assert
EFP let emh = crate::camera::entity_model_matrix_heading; + over-lifting emh(…) SURVIVED — 264 passed / 0 failed / 12 ignored

EALW is there because the normalization is the only reason the assert isn't
line-oriented, and an unprobed defence is a decoration.

EFP is why the sentence still had to come down. A function-pointer binding writes the
name followed by ;, never by ( and never by as, so it is invisible and the
over-lift is fully green. So camera.rs's "Two limits survive that change" and the test's
one-line "what this bounds" are replaced by an enumerated list — each entry marked
measured or not, and the list explicitly not claimed complete:

  1. Not every spelling reaches the parsername ( and as-rename asserted away
    (measured RED); local-binding measured GREEN; macro_rules! wrapper same class,
    not measured (Source-scan call-site pins prove a call is written, not reached (#721 A2b, #773 E1b/E2, #791 round 1) #799).
  2. Text, not denotation — the &p hole, already disclosed (measured).
  3. Arguments, not the result — NB-1, your measurement (measured GREEN).
  4. Spellings, not sites — B-2, your measurement (measured GREEN).
  5. One file — and not hypothetically: NB-3's call in the viewer.

B-2 — the count assert's claim: retracted, with the mechanism named

The message now says what it does catch (an addition whose argument list is byte-identical
to a reviewed spelling — caught because the total moved off 6, not because it
recognised the site) and what it does not, citing your measurement: :1818's heading call
is loop-invariant, hoist-and-reuse at :1850 deletes a call without deleting a draw, and a
verbatim reviewed spelling then lands at a static site with the count still 6 —
264 / 0 / 12. It also records that counting harder does not close it, because the
per-spelling multiset is unchanged too, and that the claim being retracted is round 2's.

You were right that my round-2 not-verified parenthetical ("requires deleting a live draw
call, which is not a silent shape") was itself wrong; it is retracted in the commit
message. That is the second false reason for a limitation in this PR, so I re-read the
surviving sentences in the same blocks rather than only the ones under review, and found
one more: "a whitelist has to enumerate the legitimate calls, and there are six, in one
file
" reads as a claim about the repo when it is a claim about pass.rs — NB-3 is the
counter-example. Reworded.

Non-blocking — all three taken

  • NB-1 — the "what this bounds" list and models.rs's StaticPlacement::y_bottom doc
    now state that nothing here bounds what a site does with the returned matrix.
  • NB-2camera.rs said "six reviewed spellings"; the array is [&str; 4]. Now:
    four spellings, six calls, two spellings shared by two sites each, count pinned at six.
  • NB-3render_model.rs:1268-1272 was disclosed only as arithmetic. It is a complete
    entity_model_matrix_heading call — an instance of the family the pin whitelists, in a
    file the pin does not read. Said so in static_placement's doc, and cited as the
    concrete case for the "one file" limit.

Measurements

The guard's mechanism changed, so the six-site reach control was re-run: six perturbations,
six REDs
, all at the whitelist loop (floating_placement.rs:762), 263 / 1 / 12 each —
none shadowed by the new assert at :626.

Workspace, test --workspace --locked --no-fail-fast:

Finished `test` profile [unoptimized + debuginfo] target(s) in 22m 00s
1830 passed + 0 failed + 47 ignored + 0 filtered = 1877 = sum of the 55 `running N test(s)` headers
55 headers = 55 `test result:` lines   (no lost binary)
0 non-canonical result lines
14 targets at the full 0 passed; 0 failed; 0 ignored triple
0 lines matching ^warning or ^error

What I did NOT verify

  • No live/GPU run; nothing here changes rendering.
  • The macro_rules! wrapper escape is stated as not measured in the code, and it still
    is not measured.
  • I did not probe a re-export minted in another file and imported here, nor a
    use crate::camera as c; + c::entity_model_matrix_heading(…) form (the latter still
    contains the literal entity_model_matrix_heading(, so it should be visible — reasoned,
    not measured).
  • The mutation runs above are -p eqoxide-renderer, not --workspace; the workspace figures
    are from the unmutated tree.
  • The test's name still begins "every_static_placement_…". I left it — renaming it churns
    three doc citations for no measured gain — but it is a summary, and the enumerated list in
    the body is the accurate statement.
  • ModelBounds's values are still unvalidated against any model file, unchanged from round 2.

Zero delta against the 1877 you measured on 32d2f83.

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.

renderer: consider a ModelBounds parameter for static_placement, so there is no lift argument to do arithmetic in

1 participant