Skip to content

fix(#811,#812,#813,#814): detect the joint palette structurally, delimit its token, key downgrades by file, and floor the cap - #820

Merged
djhenry merged 5 commits into
mainfrom
fix/jointcap-followups-811-814
Aug 1, 2026
Merged

fix(#811,#812,#813,#814): detect the joint palette structurally, delimit its token, key downgrades by file, and floor the cap#820
djhenry merged 5 commits into
mainfrom
fix/jointcap-followups-811-814

Conversation

@djhenry

@djhenry djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Four follow-ups left behind by #809. They are in one PR because they touch the same four files (pipeline.rs, the three palette shaders, renderer.rs, joint_cap_single_source.rs).

Closes #811.
Closes #812.
Closes #813.
Closes #814.

Round 2. The first review returned CHANGES REQUESTED with five findings, all of them tracked files stating something false rather than defects in the code. Every one is fixed below, including the four marked non-blocking. Sentences this document previously got wrong are marked [corrected] so a returning reader can find them.


#811 — the palette guard was keyed on a struct NAME

joint_palette_shaders() selected its corpus with src.contains("JointMatrices"). The IR check that actually resolves palette lengths — walking naga's type arena for uniform-address-space struct members whose type is a fixed-length array<mat4x4<f32>, N> — was already alias-immune by construction, but it only ever ran over the name-derived subset. The set under test was chosen by the property being tested.

Fix: the IR check now runs over every shader the corpus walk discovers. Name matching is gone from the detection path entirely; palette_bearing_shaders_are_exactly_the_expected_three now derives that set structurally and asserts it, so a fourth palette-bearing file fails rather than being ignored.

[corrected] The corpus walk recurses. shader_corpus() was a single non-recursive read_dir while its docstring said "every .wgsl under src/shaders/". The reviewer planted a palette-bearing decoy one directory deep and nothing named it, in the same run that named a top-level one. It now walks recursively and keys by the /-separated path relative to src/shaders/. Measured after the fix: the nested decoy is reported as nested/decoy_c.wgsl (RC-M4 below).

[corrected] every_palette_length_tracks_the_substituted_token now really is whole-corpus, and uses two sentinels. Two sentences gave this test whole-corpus scope while it iterated the three-name const list. It now iterates the discovered corpus; the const list survives only as the end-of-test positive control (a corpus that lost all three palette shaders would otherwise satisfy the loop vacuously).

The second sentinel is not belt-and-braces, and I did not reason my way to it — I measured it. With a single sentinel of 77, a planted decoy declaring array < JMat2 , 77 > slipped past this test, because a shader that hardcodes exactly the sentinel is indistinguishable from one that tracks the token. SENTINELS: [u32; 2] = [77, 91] closes that: a length derived from ${JOINT_CAP} equals both under two substitutions, a length written into the shader can equal at most one.

[corrected] discovered_corpus_is_not_silently_truncated derived both sides of its equality from the walk it was certifying. The previous version of this section said it "asserts every walked file was actually parsed, so a scanner that stops early fails instead of measuring green over a shrinking corpus." That was false, and the reviewer measured it false: with a 99-length uniform mat4x4 decoy on disk and the walk truncated to three files, this test file stayed 13 passed / 0 failed. corpus_palette_lengths() is a total map over shader_corpus(), so a short walk shrank both sides of the assert_eq! together, and the only other defence was a count floor of EXPECTED_JOINT_SHADERS.len() = 3 against a real corpus of 11. That is the #778 shape reproduced inside the control written to prevent #778.

The replacement compares the walk against an enumeration this test file does not produce: the shader file names pipeline.rs compiles in via include_str!, scanned out of its source text by shaders_pipeline_includes(). Two structural properties make that a sound oracle rather than the same circularity under a new name:

  • include_str! takes a literal path. It cannot be fed a runtime value, so the list provably does not route through shader_corpus().
  • Every name in it provably exists on disk — the crate would not compile if one did not. So a name the walk did not return means the walk missed a file that is definitely there.

The oracle gets its own reach control (MIN_PIPELINE_INCLUDED_SHADERS = 11, so a scan bug cannot shrink it into a vacuous superset check), and that floor's number was measured with a different tool: grep -o 'include_str!("shaders/[^"]*"' crates/eqoxide-renderer/src/pipeline.rs | sort -u | wc -l → 11 distinct names (12 occurrences; character.wgsl twice).

[corrected, round 3] The oracle had the same disease one level down. Its scan took include_str! hits from pipeline.rs including hits inside comments, while its docstring claimed the .wgsl extension filter prevented phantoms. The reviewer reasoned that out; I measured both consequences, and the second was not predicted by either of us:

  • False RED// include_str!("shaders/ghost.wgsl") planted as a comment made the test fail with the shader walk never returned ["ghost.wgsl"], an alarm about a file that never existed.
  • False GREEN — a phantom inflates included.len(), which is exactly what the floor counts. An orphan .wgsl on disk, a comment naming it, and the scan silently dropping one genuinely-included shader measured 14 passed / 0 failed. The floor was padded by prose, and the superset check never looked at the dropped name — a name the scan loses is a name the scan cannot check.

The scan now skips hits inside comments via comment_spanscomments() refactored to byte spans so the file still has exactly one comment scanner, not two. the_include_scan_ignores_comments_but_not_code pins both directions against a synthetic source, so it keeps testing the rule after pipeline.rs changes and fails without any plant.

Both directions of the new assertion were planted and run at the call site, per the #818 lesson that a mutation inside the thing both sides read is not a test of the guard: truncating shader_corpus() itself (RC-M2), and the reviewer's exact truncate-plus-decoy combination that used to measure green (RC-M1). Both now go RED.

Honest limitations, stated in the code's own docstrings:

  • A walk that drops only files no include_str! names — a planted decoy, or a .wgsl used by a dev bin alone — is still invisible to this control. Nothing outside the disk knows such a file exists, so no oracle in this file can miss it loudly. This is a real remaining hole, narrowed rather than closed.
  • The legacy text sweep no_shader_writes_a_numeric_palette_length is defeated by a type alias and by the whitespace form array < mat4x4<f32>, 99 >. It is retained only because it covers address spaces the IR palette check deliberately ignores (non-uniform). Mutations 811-M4 and RC-M5 both show it staying green while the IR checks go red.

#812 — the substitution was not word-boundary safe

src.replace("JOINT_CAP", "128") rewrote any identifier with JOINT_CAP as a prefix into a number, and rewrote every comment mentioning the cap into self-contradicting prose. The token is now ${JOINT_CAP}. $, { and } cannot appear in a WGSL identifier, so the token cannot be a substring of one — that is a property of the delimiters, not of the current corpus, and the test asserts it against the WGSL identifier grammar rather than against today's shaders.

[corrected] The delimiters do not make prose safe, and pipeline.rs said they did. Its doc claimed the token "touches no identifier and no prose". The identifier half is structural and holds; the prose half is simply false — the reviewer measured "// the ${JOINT_CAP} placeholder""// the 128 placeholder". Delimiting did not fix that and was never going to. The doc now says so explicitly, and separates the two claims by how each is true: identifiers by construction, prose by a guard.

That guard also had a hole. substitution_leaves_every_shader_comment_byte_identical covered line comments; a block comment spelling the token was rewritten silently. Rather than file that as a separate issue and widen the gap's lifetime, I closed it: a comments() extractor now handles // and nesting /* … */, the test compares every comment byte of both forms across the substitution, and the extractor itself carries a self-check (comment_extractor_finds_both_forms_including_nesting) so a broken extractor fails rather than passing over an empty set. The test also asserts its own premise — that WGSL has no string literals, so quote characters outside comments cannot smuggle token text past it.

Measured after the fix (RC-M6): a block comment spelling the token in character.wgsl now fails loudly with left: "/* … the ${JOINT_CAP} placeholder. */" / right: "/* … the 128 placeholder. */".

#813(label, gender) double-reported one file

The downgrade map keyed on (label, gender), so an archetype with no _f variant produced two entries for the one GLB both genders load. The key is now the file name actually loaded.

The path resolution that decides which file that is has been lifted out of ensure_character_model into a public resolve_character_model_path, because #813 is about the (label, gender) → file relationship: a test carrying its own transcription of that rule would grade its own restatement. The tests drive the real resolver against a scratch asset directory.

entry_count_always_equals_distinct_file_count is a property test over the powerset of _f-variant presence, not an example. [corrected] its docstring now states its quantifier honestly: one asset root, where distinct path and distinct base name coincide. They do not coincide in general.

[corrected] "two loads of two files cannot produce one, because the key IS the file" was wrong. The key is file_name(), and assets_path is pub, so two files sharing a base name under two roots collapse into one entry — the later load silently overwrites the earlier. I did not delete the sentence and move on; I split the paragraph by direction and pinned the false half with a test. One direction stays structural (two loads of one file cannot produce two entries, because downgrade_key is a pure function of the path). The other is now two_roots_with_the_same_basename_collide_into_one_entry, which asserts the collision happens and that the 140-joint rig under the other root is not reported at all. The doc also says why the base name is still the right key: this map is agent-facing over HTTP, and an absolute path is unstable across machines and is local detail this project does not publish.

#814 — nothing was checking the cap's VALUE

Unifying the cap removed the only loud check on it. A too-small cap does not error — it reclassifies real rigs as SkinFit::ExceedsCap and renders them unskinned. Silent downgrade, confident green.

The bound was measured, not copied. I read skins[].joints.len() out of the JSON chunk of every character GLB in the shipped asset set. Distribution: 127 (widest), 110, 109, 108, 106, 105, 104, 8. MAX_MEASURED_CHARACTER_RIG_JOINTS = 127; the docstring names the widest rig's file, the full distribution, and the date the corpus was measured, so a future reader can tell whether the number is stale. The reviewer independently re-derived it (136 .glb, 49 skinned, max 127 on race_pcfroglok.glb) and got the same figure.

Enforced twice, deliberately:

  • a const _: () = assert!(JOINT_CAP >= MAX_MEASURED_CHARACTER_RIG_JOINTS, ...) that fails the build, and
  • a non-ignored runtime test, so short-circuiting the const assert still goes red.

Mutation table — round 1

Every row was planted and run. Green control on the round-1 tree: joint_cap_single_source 13 passed / 0 failed, skin_cap_selection 14 passed / 0 failed / 1 ignored. The pass counts in this table are round-1 counts; round 2 adds five and six tests respectively, so the same mutations now report higher totals.

# Mutation Result Killed by
811-M1 The issue's exact reproduction decoy_b.wgsl (alias JMat = mat4x4<f32>; + array<JMat, 99> in a uniform struct) dropped into the corpus RED 10/3 every_uniform_mat4_palette_in_the_corpus_is_exactly_joint_cap (Offenders: ["decoy_b.wgsl: palette length 99"]), palette_bearing_shaders_are_exactly_the_expected_three, raw_palette_shader_sources_do_not_parse_without_substitution
811-M2 Rename JointMatricesBonePaletteRenamed in one shader GREEN 13/0 (intended) nothing — detection is no longer name-based. On the pre-fix tree this same rename went RED, a false alarm about a correct palette.
811-M3 Move a palette from var<uniform> to var<storage, read> RED 11/2 palette_bearing_shaders_are_exactly_the_expected_three, every_palette_length_tracks_the_substituted_token
811-M4 Palette rewritten as alias + whitespace + the correct value: alias JMat = mat4x4<f32>; struct JointMatrices { mats: array < JMat , 128 > }; RED 11/2 every_palette_length_tracks_the_substituted_token; no_shader_writes_a_numeric_palette_length stayed green — the documented gap, demonstrated.
812-M1 Full revert to the bare token in pipeline.rs + all three shaders, plus const JOINT_CAP_SCALE: f32 = 1.0; RED 5/8, reproducing the issue's exact error character_skinned.wgsl: WGSL failed to parse: expected identifier, found '128' the_joint_cap_token_cannot_occur_inside_an_identifier, substitution_cannot_alter_an_identifier_that_contains_the_word_joint_cap (left: "const 128: u32 = 1u;"), substitution_leaves_every_shader_comment_byte_identical
812-M2 Green control: the same JOINT_CAP_SCALE identifier added to the fixed tree alone GREEN 13/0 — the identifier is now harmless, which is the point
813-M1 downgrade_key mutated back to the pre-#798 label key (strip _f) RED 11/3 entry_count_always_equals_distinct_file_count, the_downgrade_key_is_the_file_name, two_genders_that_load_two_different_files_are_reported_separately
813-M2 resolve_character_model_path mutated to drop the .exists() fallback RED 12/2 gender_1_falls_back_to_the_male_glb_when_no_f_variant_exists, two_genders_that_load_the_same_file_are_reported_once
814-M1 JOINT_CAP = 126 BUILD FAILS: error[E0080]: evaluation panicked: JOINT_CAP is below the widest character rig that ships … the const _: () = assert!
814-M2 JOINT_CAP = 126 and the const assert short-circuited with `true `

Pre-fix controls, run on a throwaway worktree at the base commit so the "measured green" claim in #811 is my own measurement and not a restatement of the issue:

  • decoy_b.wgsl on the base tree → 9 passed / 0 failed. The reproduction ships unguarded, exactly as reported.
  • The JointMatrices rename on the base tree → RED 8/1 on joint_palette_shaders_are_exactly_the_expected_three, with the palette still present and still correct. The old guard alarmed on the name, not the palette.

Mutation table — round 2

Green control on the round-2 tree, unmutated: joint_cap_single_source 14 passed / 0 failed, skin_cap_selection 15 passed / 0 failed / 1 ignored.

# Mutation Result What it proves
RC-M1 The reviewer's exact reproduction: zz_palette_data.wgsl decoy (alias form, length 99) and shader_corpus() truncated to the three expected files RED 13/1the shader walk never returned ["billboard.wgsl", "character.wgsl", "nav_debug.wgsl", "shadow_masked_instanced.wgsl", "sky.wgsl", "weather.wgsl", "zone.wgsl", "zone_instanced.wgsl"] … Walked: ["character_skinned.wgsl", "shadow.wgsl", "skin_probe.wgsl"] the combination that measured 13 passed / 0 failed before no longer does
RC-M2 Call-site mutation alone: shader_corpus() truncated to three files, nothing planted RED 13/1, same message the guard fires on a short walk by itself, not only when a decoy happens to be visible
RC-M2b Neither planted: unmutated walk GREEN 14/0 the reach control's other direction — it is not simply always red
RC-M3 Sabotage the oracle: if out.len() >= 3 { break; } inside shaders_pipeline_includes RED 13/1the pipeline.rs include_str! scan found 3 shader file name(s), fewer than the 11 measured to be there — the corpus ORACLE is truncated, so the superset check below would pass vacuously. Found: {"character.wgsl", "skin_probe.wgsl", "zone.wgsl"} the new second scanner is itself watched, one level up
RC-M1b The 99-length decoy alone, walk untouched RED 10/4Offenders: ["zz_palette_data.wgsl: palette length 99"] four failures where round 1 had three, because the token check is now whole-corpus
RC-M4 The reviewer's nested decoy at src/shaders/nested/decoy_c.wgsl RED 10/4nested/decoy_c.wgsl: a uniform mat4 palette is 77 when the token is substituted with 91 the recursive walk reaches it, and it is named — previously nothing named it. Also the mutation that exposed the single-sentinel hole: with one sentinel of 77 this decoy's hardcoded 77 slipped past.
RC-M5 A fourth top-level shader with an alias palette hardcoded to the correct 128 RED 11/3zz_fourth_palette.wgsl: a uniform mat4 palette is 128 when the token is substituted with 77 a palette that is right today but no longer tracks the cap still fails. every_uniform_mat4_palette_…_is_exactly_joint_cap correctly stayed green (128 is right) and no_shader_writes_a_numeric_palette_length stayed green — the documented text-sweep gap, demonstrated again.
RC-M6 A block comment spelling ${JOINT_CAP} appended to character.wgsl RED 13/1character.wgsl:62: pipeline::wgsl rewrote a comment (eqoxide#812), left: "/* … ${JOINT_CAP} placeholder. */" / right: "/* … 128 placeholder. */" the previously-silent block-comment rewrite is now loud
RC-M7 downgrade_key mutated to return the whole path RED 8/7two_roots_with_the_same_basename_collide_into_one_entry fails with left: 2 / right: 1 the newly-documented collision limit is measured, not a vacuous pin
RC-M8 a line comment in pipeline.rs spelling include_str!("shaders/ghost.wgsl") pre-fix RED 13/1 (false alarm) → post-fix GREEN 15/0 comments are no longer ingested
RC-M9 orphan .wgsl on disk + a comment naming it + the scan dropping zone.wgsl pre-fix GREEN 14/0 (false green) → post-fix RED 14/1, found 10 … fewer than the 11 the phantom-pads-the-floor composite, closed
RC-M10 the comment filter neutered, phantom present RED 13/2 the filter is load-bearing, and the synthetic pin fires without any plant
RC-M11 comment_spans made to swallow the whole file as one comment RED 11/4, found 0 shader file name(s) a mis-parsing comment scanner is loud, not silent

Workspace test figures

Full workspace, --locked --no-fail-fast, stdout and stderr captured separately. The branch column is the tree after git merge origin/main, so both columns describe commits that exist.

base 7428809 this branch (merged)
Finished test profile in 9m 54s test profile in 6m 07s
running N tests? headers 55 55
test result: lines 55 55
headers == results yes yes
non-canonical (corrupt) lines 0 0
all-zero targets (field-anchored) 14 14
passed 1798 1809
failed 0 0
ignored 47 47
filtered 0 0
passed+failed+ignored+filtered 1845 1856
== header sum yes yes
^error|panicked|Killed in stderr 0 0

Round 3 caveat. The table above was measured on f1cd140. Round 3 adds exactly one test (joint_cap_single_source 14 → 15, measured at the binary level), so the current head's workspace total would be 1810 / 1857. That pair is arithmetic, not a measurement — the workspace suite was not re-run, at the orchestrator's instruction.

Delta: +11 passed / +0 targets / +0 ignored / 0 failed. Accounted for exactly, by running the two affected binaries at the base commit: joint_cap_single_source 9 → 14 (+5), skin_cap_selection 9 → 15 (+6), ignored 1 → 1. No test was deleted or newly ignored. The diff touches crates/eqoxide-renderer/ only, so no other target's count can have moved.

Live run

#812 changes the text wgpu actually receives, and naga validating a string is not proof a driver accepts it, so this part was run live on a release build (verified the running process was the new binary via its exe link and mtime before believing anything).

Zoned in and observed 6 distinct skinned models load and render through the shared palette path — 109, 104, 104, 38, 29 and 15 joints. 0 lines matching wgsl|shader|validation error|panicked|adapter error. 0 cap-downgrade / static-fallback lines. The only warnings present are pre-existing graphics-backend and UI ones unrelated to this change. Clean shutdown afterwards.

NOT verified

  • The live run was taken on the round-1 tree, not re-run for round 2. Round 2 changed test files, three doc paragraphs, and nothing that reaches the GPU except by way of pipeline::wgsl, whose body is byte-identical. That is a reasoned scope argument about which code moved, not a second live measurement, and it is marked as such.
  • No rig above 110 joints was exercised live. The widest rig I loaded in-session was 109. The 127-joint figure comes from reading the GLB skin chunks, not from rendering that model. If the asset set gains a wider rig, MAX_MEASURED_CHARACTER_RIG_JOINTS is stale and nothing in CI will notice — the const assert compares the cap against a number a human maintains.
  • One driver, one session. The live run proves a real driver accepts the substituted WGSL. It cannot discharge any "never/always/cannot" claim about other backends or drivers.
  • No workspace run and no live run on the round-3 head. That change is confined to the test binary; pipeline.rs is byte-identical to f1cd140, verified by md5sum against a copy taken before planting.
  • The Rust-string-literal misparse is not constructed. comment_spans was written for WGSL, which has no string literals. I measured that a maximally-wrong comment scanner is loud (RC-M11); I did not write a pipeline.rs string containing /* and watch that specific case.
  • The corpus oracle narrows the fix(#760): pin the health clock in test fixtures so a handler's HTTP status cannot depend on machine load #778 hole, it does not close it. A walk that drops only files no include_str! names stays invisible. Every shader that ships today is named, so today the two sets coincide; that is a fact about the current tree, not a property.
  • no_shader_writes_a_numeric_palette_length remains defeatable by a type alias and by the whitespace form. I did not fix it; I documented it and demonstrated the gap in 811-M4 and RC-M5.
  • The ${JOINT_CAP} identifier claim is grammar-level, not corpus-level. I assert $/{/} are not WGSL identifier characters. I did not exhaustively fuzz the substituter against arbitrary shader text.
  • The comment extractor is checked against the forms I planted (line, block, nested block) plus a premise assertion that WGSL has no string literals. It is not fuzzed, and a WGSL grammar change that introduced string literals would break the premise loudly rather than silently — but that is an assertion I wrote, not one I have watched fail against a real grammar change.
  • resolve_character_model_path is exercised against a scratch directory, not against the real asset tree.
  • The two-root collision is pinned as a unit test, not observed in a running client. assets_path is set once per renderer in the shipped flow, so I have no evidence the collision has ever occurred; the test proves it can, which is precisely the claim the old doc denied.
  • The player character was immobile for the whole live session due to an unrelated pre-existing movement hold.
  • No performance measurement of any kind was taken.

…mit its token, key downgrades by file, and floor the cap

#811: joint-palette detection was `src.contains("JointMatrices")` -- a struct NAME.
A shader declaring a fixed-length mat4x4 uniform palette under any other name, or
through a type alias, shipped unguarded and measured green. The IR check that
resolves uniform-address-space struct members through naga's type arena now runs
over the WHOLE discovered corpus instead of a name-derived subset, so the set under
test is no longer chosen by the thing being tested. A new test substitutes a
sentinel cap and asserts every palette length in the IR tracks it, which also
catches an alias-declared or whitespace-spread palette that happens to be the
correct value today.

#812: the substitution was `src.replace("JOINT_CAP", "128")`, which is not
word-boundary safe -- any identifier with `JOINT_CAP` as a prefix was corrupted
into a number, and every comment mentioning the cap was rewritten into
self-contradicting prose. The token is now the delimited `${JOINT_CAP}`; `$`, `{`
and `}` cannot appear in a WGSL identifier, so the token cannot be a substring of
one. Three tests pin this: the token cannot occur inside an identifier,
substitution cannot alter an identifier containing the bare word, and substitution
leaves every comment byte in every corpus shader identical.

#813: the downgrade map re-keyed on (label, gender), which double-reports one file
for archetypes that have no `_f` variant -- both genders load the same GLB. The key
is now the file name actually loaded, and the path resolution that decides which
file that is has been lifted out of `ensure_character_model` into
`resolve_character_model_path` so the tests drive the real resolver rather than a
transcription of it. A property test asserts entry count always equals distinct
file count over the powerset of `_f` presence.

#814: unifying the cap removed the only loud check on its VALUE, so a too-small cap
silently reclassified real rigs as ExceedsCap and rendered them unskinned. The
bound was measured from the shipped GLB corpus (widest rig 127 joints) and is now
enforced twice: a `const _: () = assert!` that fails the build, and a non-ignored
runtime test that names the measured distribution and its provenance.
@djhenry

djhenry commented Aug 1, 2026

Copy link
Copy Markdown
Owner Author

CHANGES REQUESTED

Independent adversarial review. I did not write this change. I worked in my own worktrees at the PR
head (6736287) and at the base (9e41178), planted every mutation below myself, and ran every
figure I quote. Nothing here is reasoned from reading.

The code is right. The structural detection works, the delimited token works, the file key works,
and the cap floor works — I re-derived #814's 127 from the GLB skin chunks myself and reproduced
both of the author's #814 mutations. What is wrong is one control's description: three sentences
in tracked files claim the #811 reach control detects a truncated corpus walk. It does not, and I
measured it not doing so with the issue's own reproduction sitting in the corpus. That is the
eqoxide#778 lesson restated as a property the control does not have, on the one guard whose entire
purpose is to prevent a repeat of eqoxide#778.


1. BLOCKING — the reach control does not detect a truncated walk; three tracked sentences say it does

Three places make the claim:

tests/joint_cap_single_source.rs header:

  • discovered_corpus_is_not_silently_truncated asserts the walk found a plausible corpus, that
    every file the palette checks name is inside it, and that every discovered file was actually
    parsed
    — so "the scanner couldn't see that file" fails loudly instead of passing quietly.

its own docstring:

on that issue a scanner silently stopped at ~12% of its corpus and reported clean, and every
mutation probe happened to land in the window it could still see. A truncated walk fails on the
count
, a covered file that fell out of the walk fails on the membership check, and a file the
parse pass dropped fails on the third.

and the PR body:

discovered_corpus_is_not_silently_truncated gained a reach control: it now asserts every walked
file was actually parsed, so a scanner that stops early fails instead of measuring green over a
shrinking corpus.

Why it cannot hold

The control derives both of its sets from the same walk it is certifying:

let corpus = shader_corpus();
assert!(corpus.len() >= EXPECTED_JOINT_SHADERS.len(), ...);   // floor is 3
for name in EXPECTED_JOINT_SHADERS { assert!(corpus.contains_key(name), ...); }
let parsed = corpus_palette_lengths();                        // == shader_corpus().iter().map(..)
assert_eq!(parsed.keys(), corpus.keys(), ...);

corpus_palette_lengths() is a total map over shader_corpus(). If shader_corpus() itself stops
short, both sides shrink together and the third assertion agrees. The only defence left is the count
floor — and the floor is EXPECTED_JOINT_SHADERS.len() = 3, against a real corpus of 11
shader files (12 with a decoy). A walk truncated to 25% of its corpus clears it.

Measured (RV-M3)

Planted, in my worktree at the PR head:

  1. src/shaders/zz_palette_data.wgsl — the issue's exact reproduction, at a location the author did
    not use (sorts last, name does not read as a shader):
alias JMat = mat4x4<f32>;
struct DecoyPalette { mats: array<JMat, 99> };
@group(0) @binding(0) var<uniform> decoy: DecoyPalette;
  1. a walk that silently stops short, in shader_corpus():
// RV-M3: simulate the eqoxide#778 failure mode — a walk that silently stops short of its corpus.
let keep: Vec<String> = EXPECTED_JOINT_SHADERS.iter().map(|s| s.to_string()).collect();
out.retain(|k, _| keep.contains(k));

Result — fully green, with the violation present on disk:

EXIT=0
running 13 tests
test multidigit_literal_predicate_discriminates ... ok
test pad_joint_palette_is_always_exactly_joint_cap_long ... ok
test palette_and_uniform_buffer_are_the_same_size ... ok
test no_draw_site_states_a_joint_palette_length ... ok
test no_shader_writes_a_numeric_palette_length ... ok
test substitution_cannot_alter_an_identifier_that_contains_the_word_joint_cap ... ok
test substitution_leaves_every_shader_comment_byte_identical ... ok
test every_palette_length_tracks_the_substituted_token ... ok
test palette_bearing_shaders_are_exactly_the_expected_three ... ok
test raw_palette_shader_sources_do_not_parse_without_substitution ... ok
test the_joint_cap_token_cannot_occur_inside_an_identifier ... ok
test discovered_corpus_is_not_silently_truncated ... ok
test every_uniform_mat4_palette_in_the_corpus_is_exactly_joint_cap ... ok
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.48s
--- decoy still present? ---
crates/eqoxide-renderer/src/shaders/nested/decoy_c.wgsl
crates/eqoxide-renderer/src/shaders/zz_palette_data.wgsl

That is 13/0 — indistinguishable from the clean control — with a 99-length uniform mat4x4 palette
in the corpus directory. It is the #778 shape reproduced against the control written to prevent it.

What the control does catch (RV-M9, so this is a scoped claim, not a dismissal)

I mutated only the parse stage, leaving the walk intact
(.filter(|(name, _)| name.as_str() != "sky.wgsl") inside corpus_palette_lengths):

---- discovered_corpus_is_not_silently_truncated stdout ----
assertion `left == right` failed: the palette scan parsed a different set of files than the walk discovered — a file that is walked but never parsed is a hole that reports clean
  left: ["billboard.wgsl", "character.wgsl", "character_skinned.wgsl", "nav_debug.wgsl", "shadow.wgsl", "shadow_masked_instanced.wgsl", "skin_probe.wgsl", "weather.wgsl", "zone.wgsl", "zone_instanced.wgsl"]
 right: ["billboard.wgsl", "character.wgsl", "character_skinned.wgsl", "nav_debug.wgsl", "shadow.wgsl", "shadow_masked_instanced.wgsl", "skin_probe.wgsl", "sky.wgsl", "weather.wgsl", "zone.wgsl", "zone_instanced.wgsl"]

So the control detects walk-vs-parse divergence. It does not detect walk truncation, which is the
thing all three sentences name and the thing #778 actually was.

Suggested fix (small, and it closes the gap rather than only correcting the prose)

There is already an independent enumeration of the corpus in the tree — pipeline.rs names its
shaders as literals:

$ grep -c 'include_str!("shaders/' crates/eqoxide-renderer/src/pipeline.rs
12

(12 occurrences, 11 distinct files — character.wgsl appears twice.) Asserting that the walked
corpus is a superset of the shader file names pipeline.rs includes gives the control a source
of truth it does not itself produce, and a truncated walk then fails on membership. Either do that,
or reduce the three sentences to what the control proves: a file the parse pass dropped fails,
and the corpus is not smaller than three files.


2. NON-BLOCKING — the walk is not recursive, but its docstring reads as if it is

shader_corpus:

/// Every .wgsl under src/shaders/, read from DISK at test time (filename → raw source).

It is a single non-recursive read_dir. The header is accurate (src/shaders/*.wgsl); this
docstring is not. Measured (RV-M2): I planted, at the PR head, a second decoy one directory deep —

src/shaders/nested/decoy_c.wgsl:

alias JMat2 = mat4x4<f32>;
struct NestedPalette { mats: array < JMat2 , 77 > };
@group(0) @binding(0) var<uniform> nested_decoy: NestedPalette;

In the same run that caught the top-level decoy (RV-M1, below), nothing named the nested file:

test result: FAILED. 10 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
...
Offenders: [
    "zz_palette_data.wgsl: palette length 99",
]
  left: ["character_skinned.wgsl", "shadow.wgsl", "skin_probe.wgsl", "zz_palette_data.wgsl"]
 right: ["character_skinned.wgsl", "shadow.wgsl", "skin_probe.wgsl"]

No subdirectory exists today, so this is a wording fix ("every .wgsl directly in src/shaders/"),
not a hole in what ships.


3. NON-BLOCKING — two sentences describe every_palette_length_tracks_the_substituted_token as whole-corpus; it iterates a 3-name list

Header:

…and every check below runs it over every .wgsl in the discovered corpus rather than over
a list.

and no_shader_writes_a_numeric_palette_length's docstring:

Every uniform-space palette, in any spelling, is covered by
every_palette_length_tracks_the_substituted_token instead.

The test body:

let corpus = shader_corpus();
let mut checked = 0usize;
for name in EXPECTED_JOINT_SHADERS {          // <- the list
    let raw = corpus.get(name).unwrap_or_else(|| panic!("{name} missing from the corpus"));

EXPECTED_JOINT_SHADERS is const [&str; 3]. A uniform palette in a fourth shader is not covered
by that test. Detection is not actually lost — I measured that a fourth palette shader is caught
by two other tests (RV-M1: palette_bearing_shaders_are_exactly_the_expected_three and
raw_palette_shader_sources_do_not_parse_without_substitution both go red) — so this is a doc
accuracy finding, not a gap. But "every check below" and "any spelling … covered by
[that test]" are both false as written.


4. NON-BLOCKING — "Since the token is delimited, this substitution touches no identifier and no prose"

pipeline.rs. The identifier half is a genuine structural property and I confirmed it. The prose half
is neither structural nor a consequence of delimiting: prose that spells the token is still rewritten.
I fuzzed src.replace("${JOINT_CAP}", "128") over the shapes in the issue and around it:

"${JOINT_CAP}"                   -> "128"
"${JOINT_CAP"                    -> "${JOINT_CAP"          (loud: `$` is not a WGSL token char)
"$${JOINT_CAP}"                  -> "$128"                 (loud)
"${JOINT_CAP}}"                  -> "128}"                 (loud in any code context)
"${${JOINT_CAP}}"                -> "${128}"               (loud)
"${JOINT_CAP }"                  -> "${JOINT_CAP }"        (loud)
"$ {JOINT_CAP}"                  -> "$ {JOINT_CAP}"        (loud)
"// the ${JOINT_CAP} placeholder" -> "// the 128 placeholder"        <-- SILENT
"/* block ${JOINT_CAP} comment */" -> "/* block 128 comment */"      <-- SILENT
"JOINT_CAP_SCALE"                -> "JOINT_CAP_SCALE"      (the #812 fix, holding)
"MAX_JOINT_CAP"                  -> "MAX_JOINT_CAP"        (holding)
"array<mat4x4<f32>, ${JOINT_CAP}>" -> "array<mat4x4<f32>, 128>"
"${JOINT_CAP}${JOINT_CAP}"       -> "128128"

Every partial/nested/doubled form is a loud parse failure — good, nothing silent there. The two
silent cases are comments. For line comments the corpus guard fires, which I verified rather than
assumed (RV-M7 — I appended // RV-M7: a comment that legitimately spells the ${JOINT_CAP} placeholder.
to character.wgsl):

---- substitution_leaves_every_shader_comment_byte_identical stdout ----
assertion `left == right` failed: character.wgsl:63: pipeline::wgsl rewrote a comment (eqoxide#812)
  left: "// RV-M7: a comment that legitimately spells the ${JOINT_CAP} placeholder."
 right: "// RV-M7: a comment that legitimately spells the 128 placeholder."

For block comments it is silent and undetected — the test's own docstring discloses that it does
not model block comments, and none exist in the corpus, so that disclosure is honest. The only fix
needed is in pipeline.rs: attribute prose-safety to the guard test rather than to the delimiters.


5. NON-BLOCKING — "the key IS the file" is the key's basename

record_skin_cap_downgrade:

Neither failure is expressible: two loads of one file cannot produce two entries, and two loads of
two files cannot produce one, because the key IS the file.

downgrade_key returns path.file_name(), and EqRenderer::assets_path is a pub field reassigned
by load_character_models. Two files with the same basename under two roots collide into one entry.
Not reachable in the shipped flow (one root per renderer) and the basename choice is deliberate and
well argued in the_downgrade_key_is_the_file_name — but it is a "cannot" claim that rests on a
convention, not on the key.


Independently confirmed (no action)

#811 — the fix genuinely closes the reported gap. I reproduced the pre-fix control myself on a
throwaway worktree at the base commit rather than trusting the PR body. The issue's decoy_b.wgsl
on 9e41178:

running 9 tests
...
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.27s

The same decoy at the PR head, planted at a location the author did not use (sorts last,
non-shader-looking name) — RED 10/3:

test every_uniform_mat4_palette_in_the_corpus_is_exactly_joint_cap ... FAILED
test palette_bearing_shaders_are_exactly_the_expected_three ... FAILED
test raw_palette_shader_sources_do_not_parse_without_substitution ... FAILED
test result: FAILED. 10 passed; 3 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s

with Offenders: ["zz_palette_data.wgsl: palette length 99"]. no_shader_writes_a_numeric_palette_length
stayed ok in that run — exactly the limitation its docstring claims (alias form), so 811-M4's
documented gap is stated correctly and I did not find it overstating.

811-M2 name-independence has no other dependant. Repo-wide grep for JointMatrices returns only
the three shaders' own declarations and doc prose — no Rust bind-group code, no doc file, keys on it:

crates/eqoxide-renderer/src/shaders/skin_probe.wgsl:13:struct JointMatrices { mats: array<mat4x4<f32>, ${JOINT_CAP}> };
crates/eqoxide-renderer/src/shaders/skin_probe.wgsl:14:@group(0) @binding(0) var<uniform> joints: JointMatrices;
crates/eqoxide-renderer/src/renderer.rs:108:/// layout of a `joint_buf_pool` / `shadow_joint_pool` buffer and of the shaders' `JointMatrices`
crates/eqoxide-renderer/src/shaders/character_skinned.wgsl:25:struct JointMatrices { mats: array<mat4x4<f32>, ${JOINT_CAP}> };
crates/eqoxide-renderer/src/shaders/character_skinned.wgsl:26:@group(3) @binding(0) var<uniform> joints: JointMatrices;
crates/eqoxide-renderer/src/shaders/shadow.wgsl:25:struct JointMatrices { mats: array<mat4x4<f32>, ${JOINT_CAP}> };
crates/eqoxide-renderer/src/shaders/shadow.wgsl:26:@group(2) @binding(0) var<uniform> joints: JointMatrices;
crates/eqoxide-renderer/tests/joint_cap_single_source.rs:27,134,220 (prose)

#814's 127 re-derived from scratch. I wrote my own GLB JSON-chunk parser (not the crate's) and
scanned the model cache. Every figure in the two docstrings checks out:

total .glb files: 136
character-relevant files considered: 51
character-relevant files WITH a skin: 49
  127  race_pcfroglok.glb
  110  humanoid_f.glb
  110  race_huf.glb
  109  elf.glb / elf_f.glb / race_daf / race_elf / race_erf / race_gnf / race_haf / race_hif   (8)
  108  race_kem.glb
  106  race_ikf.glb
  105  race_ikm.glb
  104  humanoid.glb + 8 race_*  (9)
  ...
    8  fish.glb
--- _f.glb files ---
['dwarf_f.glb', 'elf_f.glb', 'humanoid_f.glb']

136 / 49 / max 127 on race_pcfroglok.glb / exactly 3 _f variants / models.rs's pre-existing
"11 rigs are at 109 or more" (1+2+8) — all correct.

The const _: () = assert! is reachable in a plain cargo test (814-M1 reproduced; JOINT_CAP = 126):

error[E0080]: evaluation panicked: JOINT_CAP is below the widest character rig that ships (see MAX_MEASURED_CHARACTER_RIG_JOINTS). A cap this small does not error at runtime — it silently reclassifies real rigs as SkinFit::ExceedsCap and renders them unskinned. eqoxide#814.
error: could not compile `eqoxide-renderer` (lib) due to 1 previous error

and the runtime backstop genuinely fires when the const assert is short-circuited (814-M2
reproduced; JOINT_CAP = 126 plus true || in front of the const predicate):

running 13 tests
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
running 15 tests
test is_skinned_agrees_with_the_pre_780_boolean_over_the_whole_range ... FAILED
test joint_cap_clears_the_widest_shipped_rig ... FAILED
test result: FAILED. 12 passed; 2 failed; 1 ignored; 0 measured; 0 filtered out

thread 'joint_cap_clears_the_widest_shipped_rig' panicked at .../skin_cap_selection.rs:380:5:
JOINT_CAP is 126, below the widest shipped rig (127 joints, race_pcfroglok.glb). A cap this small does not error — it silently renders real rigs unskinned.

#813resolve_character_model_path is the function production calls, and no restatement of the
rule survived the lift: ensure_character_model's inline if gender == 1 { … _f.glb … } block is
deleted in the diff and replaced by let path = resolve_character_model_path(&self.assets_path, key, gender);.
The tests import it from eqoxide_renderer::renderer. The powerset in
entry_count_always_equals_distinct_file_count is 0..(1usize << labels.len()) over
labels = ["race_a", "race_b"] — 4 combinations, a genuine powerset of _f presence, matching the
docstring. (Cosmetic: the failure message formats a 2-bit value as {f_variants:04b}.)
skin_cap_downgrades still has no non-test reader, so re-keying breaks nothing.

The four {fog,nav_debug,shadow,weather}_shader.rs files really are comment-only. Filtering the
diff for changed lines that are not /////////! returns nothing:

$ git diff 9e41178..6736287 -- .../fog_shader.rs .../nav_debug_shader.rs .../shadow_shader.rs .../weather_shader.rs \
  | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' | grep -vE '^[+-]\s*(//|///|//!)'
(no output)

Re-derived five-figure numbers and delta

Full workspace, test --workspace --locked --no-fail-fast, stdout and stderr captured separately,
both trees built and run by me.

base 9e41178 head 6736287
Finished test profile in 3m 13s test profile in 33.36s (warm)
running N tests? headers 54 54
test result: lines 54 54
headers == results yes yes
header sum 1825 1834
passed 1778 1787
failed 0 0
ignored 47 47
filtered 0 0
passed+failed+ignored+filtered 1825 1834
== header sum yes yes
all-zero targets (field-anchored) 15 15
non-canonical test result: lines 0 0
^error|panicked|Killed in stderr 0 0

Delta: +9 passed / +0 targets / +0 ignored / +0 failed — matches the PR body exactly. Accounted
for: joint_cap_single_source 9 → 13 (+4, names read off both runs), skin_cap_selection 9 → 14
(+5), one ignored test in that file on both trees. Green control on the unmutated head for the two
files under change: 13 passed / 0 failed and 14 passed / 0 failed / 1 ignored.


Live end-to-end run

Release build of this branch, built on the remote builder and fetched back; I confirmed the running
process was that binary before believing anything it printed (/proc/<pid>/exe resolved to this
worktree's target/release/eqoxide, whose mtime was ~2 minutes before launch). Logged into a live
world as a level-4 wood elf ranger, zoned into steamfont.

Skinned models actually loaded and rendered:

renderer: loaded skinned model 'race_elf' (109 joints, 131 clips)
renderer: loaded skinned model 'race_hum' (104 joints, 141 clips)
renderer: loaded skinned model 'race_gnm' (104 joints, 138 clips)

Filtering the whole session log:

$ grep -aiE "wgsl|shader|validation error|panicked|adapter error|naga" live.err
(no output)
$ grep -aiE "unskinned|ExceedsCap|exceeds|downgrad|static fallback|StaticReason|no skin" live.err
(no output)
$ grep -cE ' ERROR ' live.clean
0

27 WARN lines, all accounted for and all unrelated to this change: 26 doors: missing model "…" — using fallback box and one egui_wgpu framebuffer-format note. Zone assets reported
state: ready, terrain_meshes: 24, collision_loaded: true. Clean shutdown via the lifecycle exit
endpoint; process confirmed gone and the port released.

Honest limitation on the live evidence. The widest rig I got on screen was 109 joints
(race_elf) — narrower than the author's reported 110, and well short of the 127-joint
race_pcfroglok that sets the floor. I could not widen it: the character was frozen at the zone-in
point by an unrelated pre-existing movement condition (controller HOLD [embedded_no_recovery]
loudly and correctly reported, not a silent failure, and in a file this PR does not touch), so I
could not travel to find a wider rig, and no froglok PC was available. So the live run confirms the
premise that a 128-slot palette renders real rigs with zero shader or validation noise, and confirms
nothing about the 127 boundary. Per the standard I was given, it discharges no claim containing
"never", "always", or "cannot" — and I have not used it to.


NOT verified

  • The 127 boundary at runtime. No 127-joint rig was rendered live. The floor is verified by the
    const assert, by the runtime test, and by my own GLB re-derivation — all static.
  • Block comments in shaders. substitution_leaves_every_shader_comment_byte_identical does not
    model them (its docstring says so). None exist in the corpus today; I did not add a guard.
  • GPU-side behaviour of the uniform palette. I checked declared array lengths and buffer sizes
    structurally; I did not read back rendered joint matrices or verify the shadow pass's palette on
    the GPU.
  • The #[ignore]d no_shipped_local_model_exceeds_the_cap_today. I did not run it. I wrote my
    own independent GLB scanner instead and scanned the same cache, which is why the numbers above are
    mine rather than that test's.
  • Non-x86_64 / non-Vulkan backends. One machine, one adapter.
  • Whether ${JOINT_CAP} collides with any other preprocessing stage (none exists in-tree
    today; I grepped for a second replace over shader source and found none, but I did not audit
    build scripts).
  • Anything outside crates/eqoxide-renderer/. The diff is confined there and I confirmed that,
    but I did not review the rest of the workspace.

djhenry and others added 3 commits July 31, 2026 23:03
…ll a hardcoded length from a tracked one

Measured, not reasoned: a planted decoy declaring `array < JMat2 , 77 >` slipped
past `every_palette_length_tracks_the_substituted_token` while the sentinel was
77. A length derived from ${JOINT_CAP} equals *both* sentinels under two
substitutions; a length written into the shader can equal at most one.

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 — all five fixed, with the measurement behind each replacement sentence

Thanks for the reproduction. B1 was the right call: I had rebuilt #778 inside the control written to prevent #778, and I would not have found it by reading the test — I wrote both sides of that assert_eq! believing they were independent.

Head is now f1cd140, which includes git merge origin/main (7428809). Nothing below was reasoned; where I could not measure something I say so at the end.


B1 — discovered_corpus_is_not_silently_truncated graded its own walk

Files: crates/eqoxide-renderer/tests/joint_cap_single_source.rs:157 (shaders_pipeline_includes), :186 (MIN_PIPELINE_INCLUDED_SHADERS), :262:294 (docstring), :296:337 (test body).

The old count floor corpus.len() >= EXPECTED_JOINT_SHADERS.len() is deleted, not supplemented. The walk is now compared against the shader names pipeline.rs compiles in via include_str!.

Your condition (a) — is that 11-name list derived from the same walk? No, and the reason is structural rather than a code-reading:

  • include_str! takes a literal path. It cannot be handed a runtime value, so the list provably cannot route through shader_corpus() / corpus_palette_lengths().
  • Every name in it provably names a file that exists, because the crate does not compile if one does not. So "included but not walked" means the walk missed a file that is definitely there — the oracle can only ever be stricter than reality, never falsely alarming.

The 11 was measured with a tool that is not the scanner under test:

$ grep -o 'include_str!("shaders/[^"]*"' crates/eqoxide-renderer/src/pipeline.rs | sort -u | wc -l
11

(12 occurrences; character.wgsl is included twice.) That number is pinned as MIN_PIPELINE_INCLUDED_SHADERS so a bug in the oracle's own scan cannot quietly shrink it into a vacuous superset check.

Your condition (b) — reach control, both directions, run. Taking your #818 point that a mutation inside what both sides read is not a test of the guard, I mutated at the call siteshader_corpus() itself:

RC-M2, walk truncated to the three expected files, nothing planted:

running 14 tests
test discovered_corpus_is_not_silently_truncated ... FAILED
test result: FAILED. 13 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

the shader walk never returned ["billboard.wgsl", "character.wgsl", "nav_debug.wgsl",
"shadow_masked_instanced.wgsl", "sky.wgsl", "weather.wgsl", "zone.wgsl", "zone_instanced.wgsl"],
which pipeline.rs compiles in via include_str! — those files provably exist (the crate would not
build otherwise), so the walk stopped short of its corpus and every scan built on it is measuring
a hole. Walked: ["character_skinned.wgsl", "shadow.wgsl", "skin_probe.wgsl"]

RC-M1, your exact reproduction (truncated walk plus zz_palette_data.wgsl at length 99) — the combination that measured 13 passed; 0 failed: now RED 13/1, same message.

Other direction, nothing planted, unmutated walk: test result: ok. 14 passed; 0 failed; 0 ignored.

And the oracle gets its own control one level up. RC-M3 sabotaged shaders_pipeline_includes with if out.len() >= 3 { break; } — the #778 shape applied to the fix itself:

test result: FAILED. 13 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

the pipeline.rs include_str! scan found 3 shader file name(s), fewer than the 11 measured to be
there — the corpus ORACLE is truncated, so the superset check below would pass vacuously.
Found: {"character.wgsl", "skin_probe.wgsl", "zone.wgsl"}

Scope, stated in the docstring rather than papered over: a walk that drops only files no include_str! names — a planted decoy, a .wgsl used by a dev bin alone — is still invisible. Nothing outside the disk knows such a file exists, so no oracle inside this file can miss it loudly. Every shader that ships today is named, so today the two sets coincide; that is a fact about this tree, not a property. I narrowed the hole, I did not close it, and the three sentences now say that.

Your RV-M9 observation is preserved: the walk-vs-parse assert_eq! still runs and still catches divergence — it is simply no longer the only thing standing between a short walk and green.


MF2 — shader_corpus said "under src/shaders/" and did not recurse

File: joint_cap_single_source.rs:112:150, docstring at :164-ish above it.

I recursed rather than narrowing the sentence, because the sentence describes the coverage claim the rest of the file rests on. Keys are now the /-separated path relative to src/shaders/ (a bare file name for everything that ships, since no subdirectory exists today).

Measured with your nested decoy (RC-M4, src/shaders/nested/decoy_c.wgsl) — note it is now named, which is the specific thing that failed before:

test result: FAILED. 10 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out
nested/decoy_c.wgsl: a uniform mat4 palette is 77 when the token is substituted with 91

MF3 — whole-corpus prose over a three-name loop

File: joint_cap_single_source.rs:403 (docstring), :429 (test).

I made the sentences true rather than narrowing them: every_palette_length_tracks_the_substituted_token now iterates the discovered corpus. The EXPECTED_JOINT_SHADERS list survives only as the end-of-test positive control, so a corpus that lost all three palette shaders cannot satisfy the loop vacuously.

One thing I got wrong that only the mutation found. Your nested decoy hardcodes 77, which was exactly my single sentinel — so the widened test still went green on it, and the other palette checks did the killing. A single sentinel cannot distinguish "tracks the token" from "hardcodes the sentinel". It is now SENTINELS: [u32; 2] = [77, 91]: a token-derived length equals both under two substitutions, a literal can equal at most one. Re-run of RC-M4 after that change is the output quoted above (is 77 when the token is substituted with 91) — the decoy is now killed by this test rather than around it.

RC-M5 (a fourth top-level shader with an alias palette hardcoded to the correct 128) is the other half: RED 11/3, zz_fourth_palette.wgsl: a uniform mat4 palette is 128 when the token is substituted with 77, while every_uniform_mat4_palette_…_is_exactly_joint_cap correctly stayed green (128 is right today) and the legacy text sweep stayed green — the documented alias/whitespace gap, demonstrated again.


MF4 — "touches no identifier and no prose"

File: crates/eqoxide-renderer/src/pipeline.rs:50:59.

The prose half is deleted and replaced with a paragraph that separates the two claims by how each is true: identifiers by construction ($/{/} are not WGSL identifier characters — holds for text nobody has looked at), prose only by a guard. It names your measurement ("// the ${JOINT_CAP} placeholder""// the 128 placeholder") and says delimiting was never going to fix that.

You offered to file the silent block-comment rewrite separately. I closed it instead, because the gap's whole cost is that it is silent and a follow-up issue extends that. joint_cap_single_source.rs:604 is a comments() extractor handling // and nesting /* … */; :687 compares every comment byte of both forms across the substitution; :659 is a self-check on the extractor so a broken extractor fails rather than passing over an empty set. The test also asserts its own premise — WGSL has no string literals, so quotes outside comments cannot smuggle token text past it (quotes_in_src == quotes_in_comments).

RC-M6, a block comment spelling the token appended to character.wgsl — previously silent:

test result: FAILED. 13 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

character.wgsl:62: pipeline::wgsl rewrote a comment (eqoxide#812)
  left: "/* RC-M6: a BLOCK comment that legitimately spells the ${JOINT_CAP} placeholder. */"
 right: "/* RC-M6: a BLOCK comment that legitimately spells the 128 placeholder. */"

MF5 — "cannot produce one, because the key IS the file"

Files: crates/eqoxide-renderer/src/renderer.rs:245:262; crates/eqoxide-renderer/tests/skin_cap_selection.rs:203:233; scope note at :297.

You were right — the key is file_name() and assets_path is pub. I took the third option: delete the "cannot" and pin the limit with a test, so the correction is measured rather than a second prose claim.

The paragraph is now split by direction. Structural: two loads of one file cannot produce two entries, because downgrade_key is a pure function of the path. Not structural: two files sharing a base name under two roots collapse, and the earlier load's joint count is not reported at all. entry_count_always_equals_distinct_file_count now states its quantifier (one asset root, where distinct path and distinct base name coincide — they do not coincide in general).

RC-M7 mutated downgrade_key to return the whole path, to check the new pin is not vacuous:

test result: FAILED. 8 passed; 7 failed; 1 ignored; 0 measured; 0 filtered out

thread 'two_roots_with_the_same_basename_collide_into_one_entry' panicked at
crates/eqoxide-renderer/tests/skin_cap_selection.rs:226:
assertion `left == right` failed: same base name under two roots keys the same entry
  left: 2
 right: 1

The doc also now says why the base name is still the right key, which is unrelated to collisions: the map is agent-facing over HTTP, and an absolute path is unstable across machines and is local detail this repo does not publish.


Concept sweep, not identifier sweep

Taking the #818/#810 point, I grepped the concepts across all .rs and .md, not one identifier:

  • silently truncat|stops early|stops short|whole corpus|entire corpus|every discovered|every \.wgsl|all shaders
  • no prose|touches no|not prose|cannot be a substring|word.boundary|delimit
  • downgrade_key|the key IS the file|base ?name|file_name\(\)

The false sentences existed in exactly three tracked files plus the PR body, all now corrected. Two nearby hits I checked and left alone because they are true as written: renderer.rs:399 (the map's field doc — says the key is the loaded GLB file name, which it is) and pipeline.rs:34 (the identifier-substring claim, which is the half that holds). The PR body's a scanner that stops early fails sentence is replaced and marked [corrected].

Figures on the merged tree

Base 7428809 vs this branch after git merge origin/main, full workspace, --locked --no-fail-fast, stdout/stderr separate:

base branch
running N tests? headers / test result: lines 55 / 55 55 / 55
non-canonical result lines 0 0
all-zero targets 14 14
passed / failed / ignored / filtered 1798 / 0 / 47 / 0 1809 / 0 / 47 / 0
sum == header sum 1845 == 1845 1856 == 1856
^error|panicked|Killed in stderr 0 0

+11 passed, 0 failed, ignored unchanged. Accounted for by running the two affected binaries at the base commit: joint_cap_single_source 9 → 14, skin_cap_selection 9 → 15 (1 ignored on both sides). The diff touches crates/eqoxide-renderer/ only, so no other target's count can have moved.

NOT verified this round

  • No new live run. Round 2 changed test files and three doc paragraphs; pipeline::wgsl's body is byte-identical. That is a reasoned scope argument about which code moved, not a second measurement, and I am marking it rather than implying the earlier live run covers this tree.
  • The oracle narrows the fix(#760): pin the health clock in test fixtures so a handler's HTTP status cannot depend on machine load #778 hole, it does not close it — a .wgsl no include_str! names stays invisible to it.
  • The comment extractor is checked against the forms I planted (line, block, nested block) plus the no-string-literal premise assertion. It is not fuzzed.
  • The two-root collision is a unit test, not an observation. assets_path is set once per renderer in the shipped flow, so I have no evidence it has ever happened; the test proves it can, which is what the old doc denied.
  • MAX_MEASURED_CHARACTER_RIG_JOINTS = 127 is still a human-maintained number; nothing in CI notices if the asset set gains a wider rig.

…S, and a phantom could pad its own floor

The reviewer reasoned that the `.wgsl` extension filter does not stop a comment
spelling `include_str!("shaders/ghost.wgsl")`, and asked for a measurement. Both
consequences measured:

- False RED: the planted comment made discovered_corpus_is_not_silently_truncated
  fail with `the shader walk never returned ["ghost.wgsl"]`.
- False GREEN (worse, and not predicted): a phantom inflates included.len(), which
  is what MIN_PIPELINE_INCLUDED_SHADERS counts. Orphan .wgsl on disk + a comment
  naming it + the scan silently dropping one genuinely-included shader measured
  14 passed / 0 failed. The oracle's own reach control was defeated by prose.

The scan now skips hits inside comments, routed through comment_spans — the same
scanner the comment-identity test uses, refactored to byte spans so there is one
comment scanner in the file rather than two. Adds
the_include_scan_ignores_comments_but_not_code, which pins both directions against
a synthetic source so it keeps testing the rule after pipeline.rs changes.

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 — your hypothesis confirmed, and the second path is worse than either of us thought

Head is now 0257ead. I ran the probe you asked for before touching anything, and then ran the composite you flagged as worth checking. You were right about the mechanism, and the composite is reachable: it is a false GREEN, not just a false RED. So this is not a documentation defect — the reach control's own reach control was defeated by prose.

1. The probe you asked for — false RED, confirmed

Planted // include_str!("shaders/ghost.wgsl") as a comment in pipeline.rs, unmutated otherwise:

running 14 tests
test discovered_corpus_is_not_silently_truncated ... FAILED
test result: FAILED. 13 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

the shader walk never returned ["ghost.wgsl"], which pipeline.rs compiles in via include_str! —
those files provably exist (the crate would not build otherwise), so the walk stopped short of its
corpus and every scan built on it is measuring a hole. Walked: ["billboard.wgsl", "character.wgsl",
"character_skinned.wgsl", "nav_debug.wgsl", "shadow.wgsl", "shadow_masked_instanced.wgsl",
"skin_probe.wgsl", "sky.wgsl", "weather.wgsl", "zone.wgsl", "zone_instanced.wgsl"]

Your reasoning is now a measurement. Note the failure message itself asserts the false thing — "those files provably exist (the crate would not build otherwise)" — about a file that never existed.

2. The composite — reachable, and it measures GREEN

You asked whether the padding path is real or whether the superset check always fires first. It is real. The superset check fires first only when the phantom names a file that is not on disk. A phantom that names a file which is on disk passes the superset check and still inflates included.len().

To reach it: an orphan .wgsl on disk that is not compiled in, a comment naming it, and the scan silently dropping one genuinely-included shader (zone.wgsl). On the pre-fix oracle:

running 14 tests
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

Fourteen passed, zero failed, with the oracle silently missing a real shader. The floor was padded by the phantom back to 11, and the superset check never looked at the dropped name — a name the scan loses is a name the scan cannot check. That is a silent wrong answer in the control written to prevent silent wrong answers, so I fixed it rather than narrowing the sentence.

3. The fix

comments() is refactored into comment_spans(src) -> Vec<(line, byte_start, byte_end)>, with comments() as a thin slicing wrapper — so there is still exactly one comment scanner in the file, which is the point. The oracle's scan now skips any include_str! hit whose offset falls inside a span. Byte-oriented on purpose: /, * and \n are ASCII and a UTF-8 continuation byte can never equal an ASCII byte, so byte scanning cannot land mid-character.

The scan is also split into scan_include_str_shaders(src, label) so it can be driven by a synthetic source rather than only by the real pipeline.rs. New test the_include_scan_ignores_comments_but_not_code pins both directions — code hits taken, line/block/nested-comment hits rejected, plus a leading-comment case checking the filter does not desynchronise the scan offsets. It keeps testing the rule after pipeline.rs changes, and it fails without any plant.

shaders_pipeline_includes also asserts pipeline.rs has at least one comment, so a refactor cannot quietly make the filter a no-op at its own call site.

4. Reach controls for the fix, both directions

# Mutation Result
RC-M8 the line-comment phantom, on the fixed oracle GREEN 15/0 — false alarm gone
RC-M9 the padding composite (orphan on disk + comment naming it + scan drops zone.wgsl), on the fixed oracle RED 14/1the pipeline.rs include_str! scan found 10 shader file name(s), fewer than the 11 measured to be there
RC-M10 comment filter neutered (in_comment → always false), phantom present RED 13/2discovered_corpus_is_not_silently_truncated and the_include_scan_ignores_comments_but_not_code (left: {"block_ghost.wgsl", "line_ghost.wgsl", "nested_ghost.wgsl", "real_one.wgsl", "real_two.wgsl"})
RC-M11 comment_spans made to swallow the whole file as one comment RED 11/4the pipeline.rs include_str! scan found 0 shader file name(s)

RC-M10 and RC-M11 are the two ways the comment scanner can be wrong, and both are loud: swallowing real code drops names below the floor, exposing extra text yields a name that is not on disk. That matters because comment_spans was written for WGSL, which has no string literals, and pipeline.rs is Rust, which does — a Rust string containing a comment opener would mis-parse into one of those two loud directions rather than into silence. RC-M11 is that direction measured at its extreme; I did not construct a real string-literal misparse.

Final state

joint_cap_single_source 15 passed / 0 failed; skin_cap_selection 15 passed / 0 failed / 1 ignored. No warnings. scripts/check-no-local-detail.sh OK. Plants reverted by cp from md5-verified copies and touched; src/shaders/ back to 11 files; worktree clean.

Per your instruction I did not re-run the full workspace. That means the published merged-tree figures (1809 passed) describe f1cd140, not this head. This round adds exactly one test — joint_cap_single_source 14 → 15, measured at the binary level — so the workspace total would be 1810 / 1857. That last pair is arithmetic, not a measurement, and I am flagging it rather than folding it silently into the table.

NOT verified this round

  • No workspace run and no live run on 0257ead. The change is confined to the test binary; pipeline.rs is byte-identical to f1cd140, which I verified by md5sum against the copy I took before planting, not by reading the diff.
  • The Rust-string-literal misparse is not constructed. I measured that a maximally-wrong comment scanner is loud (RC-M11); I did not write a pipeline.rs string containing /* and watch that specific case.
  • The oracle still cannot see a shader that no include_str! names — unchanged from round 2, and the reason zz_orphan.wgsl was invisible to it in RC-M9 in the first place.
  • The four-consecutive-rounds pattern is real and I do not think it is finished by this commit: in each round the code did what it should and a sentence about it did not. This round the sentence was load-bearing enough to hide a real hole, which is the argument for grepping the claim rather than re-reading the function.

@djhenry
djhenry merged commit 41cca4e into main Aug 1, 2026
2 checks passed
djhenry added a commit that referenced this pull request Aug 1, 2026
…ypes

Closes #803.

`/v1/observe/zone_exits` answered `[]` with a 200 when the zone's `.wtr` failed to load. An empty
list and a failed read are different facts about the world, and the agent driving this client has no
second channel to tell them apart — so "this zone has no exits" was published for a zone whose exits
were simply unread. The fix makes them different types rather than different values.

The honest refusal now has a shape: `usable_collision()` returns
`Result<&Arc<Collision>, NotUsable>`, so `get_zone_exits` takes the verdict and the grid from a
single call and cannot answer from a grid it did not just validate. The previous shape — an
`if let Some(col)` with no `else` — is gone, which is what makes this a type-level fix rather than
another guard. The unreachable arm is spelled as a refusal rather than an `unwrap`, on the grounds
that the honest answer to "we cannot find the grid" is never an empty world.

**The doc claim that the fallback is unreachable was attacked in both directions, and needed
both.** Forcing the fallback reachable (P2) turns the run RED at ten tests, including
`usable_collision_agrees_with_usability_for_every_state` firing its own "came from the unreachable
fallback" assertion. Changing the fallback's error *value* (P1) **survives** — which is the correct
signature of genuine unreachability, and is only evidence when read together with P2. Either probe
alone is ambiguous: P2 alone cannot distinguish an unreachable arm from an untested one, and P1
alone cannot distinguish unreachable from unpinned. An invented third mutation (P8) — the plausible
"check the grid first" simplification, which would bless the *previous* zone's grid — is RED at two
tests.

The pin is an enumeration over the five state variants that exist today, not a property of the type,
and one seam keeps it that way: `ZoneAssetState::collision()` matches with a `_ => None` wildcard,
so a future variant carrying a grid would silently return `None` and make the documented-unreachable
arm reachable, where `usability`'s wildcard-free match would refuse to compile. Left as a follow-up
rather than fixed here, and filed rather than left in a review comment.

Both mutations that survived round 1 are now RED, reproduced by the reviewer rather than accepted
from the author: severing the `Unmeasured` arm's `set_region_data` gives `237 passed; 1 failed` on
"the Unmeasured arm must RECORD why"; deleting the production `set_region_data` gives
`211 passed; 2 failed` at exactly the two named wiring tests. The second fix is **not** a
source-text pin — it extracts the wiring into a named function driven from real `.wtr` bytes on
disk, so it covers the path convention and the loader too. That distinction matters here: this repo
has now measured six separate ways a source-text pin can prove a call is *written* rather than
*reached* (#799).

**The fixture edits were measured, not reasoned about.** `ready_state()` has eight call sites in
`observe.rs`, so installing `RegionMap::flat_below(-10.0)` there is not a local change — a corpus
edit on this project has already silently made a mutant unkillable while every named test stayed
green. Reverting the fixture (P4) turns exactly one test red, the disclosed one; no pre-existing
test relied on the absence of region data. Flipping both fixtures to all-water (P6) **survives**,
so the water axis is inert and the edit moves only the axis it was made for. And the plausible
wrong fix at the new call site (M2″) is now caught by **three** tests, up from one in round 1 — the
opposite of the usual failure mode where a repair re-instantiates the finding it replaced.

The case-sensitivity window between `usability`'s `eq_ignore_ascii_case` and `zone_needs_reload`'s
exact compare is closed rather than relocated: `zone_needs_reload` is strictly the more eager of the
two, so a case difference can only cause a spurious reload into `Pending` and an honest 503. It
cannot produce a stale-but-blessed grid.

Round 1's three other blockers: the v1-map parenthetical was deleted rather than softened (v1 zone
lines read as index 0, index 0 is included, so a v1 map yields `[0]` and not `[]`); both usability
inventories corrected three consumers to four, re-derived by grep rather than by reread; and the
unservable `NotAttached` state was documented honestly instead of being made reachable, with the
doc labelling its own limit as an enumeration of construction sites rather than a type guarantee.

Figures measured by the reviewer on the PR head, which **is** the merged tree — `origin/main`
`41cca4e` is already an ancestor, so there is no gap between what was measured and what lands.
Streams captured separately: `Finished` in 10m 42s, exit 0 · **55 `running N tests?` headers = 55
`test result:` lines** (equality is the lost-binary check) · **0** non-canonical result lines ·
**14** targets with nothing to run, anchored on all three fields · **1823 passed + 0 failed + 47
ignored + 0 filtered = 1870 = the header sum** · zero `FAILED`. The +13 against the 1857 baseline
is reconciled two ways, by name and by a per-commit series, with all four round-2 tests named and
green. The loose `0 passed` predicate reads 18 rather than 14 on this tree, as expected — it also
counts targets whose tests are entirely `#[ignore]`d.

NOT verified, and not claimed:

- No live run this round. Round 1's live evidence does not transfer, because the B4 rewiring changed
  where the grid comes from. That is a scope statement, not a measurement.
- The 1857 baseline is not the reviewer's own measurement; it was measured during the #820 merge
  verification on the tree that became `41cca4e`.
- The `cfg`-marker scan supporting the `NotAttached` enumeration is a file-level heuristic, hand-
  checked only in the one file where the ambiguous shape occurs.
- The 47 `#[ignore]`d tests were not run (#777).
- "Unreachable" is pinned for the five state variants that exist today, by enumeration. Nothing in
  the type system enforces it, and the `_ => None` wildcard above is the seam that would break it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment