diff --git a/config/parked.json b/config/parked.json index 87c583bb..d779e02c 100644 --- a/config/parked.json +++ b/config/parked.json @@ -3520,7 +3520,7 @@ "unit": "source/sound/sound_manager", "function": "_sound_refresh_looping", "class": "tu-context-optimization", - "evidence": "Best complete ordinary-C refresh graph retained with zero exact credit. January is 1,136 padded bytes with 43 relocations; canonical is 1,088 with 42. Target-backed datum validation calls, the named sound_inaudible_fade_out_time constant, and the source-authentic `loop` assertion spelling are preserved. The remaining missing relocation is a compiler-merged duplicate fade-call tail; HCEA cache-release logic is intentionally absent because January has none of its dependencies. No compiler steering, fake dependency, raw offset, undefined behavior, or nonsensical branch is retained. See docs/object_matching_logs/sound_manager_obj_small_looping_graph_20260908.md.", + "evidence": "Best complete ordinary-C refresh graph retained with zero exact credit. January is 1,136 padded bytes with 43 relocations; canonical is 1,088 with 42. Target-backed datum validation calls, the named sound_inaudible_fade_out_time constant, and the source-authentic `loop` assertion spelling are preserved. The remaining missing relocation is a compiler-merged duplicate fade-call tail; HCEA cache-release logic is intentionally absent because January has none of its dependencies. No compiler steering, fake dependency, raw offset, undefined behavior, or nonsensical branch is retained. See docs/object_matching_logs/sound_manager_obj_small_looping_graph_20260908.md. 2026-09-15 (next-150K lane wave n3a): re-baselined at ZERO strict credit and strictly closer than 1088/42. The recorded criterion's premise - that the missing relocation was a compiler-merged duplicate fade-call tail - is REFUTED by the census: it was a source defect. Deleting the fade_in_time phi local recovers January's fourth _sound_start_fade relocation, so the relocation count is now exact at 43/43 and the size moves 1088 -> 1120 toward January's 1136; alndiff regions drop 74 -> 37. The row stays parked and fuzzy [sha].", "measurements": { "target": { "size": 1136, @@ -3528,11 +3528,11 @@ "normalized_sha256": "22e6f76584244a8861a4c646d88eb7c301fa5cdc1fc8d98849e505c2d3a391db" }, "base": { - "size": 1088, - "relocation_count": 42, - "normalized_sha256": "4820b25c27684fd9430551da92e07ac3c2f358bb66962348d445b5697defb11a" + "size": 1120, + "relocation_count": 43, + "normalized_sha256": "86e26bb3e59f759070492aaa2b2071ff5f9e2db818be0898a711ffe2501ba9e6" }, - "objdiff_percent": 36.576504 + "objdiff_percent": 97.800545 } }, { @@ -3723,11 +3723,11 @@ "unit": "source/sound/sound_manager", "function": "_update_channel_for_looping_sound", "class": "unclassified", - "evidence": "Complete typed looping-channel update reconstructed from January code/relocations and HCEA semantics. It covers properties, cache transitions, linked permutations, definition changes and crossfades. January is 1,360 padded bytes with 61 relocations; canonical is 1,376 with 61. No unique source-level mechanism explains the residual, so it receives zero exact credit. No compiler steering, raw offset, fake dependency, undefined behavior or nonsensical branch is retained. See docs/object_matching_logs/sound_manager_obj_idle_channel_spine_20260908.md.", + "evidence": "Complete typed looping-channel update reconstructed from January code/relocations and HCEA semantics. It covers properties, cache transitions, linked permutations, definition changes and crossfades. January is 1,360 padded bytes with 61 relocations; canonical is 1,376 with 61. No unique source-level mechanism explains the residual, so it receives zero exact credit. No compiler steering, raw offset, fake dependency, undefined behavior or nonsensical branch is retained. See docs/object_matching_logs/sound_manager_obj_idle_channel_spine_20260908.md. 2026-09-15 (next-150K lane wave n3a): re-baselined at ZERO strict credit and strictly closer than 1376/61. Size and relocation count are now both January-exact (1360/61); the residual is [sha] only. The row stays parked and fuzzy.", "measurements": { "target": { "size": 1360, "relocation_count": 61, "normalized_sha256": "457d122269e7e06c8b4a557f80eef434e3eb925aa8aaaa73d9e69bce635a6d78" }, - "base": { "size": 1376, "relocation_count": 61, "normalized_sha256": "5fa29769faf68bcaa178d1a65f979d8641b10b3727a404d00849b345f42a0ff7" }, - "objdiff_percent": 96.782814 + "base": { "size": 1360, "relocation_count": 61, "normalized_sha256": "cb7925e4a26b946436b50b7c0f3172fdc975c1e48f7f9780f0d6d1cc8bc12f10" }, + "objdiff_percent": 99.19809 } }, { diff --git a/docs/object_matching_logs/actor_type_infection_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/actor_type_infection_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..fe0c1006 --- /dev/null +++ b/docs/object_matching_logs/actor_type_infection_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,186 @@ +# `actor_type_infection.obj` next-150K lane, wave n3a (2026-09-15) + +## Result + +No production change. `source/ai/actor_type_infection.c` is byte-for-byte unchanged +(`git diff --stat` empty). The object stays at 4/5 strict exact functions. + +| Function | January padded / relocs / sha16 | Real file (baseline = final) | Disposition | +| --- | --- | --- | --- | +| `_infection_decide_action` | `304 / 26 / eb35b33e...` | exact | inherited | +| `_infection_swarm_aim_jump` | `688 / 22 / 5c2698e0...` | exact | inherited | +| `_infection_wander_move_time` | `96 / 5 / 2b2b9cfb...` | exact | inherited | +| `_infection_wander_pause_time` | `96 / 5 / b4122501...` | exact | inherited | +| `_infection_swarm_control` | `3616 / 104 / 64eacc5c...` | `3360 / 103` residual | NOT-LANDED (census confirms the recorded premise) | + +Gate (baseline and final): `== exact 4 residual 1 unwritten 0`; `_point_from_line3d` guard passed. +`config/parked.json` has **no entry for this unit** (`parkcheck.py` prints +`no parks for source/ai/actor_type_infection`), so the wave's "currently PARKED" framing does not +apply: `_infection_swarm_control` is an ordinary written residual and no parked body was edited +anywhere. `fake_match_scan.py`: 0 review leads (real file and candidate). + +## Duplicate prevention + +- Ledgers read in full: `_jonas_helper_pair_20260825`, `_jonas_swarm_jump_recovery_20260827`, + `_jonas_exhaustive_census_20260830`, `_aim_jump_owner_reconciliation_20260909`, + `_opus5_fresh_graphs_20260914` (including its lead admission correction), + `_opus5_150k_w1_20260914`, `_opus5_250k_w3c_20260915`; worker logs + `scratch/workers/actor_type_infection.md` and `scratch/workers/w3c_actor_type_infection.md`. +- `git log --all --oneline -- source/ai/actor_type_infection.c`: 15 refs, newest `82330421f`. +- `branch_sweep.py`: 13 unique blobs; the current file is the unique best + (`exact 4 residual 1 sizedelta 256`); 9 blobs compile-fail, the others are worse. +- Binding prior decisions (R13): the fresh-graphs 3600/104 body was rejected and reverted because an + already-written residual may land only when strict EXACT; w1 recorded SKIPPED-EXHAUSTED; w3c spent + 2 of 5 shapes and classified the remainder a backend tie. + +## Census (structural-first, run on the REAL FILE) + +Objects: `scratch/workers/n3_actor_type_infection/real.obj` (current production file recompiled) and +`v0.obj` (= wave w3c's `c1.c`, re-gated here at 3600/104 — the best known body, used only as the +measurement base). + +### Frame census + +| build | `sub esp` | distinct `[ebp-N]` slots | instructions | +| --- | --- | --- | --- | +| January | `0xe8` | 61 | 1127 | +| real file | `0xe4` | 61, **13 of them differ** | 1052 | +| `v0` | `0xe8` | 61, **identical set** | 1121 | + +Slots the real file has and January does not: `0x12 0x21 0x80 0xde 0xe2 0xe3`. +Slots January has and the real file lacks: `0x24 0x7c 0xcc 0xd6 0xda 0xdb 0xe8`. +`[ebp-0x24]/[ebp-0x20]/[ebp-0x1c]` is January's `up` local (`up = biped->object.up;`, three integer +moves at `0x113-0x124`); `0xd6/0xda/0xdb` are `unit_control_data` fields. The real file's 4-byte +frame gap is a local-set defect, and `v0` already reproduces January's slot set exactly. + +### Relocation census by target (`relocdiff --count-by-target --allow-structural`) + +Every call and global target matches 1:1 between the real file and January **except**: + +| target | January | real file | +| --- | --- | --- | +| `_normalize3d` | 1 | 3 | + +(The `__real@*` rows that print `0` on our side are the relocation-*spelling* class — our object +defines the literal as a `.rdata` select-any COMDAT where January references it undefined; +`relocdiff` prints the `symbolic=` equivalence on each of those rows.) + +The real file's single missing relocation is therefore not a missing call but **two surplus +out-of-line `normalize3d` calls**: January inlines `normalize3d` at the first and third +`has_direction` sites and calls it out of line only at the second, which is reachable only when the +`if (has_direction)` block sits at member-loop level rather than nested inside +`if (parent == NONE) { if (!airborne) ... }`. `v0` already carries that hoist and matches the whole +104-relocation multiset. + +### Census verdict + +The census **confirms** the recorded premise rather than refuting it. The real-file `[size]` and +`[relocs]` tags are the already-diagnosed structural gap whose source fix exists and is verified in +scratch (`v0`: frame, slot set, relocation multiset, call set, loop spellings, switch layouts, +jump-table case targets and literals all match January). What blocks the row is the standing rule +that an already-written residual may land only when strict EXACT, together with four backend +clusters — not missing evidence. + +## Residual at the best known body (`v0`): 118 instructions, 4 clusters + +1. `0x20f` (4 insns) — inlined `magnitude_squared3d` in the prop scan; A35(2) term age, diagnostic. +2. `0x874` (3 insns, +2 bytes) — inlined `normalize3d` #1: January keeps `1/magnitude` live through + the third component multiply and pops it at the join shared with the zero path; ours folds the + last multiply destructively and needs a `jmp` over the join pop. +3. Three `cross_product3d(&up, &direction, ...)` sites (12 insns) — only the two products whose `a` + operand is `up.i` commute. +4. `0xbfd-0xd18` register web — carries the **entire** 16-byte padded gap. + +## New evidence this wave (decisive negatives — treat as do-not-repeat) + +**N1. The `cross_product3d` header spelling is byte-proven, so cluster 3 can never be closed by +respelling `real_math.h`.** `action_obey.obj` owns an out-of-line January COMDAT copy of +`_cross_product3d` that is **strict EXACT** against our `real_math.h` body: + +``` +0009 fld [ecx]; 000b fmul [eax+4]; 000e fld [ecx+4]; 0011 fmul [eax]; 0013 fsubp ; k +0015 fld [ecx+8]; 0018 fmul [eax]; 001a fld [ecx]; 001c fmul [eax+8]; 001f fsubp ; j +0021 fld [ecx+4]; 0024 fmul [eax+8]; 0027 fld [ecx+8]; 002a fmul [eax+4]; 0030 fsubp ; i +``` + +January's own helper loads the `a` operand first in every product, including `a->i*b->k` +(`fld [ecx]; fmul [eax+8]`) — exactly what our inline expansion emits in the infection body. The +alternative hypothesis (January's header writing the `a->i` terms as `b->j*a->i`) is refuted by +bytes. Future attempts on cluster 3 must stay inside the caller and must not touch `real_math.h`. + +**N2. Our build already reproduces VC7's commutation where the operands are pointer-based, so this +is not a compiler-version gap.** `matrix_math::_matrix3x3_from_forward_and_up` is strict EXACT and +commutes 3 of its 6 cross-product products in both builds (`fld [edx+4]; fmul [ecx]` for `a->i*b->j` +with `a = ecx = up`, `b = edx = forward`). A tree scan over every `cross_product3d` consumer +(`scratch/workers/n3_actor_type_infection/crossscan.py`, 89 functions) shows that **every +strict-exact cross-product site in the tree uses register/pointer bases**, and every site whose +operands are `ebp`-folded locals is non-exact. The infection anomaly is the offset-0 clause already +recorded in laws_w3 **A35(1)**: with `ebp`-folded locals January prefers the older leaf (`up.j` beats +`dir.k`, `up.k` beats `dir.j`) except at `up`'s offset 0, where it loads `direction` first. `up` is +written once by a named struct copy and never re-aged, so no naming/scoping/aliasing lever remains, +and a `{j,k}` pair that resolves both ways proves the rule is not a field-index order. + +**N3. Cluster 4's 16 bytes are exactly the tail duplication of `mov al,1`, and that duplication is a +machine-level cross-jump decision, not a source spelling.** January materialises `should_jump = TRUE` +at four separate exits (`0xc06`, `0xc3c`, `0xc47`, and the speculative `0xc50 mov al,1; jne 0xc57`), +loads the component flags into `cx`, keeps the boolean in caller-saved `al`, converts it *before* the +`csmemset` call (`0xcba neg al; push 0x40; push 0; sbb eax,eax; and eax,2; mov esi,eax`) and keeps +the unit pointer in `edi`. Ours cross-jumps to one `mov bl,1`, loads the flags into `ax`, keeps the +boolean in callee-saved `bl` across the call and converts afterwards (`0xcc8 neg bl` ... +`0xcf2 sbb ebx,ebx`). Region measurement: January `0xbfd..0xd18` = 283 bytes, ours +`0xbfd..0xd08` = 267 bytes — the entire 16-byte padded gap; after it the two tails are identical up +to the `edi`<->`esi` rename. w1's `p1` already wrote the four `should_jump = TRUE;` assignments as +separate statements and VC7 still cross-jumped them into one `mov bl,1` (1115 insns, worse); w3c's +`c2` (a `word control_flags` local computed before `csmemset`) was byte-inert because VC7 sinks the +single-use `?:` to its store. The only spellings that would force the pre-call conversion are +prohibited — assigning `control.control_flags` before the `csmemset` that wipes it (dead/nonsensical +store) or inventing a second use (fake dependency). Both allocations cost exactly one callee-saved +register across the call (January `esi` = flag word + `edi` = unit; ours `ebx` = boolean + +`esi` = unit), so this is a genuine allocator/cross-jump tie of the A43 class. + +## Shapes tried + +None. Triage under brief section 5 stopped before any shape: strict exact requires all four clusters +at once; cluster 4 alone carries the whole size gap and has no admissible source form (N3); cluster 3 +is byte-excluded from the header (N1) with no caller-side lever left (N2); clusters 1-2 are x87 +emission inside inlined `real_math.h` helper bodies (A35(2), diagnostic only). Spending the shape +budget on register/x87/scheduling ties is what the wave brief forbids. `v0.c` was re-gated only as +the measurement base for the census above. + +## Checks + +Real file untouched, so the final real-file gate equals the baseline +(`== exact 4 residual 1 unwritten 0`, all four siblings EXACT); `_point_from_line3d` guard passed; +no parked functions in this unit (no drift possible); owner census not applicable (no production +change) — for the record, `v0`'s owner set versus the real file adds only `__real@bd888889` and +`__real@bfc90fdb` (both present in January's own symbol table) and drops the non-January +`__real@3f6eeeef`, with no new code, helper, BSS or COMMON owner; `fake_match_scan.py` reports +0 review leads for both. + +## Proposals + +None. No header, `config/symbols.json`, `parked.json` or build-configuration need was found, and no +candidate here depends on a FINAL owner ruling. + +Recorded for the owner only, **not re-proposed** (R13 binds): the production body differs from +January in 810 of 1127 instructions, has a 4-byte frame gap, 13 wrong frame slots and two surplus +out-of-line `normalize3d` calls, which is arguably "materially incomplete" under brief section 8, and +`scratch/workers/n3_actor_type_infection/v0.c` is a complete, coherent, strictly-closer replacement +(3600/104, 118 differing instructions, all siblings exact, owner census and fake scan clean). The +100K lane rejected exactly this landing, so this wave neither lands nor re-proposes it. + +## Reopen criteria (supersede the w3c ledger, narrowed by N1-N3) + +Reopen only when all of the following are answerable at once, or when a same-compiler donor explains +the whole `0xbfd-0xd18` register web: + +1. an admissible caller-side source reason for the `up.i`-only (offset-0) operand commutation at the + three inlined `cross_product3d` sites — `real_math.h` is now byte-excluded (N1) and no + naming/scoping lever remains (N2); +2. a source reason that makes `should_jump` dead at the `csmemset` call without a dead store or a + fake second use (N3); +3. a source reason for the inlined `normalize3d` #1 reciprocal staying live to the shared join. + +Start any future attempt from `scratch/workers/n3_actor_type_infection/v0.c` (3600/104, 118 insns), +never from `r3600.c` (151 insns) or from the real file. Do not re-spend the fresh-graphs +do-not-repeat list, w1's `p1`, or w3c's `c1`/`c2`. diff --git a/docs/object_matching_logs/bink_playback_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/bink_playback_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..a7de667f --- /dev/null +++ b/docs/object_matching_logs/bink_playback_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,200 @@ +# `bink_playback.obj` — next-150K lane, wave n3a (structural park pass, 2026-09-15) + +## Scope and provenance + +- Translation unit `source/bink/bink_playback.c`; target `build/split/source/bink/bink_playback.obj` + (January 2002 Xbox debug build, VC7 13.00.9254, `/O2 /Oy- /DDEBUG /Dxbox`). +- Lane `opus/next-150k-house-clean-20260915`. No header, config, park, build or tooling edit; the real + file is byte-identical to HEAD (`git diff --stat -- source/bink/bink_playback.c` is empty). +- Ledgers read first: `bink_playback_obj_fable_graph_reconciliation_20260908`, + `bink_playback_obj_jonas_six_leaf_20260821`, `bink_playback_obj_jonas_stop_render_followup_20260821`, + `bink_playback_obj_jonas_anonymous_ret_leaf_20260830`, `bink_playback_obj_opus5_150k_w2_20260914`, + `bink_playback_obj_opus5_next150_n1_20260915`, `fable_bink_ui_network_hs_reconciliation_20260908`, + all three `config/parked.json` entries, `scratch/workers/n1_bink_playback.md` and the full n1 impl + + verifier blocks in `scratch/opus5-next150/wave-n1-results.json`. +- Worker notes and every candidate: `scratch/workers/n3_bink_playback.md`, + `scratch/workers/n3_bink_playback/`. + +## Baseline correction + +The wave brief quoted `exact 20 / residual 4` with `_bink_free_texture_cache` non-exact. That is stale: +n1's reopen of that row has already landed (commit `3083f800b`). The measured baseline at this HEAD is + + == emitted-symbol guard passed (1 forbidden names checked) + == exact 21 residual 3 unwritten 0 (of 24 listed) + +and **all three remaining residuals are parked**, so nothing in this object can land in the real file +this wave; everything below is a park-reopen proposal measured in scratch. + +| | exact | residual | unwritten | +| --- | ---: | ---: | ---: | +| baseline real file (guard passed) | 21 | 3 | 0 | +| final real file (unchanged, guard passed, park drift 0) | 21 | 3 | 0 | +| candidate `reopen_draw_frame_plus_query.c` | 23 | 1 | 0 | + +## Two park reopens proposed — 992 padded bytes, both strict EXACT in scratch + +Candidate for both: `scratch/workers/n3_bink_playback/reopen_draw_frame_plus_query.c` (the current real +file plus the two reopened bodies and nothing else). Individually: +`reopen_draw_frame.c` (= `d7.c`) and `q3.c`. + +### `_bink_draw_frame` (912) — the recorded `[relocs]` gap was never this function's + +**Census first.** Frame census: `mov eax,0x1128` on both sides, and every `[ebp±N]` slot is shared +(`[ebp-4]`/`[ebp-0x14]` fild scratch, `[ebp-0xc]` top, `[ebp-0x18]` the homed `long`, `[ebp-0xac]` +`vertices[4]`, `[ebp-0x5c]` bounds, `[ebp-0x112c]` string) — no slot ours lacks or adds. Relocation +census by target: 59 == 59, identical targets and addends. The park's `[relocs 61!=59]` tag belongs to the +*old parked body*; n1's `d0` shape already closed it, and this wave reproduced that on the post-n1 HEAD as +`d0r.c` (912 / 59 / `[sha]`). `d0.c` itself can no longer be gated here — it still spells line 797 +`match_dassert` and would regress the landed `_bink_free_texture_cache`. + +`alndiff` of `d0r` vs target: 284 vs 284 instructions, all equal except two windows whose start and end +addresses agree on both sides: + +| addr | January | `d0r` | +| --- | --- | --- | +| 0x119 | `inc edx` ; `mov [ecx+0xc],0xffffffff` | `mov [ecx+0xc],0xffffffff` ; `inc edx` | +| 0x123 | `inc esi` ; `fxch st(1)` ; `add ecx,0x14` | `add ecx,0x14` ; `fxch st(1)` ; `inc esi` | + +n1 classified that as a machine-scheduler tie. **It is not.** Both windows move together, and what moves +them is the identity of the address induction variable: January's vertex cursor ECX is coupled to the +**long** counter (`inc esi`, one instruction, `add ecx,0x14`), ours to the **short** counter. That is the +lvalue-topology class of laws_w3 A33, i.e. a source fact — which variable the array subscript is written +on. + +January's two loop variables, read off its own bytes: + +- EDX — `xor edx,edx` (0), `cmp dx,1 / jle` twice (the `>1` top/bottom selector), `cmp dx,4 / jl` bound. + 16-bit compares and a signed `jl`: a `short`, 0-based, bounded by a signed literal `4`. +- ESI / `[ebp-0x18]` — `mov dword [ebp-0x18],1`, `and esi,2` (`TEST_FLAG(x, 1)`, bit 1), 32-bit: a `long`, + 1-based, memory-homed because the register file is full (ebx/edi/eax hold left/right/bottom, ecx the + cursor, edx the index, esi the scratch). +- Quad recovered: bit 1 of {1,2,3,4} = F,T,T,F = left,right,right,left; `>1` of {0,1,2,3} = + top,top,bottom,bottom. v0=(left,top) v1=(right,top) v2=(right,bottom) v3=(left,bottom). +- `lea ecx,[ebp-0xa8]` = `vertices + 4`, so the cursor is `&vertices[corner_index-1] + 4`. + +**Shapes (7 + 4 controls).** + +| # | shape | result | +| --- | --- | --- | +| `d0r` | n1's `d0` body rebuilt on this HEAD | 912/59 `[sha]`, the two windows | +| `d1` | explicit `vertex` pointer, `vertex++` last in the clause | January's exact ORDER, but cursor bias +8 (`lea ecx,[ebp-0xa4]`; `fstp [ecx-4]` is 3 B where January's `fstp [ecx]` is 2 B) → +1 byte, `[sha]` | +| `d2` | clause `corner_index++, vertex_index++` | January's exact BYTE POSITIONS, only `inc esi`/`inc edx` swapped | +| `d3` | `corner_index++;` as the last body statement | identical to `d2` | +| `d4` | in-loop `struct dynamic_screen_vertex *vertex= &vertices[vertex_index];` | bias +8 like `d1`, `[sha]` | +| `d5` | `d1` with `vertex++` between the two index increments | identical to `d1` | +| `d6` | initialiser order `corner_index= 1, vertex_index= 0` | byte-identical to `d0r` (init order inert) | +| **`d7`** | **subscript `vertices[corner_index-1]`** | **strict EXACT 912 / 59 / `24aa7c5023629b6b`** | +| `d8` | one long counter, `short vertex_index` derived in the loop | frame 0x1124 (the short loses its own slot), 60 relocs | +| `d10`/`d11`/`d12` | bound moved onto the long (`corner_index<=NUMBEROF(vertices)`, `<5`) | 928 `[size]` | +| `d7n` | `d7` with `vertex_index1`), and the vertex written is the corner's, +`vertices[corner_index-1]`. + +**Disclosure.** The two counters run in lockstep, so with `vertex_index` in scope a programmer could +equally have written `vertices[vertex_index]`. The redundancy is a *January* fact (its bytes carry both +counters, at two widths, with two different initial values), and so is which of the two carries the +address (the IV is built from the 32-bit counter, with no `movsx`). `d0r`/`d2`/`d3` measure exactly what +the other choice costs. If the owner rules `corner_index-1` a steering respelling, the fallback is +`d0r.c` — 912/59 `[sha]`, zero credit, R11: do not land it. + +### `_bink_query_analog_controller_buttons` (80) — the park premise is refuted + +This row is on the laws_w2 section D do-not-repeat table ("register allocation and scheduling", five +shapes) and n1 skipped it. It was re-opened only because a fresh census produced a fact no ledger records. + +**Frame census.** January is `push ebx; push esi` and nothing else — **no `push ebp; mov ebp,esp`, no +`sub esp,N`, zero stack slots.** Ours adds `push ebp; mov ebp,esp; push ecx` … `mov esp,ebp; pop ebp` and +one slot `[ebp-1]` for `button_pressed`. Under `/Oy-` VC7 keeps an EBP frame exactly when the function has +stack locals, so the absent frame *proves* January has no stack local here. The two alignment pads ours +carries (`nop` at 0xf, `lea ecx,[ecx]` at 0x2d) exist only because the extra bytes push the two loop heads +onto 0x10/0x30; January's heads at 0x6/0x25 are too far from a boundary to be padded. That is the whole +16-byte gap. + +**Relocation census.** 1 == 1, `_input_get_gamepad_state`, same site. Nothing missing, nothing extra. + +**The defect.** January holds `button_pressed` in BL (`xor bl,bl` @0x2, `test bl,bl` @0x6 loop head, +`mov bl,1` @0x38, `mov al,bl` @0x42) and rematerialises the comparison constant as an immediate +(`cmp byte ptr [eax+0x1c], 1` ×3). Ours pins the constant 1 in EBX in the preheader (`mov bl,1` @0xd) and +spends the three compares as `cmp byte ptr [...], bl`, one byte cheaper each — which evicts the boolean to +`[ebp-1]` and costs the frame, a store/reload pair and both pads. This is the recurring constant-pinning +family (memory note "zero-register pinning residual"), inverted: January spends the callee-saved byte +register on the live boolean, we spend it on a folded constant. + +**Shapes (3).** `q1` declaration-order swap: inert. `q2` `gamepad` hoisted to a function-scope +declaration: inert. **`q3`** — `if (start==1) {p=TRUE;} else if (back==1) {p=TRUE;} else {loop}` — strict +EXACT 80 / 1 / `c88d131ba2ff1ac7`. The third assignment site raises the boolean above the folded constant +in the allocator's ranking; the boolean takes EBX, the constant returns to an immediate, the stack home +and therefore the EBP frame and both pads disappear, and the three sites cross-jump into January's single +`mov bl,1` at 0x38. + +**What the programmer meant.** Poll each gamepad; if START was pressed this frame, or BACK was, skip the +movie; otherwise scan the eight analog-button mirrors for one pressed this frame. `buttons[]` is a `byte` +frames-held tick counter (`update_ticks(byte *ticks, boolean down)`, input.h:209), which is why the test is +against the literal 1 rather than a boolean test. + +**Disclosure.** Both arms carry the same assignment and `||` is the more idiomatic spelling, so this is +the laws_w3 A26 device (admitted in the w3 newly-admitted list) *without* A26's strongest evidence: +January merges all three `je 0x38` into one physical `mov bl,1` block, so its CFG alone does not +distinguish `||` from `else if`. The discriminating evidence is the allocation — the `||` form does not +reach January's frameless BL-carried shape in any arrangement measured here or in the five shapes of the +earlier waves, and the else-if form reaches it byte-for-byte. It is R2-adjacent and needs a verifier +judgement. + +## `_bink_alloc@4` (496) — OWNER-BLOCKED, no new lever + +n1 refuted this park in full and produced a strict-EXACT candidate; +`scratch/workers/n1_bink_playback/reopen_alloc_plus.c` still gates `EXACT 496 _bink_alloc@4` +(`== exact 22 residual 2`) unchanged at this HEAD. The single remaining byte is the corpus-unique `int3` +at 0xd1; `__asm { int 3 }` reproduces it and `__debugbreak()` does not. + +New this wave, and negative: there is no existing breakpoint construct in the tree to reuse. The only +`__asm` in any header is `fast_ftol` (cseries.h:314); no `__debugbreak`/`DebugBreak`/`debug_break` exists +anywhere under `source/`; and the assert family cannot be the source of the int3, because `match_assert` +lowers to `display_assert(...); system_exit(-1);` (= January's `add esp,0x14` = 0x10 + 0x4) while +`system_exit` and `halt_and_catch_fire` are ordinary out-of-line functions (cseries_windows.c:116, +main.c:2766), not traps. So no non-asm route exists, and the n1 verifier's narrow question — a one-site, +unit-local `__asm { int 3 }` in a non-helper function — remains the only opening. The declined cseries.h +`debug_break()` macro is NOT re-proposed and no body depending on an ungranted item is submitted. Zero +shapes spent. + +## Checks + +- Final real-file gate: `== exact 21 residual 3 unwritten 0`, emitted-symbol guard passed, identical to + baseline row for row; `scratch/parkcheck.py` on the real object: parks 3, drift 0. +- Candidate `reopen_draw_frame_plus_query.c`: gate `== exact 23 residual 1`; the row-by-row diff against + the HEAD `--all` listing moves only the two reopened rows and no baseline-EXACT row changes; point guard + passed; parkcheck drift 2, each drifting row reaching exactly its target measurement + ((912, 59, `24aa7c5023629b6b`) and (80, 1, `c88d131ba2ff1ac7`)) with `_bink_alloc@4` unchanged at + 512/51; owner census vs `build/base` and `build/split`: candidate-only `[]`, absent-from-target `[]`, + base-only-lost `[]` (identical to a HEAD control object built the same `--source` way); + `tools/fake_match_scan.py` 0 leads, same as HEAD. `q3.c` and `reopen_draw_frame.c` pass the same checks + individually. CRLF endings preserved throughout (0 bare LF). + +## Reusable laws measured here + +1. **Address-IV identity is a source fact, not a scheduler tie.** When a loop carries two lockstep + counters of different widths, the strength-reduced pointer couples to whichever counter the subscript + is written on, and the coupling fixes the emission slots of *both* increments relative to the body + stores. Detector: two adjacent-instruction permutations that both involve an induction-variable update, + inside windows whose start and end addresses agree — that is not a tie, it is the wrong subscript + variable. Cites `d0r`/`d2`/`d3` vs `d7`. +2. **Subscript spelling sets the cursor bias.** `array[index].field` biases the strength-reduced cursor by + +4 here; an explicit `T *p` walked with `p++` (whether declared outside the loop or re-derived inside + it) biases it by +8, which costs one displacement byte on the store that January emits as `fstp [ecx]`. + So a one-byte size gap in a pointer-walk loop can distinguish the two spellings. Cites `d1`/`d4`/`d5`. +3. **`NUMBEROF` as a loop bound is byte-visible.** `NUMBEROF` is `sizeof/sizeof`, i.e. `size_t`, so a + `short < NUMBEROF(x)` compare goes unsigned (`jb`). A signed `jl` against a small literal in January + therefore proves a plain integer bound and forbids the house-style macro at that site. Cites `d7n`. +4. **An absent EBP frame is a structural proof.** Under `/Oy-`, VC7 emits `push ebp; mov ebp,esp` only for + a function with stack locals. A target prologue of bare `push`es where ours has an EBP frame proves the + target has *no* stack local, which converts a "register allocation" park into a locatable defect; here + the defect was a folded constant pinned into the one callee-saved byte register the live boolean needed. + Alignment `npad`s downstream of such a gap are consequences, not independent differences. +5. **Duplicating an assignment across an else-if chain can unpin a folded constant** (refines laws_w3 A26 + / the zero-register-pinning note): a third source assignment site to a boolean raised it above the + comparison constant in VC7's ranking for the callee-saved byte register. Recorded with the caveat that + January's CFG merges the sites, so the evidence is allocational, not control-flow. diff --git a/docs/object_matching_logs/biped_limp_noodle_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/biped_limp_noodle_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..d0056c04 --- /dev/null +++ b/docs/object_matching_logs/biped_limp_noodle_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,177 @@ +# `biped_limp_noodle.obj` structural park pass (opus5 next-150K lane, wave n3a, 2026-09-15) + +Worker scope: `source/units/biped_limp_noodle.c` only. All three non-exact rows are parked +(`config/parked.json`), so every body referenced below is scratch-only. **The real file is unchanged** +(`git diff --stat -- source/units/biped_limp_noodle.c` is empty). No exact bytes are claimed and no +orchestrator proposal is filed. + +Notes: `scratch/workers/n3_biped_limp_noodle.md`. Scratch: `scratch/workers/n3_biped_limp_noodle/` +(`base.obj`, `vp1r.obj`, `vm6pr.obj`, `vp1_full.txt`, `vm6p_diff.txt`). + +Read first: `scratch/opus5-next150/WORKER_BRIEF.md` (incl. the FINAL 2026-09-15 owner rulings), +`scratch/w2/laws_w2.md`, `scratch/w3/laws_w3.md`, and every ledger for this object: +`biped_limp_noodle_obj_opus5_250k_w3c_20260915.md`, `biped_limp_noodle_obj_opus5_150k_w1_20260914.md`, +`biped_limp_noodle_codex_checkpoint.md`, `bipeds_obj_jonas_limp_flight_pair_20260831.md`, +plus `scratch/workers/biped_limp_noodle.md` and `scratch/workers/w3c_biped_limp_noodle.md`. + +## Baseline and final (identical) + +Whole-TU gate with `--forbid-emitted-symbol _point_from_line3d`: + +| Function | Target padded / meaningful / relocs | Real file | Status | +| --- | --- | --- | --- | +| `_biped_limp_noodle_get_max_relaxation_iterations` | 16 | exact | EXACT | +| `_biped_limp_noodle_valid_joint_rotation` | 1248 / 1244 / 34 | 1216 / 34 | residual (parked) | +| `_biped_limp_noodle_move_relax_and_constrain_positions` | 1968 / 1955 / 66 | 1888 / 66 | residual (parked) | +| `_validate_real_vector3d_axes3` | 496 | exact | EXACT | +| `_biped_limp_noodle_adjust_orientations` | 704 | exact | EXACT | +| `_biped_limp_noodle_relax_nodes_onto_environment` | 256 / 244 / 9 | 240 / 9 | residual (parked) | + +`== exact 3 residual 3 unwritten 0` at baseline and at the end; the `_point_from_line3d` guard passes; +`scratch/parkcheck.py` reports parks 3 / drift 0; `tools/fake_match_scan.py` finds 0 leads on the real file. +`branch_sweep.py` (8 unique blobs) shows the current blob `706055aa` is the best in history — no donor. + +## 1. Mandatory wave-n3 census + +The wave-n3 method asks whether a fresh census refutes each park's recorded premise (a `[size]`/`[relocs]` +gap that the park text calls a compiler tie but that is really a source defect: a missing statement whose +relocation January owns, a hand-expanded initializer, a one-use alias, a wrong declared width, or wrong +control flow). The census was run before any shape. + +### 1.1 Relocation census by target (`relocdiff --count-by-target --allow-structural`) + +**All three rows carry the identical relocation multiset to January**, target for target and (for +`relax_nodes`) in the identical order; only addresses shift. The rows `relocdiff` prints as differing in +the summary tables are the csplit representation of the same literal (`symbol:__real@3f800000` versus +`defined-noncode:.rdata:__real@3f800000`, whose `symbolic=` form resolves equal) — see +`csplit-relocation-alias` / laws_w3 A45 for the class. + +* `relax_nodes_onto_environment` 9/9; `valid_joint_rotation` 34/34; `move_relax_and_constrain_positions` 66/66. + +**No call and no global is missing from any of the three bodies.** The "a missing relocation is a missing +statement" route is closed for this object. + +### 1.2 Frame census + +* `relax_nodes_onto_environment`: January `sub esp,0x10`, ours `sub esp,0x10`. Four homed cells each side. + January: `[-0x10]` animation_graph, `[-0xc]` node_matrices, `[-8]` node_index, `[-1]` relaxation_complete. + Ours: `[-0x10]` biped, `[-0xc]` animation_graph, `[-8]` node_matrices, `[-1]` relaxation_complete. + Neither side lacks or adds a slot; the *set* of homed values differs by exactly one swap + (January spills `node_index` and keeps `biped` in ESI; ours does the reverse). + `alndiff --include-equal`: 103 target instructions including 12 tail-padding bytes versus 93 ours + including a 4-byte loop-top pad — **the same 91 real instructions, the same control flow, the same widths.** +* `valid_joint_rotation`: January `sub esp,0xe4`; the real file `sub esp,0xcc` (the park's recorded first + divergence); the w3c scratch body `vp1.c` `sub esp,0xe4`, 431/431 instructions, **size 1248** — re-gated + this wave as `residual 1248 ... [sha]`, i.e. the `[size]` tag is gone. +* `move_relax_and_constrain_positions`: the w3c scratch body `vm6p.c` reproduces January's frame + **slot for slot with identical per-slot reference counts** (34 distinct `[ebp±N]` cells, including the + `[ebp-0x20]` integer-conversion temporary and the `[ebp-0x27]` byte the park text names). The real file + differs at −0x48/−0x44/−0x40/−0x32/−0x28/−0x27/−0x24/−0x23/−0x20. Instruction counts: target 631, + `vm6p` 619, real file 598. Full table in the worker notes §0.3. + +### 1.3 Verdict + +* `relax_nodes_onto_environment` — the park premise is **CONFIRMED**, not refuted: same frame, same + relocations, same instruction count, same widths, same control flow; the only difference is which of + `biped` / `node_index` loses the last callee-saved register. +* `valid_joint_rotation` and `move_relax_and_constrain_positions` — the **recorded** premises (a frame gap + at `+0x03`; a `[ebp-0x20]` vs `[ebp-0x1C]` homing choice) are **stale rather than newly refuted**: w1 and + w3c already found the source defects behind them and fixed them in scratch. The remaining blockers are + not structural (below). + +**No wave-n3 "hidden source defect" was found.** The structural work on this object was already done; the +residuals are a FINAL-OUT helper spelling and one allocation tie. + +## 2. `_biped_limp_noodle_valid_joint_rotation` — three instructions, prohibited-helper-blocked + +`alndiff` of `scratch/workers/w3c_biped_limp_noodle/vp1.obj` against `build/split` (re-run this wave) shows +the candidate is **instruction-for-instruction identical to January at every address except one +3-instruction block**. The entire residual is the association of the inlined `dot_product3d` inside the +assert's `plane3d_distance_to_point(&plane, &rotate_to_position)`: + +* January (T 0x293): `fld [-0xc]; fmul [-0x44]` (j-term, point field first), `fld [-0x10]; fmul [-0x48]` + (i-term, point first), `faddp`, `fld [-0x40]; fmul [-8]` (k-term, **normal** first), `faddp`, + `fsub [-0x3c]` — the source association `(i*bi + j*bj) + k*bk`, with the leaves of the first add swapped. +* Ours emits the same three products with the **same per-term operand orders** but associates `(j + k) + i`. +* The preceding `set_real_point3d` compute/store block (T 0x271-0x291, + `fld [-0x38]; fmul st(1); fadd [esi]; fstp [-0x10]` and its y/z copies) is byte-equal, so + `rotate_to_position` is produced exactly as January produces it. + +The term rank is therefore set by the leaf ages of `plane_normal.i/j/k`, which every admissible call-site +spelling reads **by name** (laws_w3 A35(1)/(4)). I enumerated every `__inline` helper in `source/math/real_math.h`: +`point_from_line3d` (line 1077) is the only one whose body is `result->x = v->i*t + p->x`; a +`scale_vector3d` + `add_vectors3d` pair splits the fused multiply-add into six stores. `vj2.c` is already +strict EXACT with the inline helper and fails the lane guard, and the `_point_from_line3d` COMDAT ruling is +FINAL (brief §1; laws_w3 C2 and R14, which also rejects re-proposing it). + +Shapes spent to date: w1 j1/j2/j3/j4/j5 and w3c vp1/vp2/vp3 (nine, including the strict-exact OUT body); +the post-tie 2-shape budget is spent. **No shape was spent this wave** — the triage result is SKIPPED-EXHAUSTED. + +## 3. `_biped_limp_noodle_move_relax_and_constrain_positions` — 31 real blocks, two of them OUT-shaped + +`alndiff` of `vm6p.obj` against `build/split` gives 76 blocks, of which **31 are real** after filtering the +csplit representation rows. They fall into four families: + +1. **First-node block (T 0x1a0-0x1e8).** January forms the segment with full indexed addressing + (`fld [edi+esi+0x28]; fsub [eax+esi+0x28]`, then the y pair) and only afterwards emits the two + `lea [edi+esi+0x28]` / `lea [eax+esi+0x28]` pointer forms; ours emits the `lea`s first and reads + `[ebx]`/`[edi+4]`. This is the accessor-binding split (January uses both forms, per site). w1 tried the + reorder as m7/m8/m9/m10/m14 — all byte-identical to m6. +2. **Two squared-term hoists (T 0x356, T 0x43f).** Ours hoists `fld [-0x78]; fmul [-0x78]` above the + neighbouring `xor ecx,ecx`; January emits it after. Pure schedule; the differing 2- versus 3-byte + alignment pad (`mov edi,edi` versus `lea ebx,[ebx]`) follows from it. +3. **Endpoint loop (T 0x481-0x4d3).** January colours the counter EDX and the endpoint pointer ECX (ours + ECX/EAX) and its layout places the `mov [ebp+ecx*4-0x38],edx; jmp` block between the two ternary arms + where ours places it at the tail. Register colouring plus block placement — laws_w3 A43 records that no + source lever moves which duplicate tail survives. +4. **Apply loop (T 0x632-0x67c).** January holds the scale in `st` across the three components + (`fld [-0x10]; fmul st(1); … fstp st(0)`) and, after `collision_move_point`, loads the **vector component + first** in each product (`fld [-0x18]; fmul [-4]`), where ours loads the scalar first and re-reads it from + memory. That is the two-pointer `point_from_line3d` body; `vm6.c`, which uses it, reaches January's 1968. + +Family 4 occurs at two sites (ray origin and endpoint push), so this row is unreachable in this lane +whatever happens to families 1-3. + +`vm6p.c` re-gated this wave: **1952/66** (real file 1888), with `_valid_joint_rotation` simultaneously at +1248/34 `[sha]` and all three sibling rows EXACT, guard clean. `parkcheck` on that object reports +`DRIFT valid_joint_rotation 1216 -> 1248` and `DRIFT move_relax 1888 -> 1952`, so it cannot land even as a +remeasure (R11 rejects zero-credit park remeasures that are not tied to a reopen). Fifteen shapes are spent +(m1-m14 plus vm6p); **none this wave**. + +## 4. `_biped_limp_noodle_relax_nodes_onto_environment` — allocation tie, confirmed by the census + +Re-derived from `alndiff --include-equal` this wave: the guard is `cmp dl,cl; sbb cl,cl; inc cl`, i.e. +`relaxation_complete = biped->biped.limp_body_current_relaxation_iterations >= +biped->biped.limp_body_max_relaxation_iterations` (`[esi+0x47c]` / `[esi+0x47d]`), stored to `[ebp-1]` and +returned on both exits — identical on both sides. The snapshot loop uses the same two strength-reduced IVs +(`add eax,0x34`, `add ecx,0xc`) and the same two fresh pointer copies per trip (`mov edx,eax` / `mov edi,ecx` +in January, `mov esi,eax` / `mov edi,ecx` in ours), i.e. the same struct-copy source shape. The whole +residual is that January's loop scratch is EDX so ESI can carry `biped` across both calls, while ours takes +ESI for the loop scratch and spills `biped` to `[ebp-0x10]`. Ours is the cheaper schedule (one store plus one +reload versus a per-trip reload and store of `node_index`), so January took the option VC7's own cost model +disprefers — consistent with w1's `rF` probe, which flipped the allocation by re-reading the two biped fields +at the guard but emitted an extra compare and is therefore not January's source. + +Detector sweep this wave: A23 cannot fire (our loop already carries its increment clause; w1's `while` +respelling rD was byte-identical); A24 cannot fire (frames equal, no parameter home reused for a temp); +A28/A31/A33/A35/A38/A42 have no site. laws_w3 A44 ("frame gaps that are allocation, not missing locals") is +the classification. Ten shapes spent (rA, rB, rC, rD, rE, rF, rH2, rH3, rw1, rw2); **none this wave**. + +## Proposals + +None. The only lever that closes `valid_joint_rotation` and `move_relax_and_constrain_positions` is the +`_point_from_line3d` COMDAT, whose OUT ruling is FINAL for this lane (brief §1; laws_w3 C2 and R14), and no +header, config or `symbols.json` change is needed by any of the three rows. + +## Reopen criteria (all unchanged from w3c) + +* **`valid_joint_rotation`** — an owner ruling admitting an inline `point_from_line3d` that emits no COMDAT. + `scratch/workers/biped_limp_noodle/vj2.c` is already strict EXACT; reopening it drifts the + `move_relax_and_constrain_positions` park (1888/66 `34b872cd` -> `683135cea08a8197`, the call-site register + ABI), so both must move in one commit. Do not re-spend j1/j3/j4/j5 or vp1/vp2/vp3. +* **`move_relax_and_constrain_positions`** — (a) an authentic spelling for the first-node-block schedule AND + (b) either the same owner ruling (then `vm6.c` is the base) or an admissible two-pointer helper spelling. + Start from `scratch/workers/w3c_biped_limp_noodle/vm6p.c` (1952/66, guard-clean). Do not re-spend m1-m14. +* **`relax_nodes_onto_environment`** — an authentic spelling that raises `biped`'s allocation weight above + the loop index while still branching on the stored `relaxation_complete` boolean. Do not re-spend + rA-rH3, rw1, rw2. diff --git a/docs/object_matching_logs/glow_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/glow_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..a9fd17d9 --- /dev/null +++ b/docs/object_matching_logs/glow_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,194 @@ +# `glow.obj`: Opus5 next-150K lane, wave n3a (structural park pass, 2026-09-15) + +## Result + +Nothing landed. `source/objects/widgets/glow.c` is **unchanged** (`git diff --stat` empty). +Baseline and final real-file gate, with the `_point_from_line3d` guard: **exact 24 / residual 1 / +unwritten 0**, row-for-row identical. Park drift 0, owner census identical to `build/base`, +fake scan 0 leads. + +The wave's brief was a structural park pass: run a fresh frame and relocation census on a parked +`[size]`/`[relocs]` row on the theory that the recorded park premise would not survive it, as it did +not for the five parks wave n1 closed. **Here the census confirms the premise**, and this wave turns +the confirmation from an inference into a measurement. Three things are new: + +1. a **measured** proof (shape v3) that no defined-C body can reach January's 1,744 bytes even when + its frame and its entire 43-relocation census already match; +2. the **refutation of the `precondition` branch** of the park's own reopen criterion; +3. the **blocker set narrows from three to two** — the point-to-vector casts the w1 ledger counted as + a blocker are now explicitly admitted by laws_w2 and laws_w3. + +## Duplicate checks + +Ledgers read: `glow_obj_opus5_250k_w3d_20260915`, `glow_obj_opus5_150k_w1_20260914`, +`glow_obj_house_rule_reconciliation_20260908`, `glow_obj_opus_source_admission_hold_20260906`, +`glow_obj_jonas_complete_hcea_packet_20260830`, `glow_obj_jonas_new_20260829`, +`glow_obj_jonas_delete_20260829`, `glow_obj_jonas_nonuniform_spline_pair_20260827`, +`glow_obj_jonas_parametric_line_leaf_20260821`. The w1 do-not-repeat list (four-statement knot copies, +a bound marker pointer, cross-product reordering, the unnamed `cos`/`sin` form, any further defined-C +repair aimed at exactness) was honoured; R13 binds it. `git log --all -- source/objects/widgets/glow.c` +lists 18 commits, newest `db720270e`. `branch_sweep`: 13 unique blobs; the best historical blob +(`aab61c21`) is exact 8 / unwritten 17 — no historical blob beats the current 24/1/0. + +## Census (mandatory) + +| | padded / meaningful / relocs / sha16 / frame | +| --- | --- | +| January target | 1744 / 1729 / 43 / `84720a5067d22512` / `sub esp,0xc4` | +| real file (parked body) | 2528 / — / 55 / `fb7bb3c6b7236d62` / `sub esp,0xf4` | + +### Frame census — the gap is a SURPLUS local, not a missing one + +January's slot map, read off the three `nonuniform_cubic_spline_vector3d` argument `lea`s (pushes are +right-to-left, so the last push before each call is the result pointer): + +| range | size | local | +| --- | ---: | --- | +| `[ebp-0xc4] [-0xb8] [-0xac] [-0xa0]` | 48 | `sides[0..3]` | +| `[ebp-0x94]` | 12 | `up` (result of spline 2) | +| `[ebp-0x88]`, `[ebp-0x84]` | 4 + 4 | one spilled `real`; one strength-reduced pointer CSE (`lea ecx,[esi+ecx*2+0x22a]` at +0x18d) | +| `[ebp-0x80] [-0x74] [-0x68] [-0x5c]` | 48 | `ups[0..3]` | +| `[ebp-0x50] [-0x44] [-0x38] [-0x2c]` | 48 | `positions[0..3]`, passed **directly** as the spline's vector arguments | +| `[ebp-0x20]` | 12 | `side` (result of spline 3) | +| `[ebp-0x14]` | 4 | long marker index | +| `[ebp-0x10] [-0xc] [-8] [-4]` | 16 | `knots[0..3]` | + +Sum = 196 = `0xc4`. Spline 1's result pointer is `lea esi,[edi+0x2c]`: January splines straight into +`particle->position`, so it has neither a `position` result local nor a vector staging array. + +Ours is 244 = `0xf4`. The whole 48-byte difference is one object our source adds and January does not +have: `real_vector3d position_vectors[4]`, plus the four-trip loop that fills it, which exist only so +`real_point3d positions[]` never has to be cast to `real_vector3d *`. Measured directly below: removing +that hunk alone (v2) takes the frame to `0xb8` and the size down 64 bytes; adding January's `delta` + +`point_from_parametric_line` routing on top (v1) reproduces `sub esp,0xc4` exactly. + +### Relocation census by target — nothing missing, twelve surplus constants + +| relocation target | January | ours | delta | +| --- | ---: | ---: | ---: | +| `_display_assert` | 3 | 3 | 0 | +| `_system_exit` | 3 | 3 | 0 | +| `_nonuniform_cubic_spline_vector3d` | 3 | 3 | 0 | +| assert file + predicate literals | 6 | 6 | 0 | +| `__real@3f000000` (0.5f) | 14 | 20 | **+6** | +| `__real@3e800000` (0.25f) | 7 | 10 | **+3** | +| `__real@3f400000` (0.75f) | 7 | 10 | **+3** | +| TOTAL | 43 | 55 | **+12** | + +No missing call, no missing global, no missing literal: ours is a strict superset. `cos`/`sin` lower to +`fcos`/`fsin` and carry no relocation; `cross_product3d`, `vector_from_points3d` and +`point_from_parametric_line` are inlined and are relocation-invisible. + +An interpolated `real_vector3d` costs three constant references, so the surplus decomposes exactly: ++3 at 0.25f and +3 at 0.75f is one extra interpolated pair (the 2-marker path's `sides[1]`, `sides[2]`), +and +6 at 0.5f is two extra interpolations (the two 3-marker sub-cases' interior side control). +January's 2-marker arm spends 7 × 0.25f and 7 × 0.75f — positions(3) + ups(3) + knots(1) each — and each +3-marker sub-case spends 7 × 0.5f on the same three objects. **Side controls are absent from January's +constant budget entirely.** + +## Does the census refute the park premise? No — and v3 proves it by measurement + +Shapes (3 of the 5-shape budget; all scratch, the real file was never touched): + +| shape | what it is | size / relocs / frame | verdict | +| --- | --- | --- | --- | +| real (parked) | defined sides, defined knots, staging array | 2528 / 55 / `0xf4` | baseline | +| **v1** `scratch/workers/n3_glow/v1.c` | January's routing and casts (`delta` + `point_from_parametric_line`, named `sine`/`cosine`, knot copy last in the marker loop) but sides and knots still fully defined | 2544 / 55 / **`0xc4`** | frame exact; the whole remaining 800 B / 12-reloc gap is the side+knot initialisation | +| **v2** `scratch/workers/n3_glow/v2.c` | the current parked body with ONLY the point→vector cast hunk (drop `position_vectors[4]` and `position`, spline straight into `particle->position`) | 2464 / 55 / `0xb8` | strictly closer, −64 B, 24 siblings unchanged | +| **v3** `scratch/workers/n3_glow/v3.c` (measurement probe, not a proposed source form) | v1 with the interior side controls filled by bare struct copies — the cheapest defined fill, costing zero constants | 2336 / **43** / **`0xc4`** | frame and relocation census both match January exactly, and it is still **+592 bytes** | + +v3 is the decisive measurement. Give the function January's frame and January's exact 43-relocation +census, then fill `sides[0..3]` in the cheapest way C allows, and it still emits 592 bytes January does +not contain — four inline `cross_product3d` expansions for the endpoint controls plus the interior +copies. **No defined-C body can reach 1744/43.** The gap is not a repair that could be written more +cheaply; it is code January never emitted, because January never gave those controls a value. + +## The `precondition` branch of the reopen criterion is refuted + +The park admits "precondition evidence", i.e. evidence that 2-/3-marker glows can never reach the side +spline, which would make the read unreachable rather than indeterminate. That branch is dead: + +- `glow->number_of_markers` is whatever `object_get_marker_by_name(object_index, + definition->attachment_marker, glow->markers, MAXIMUM_GLOW_MARKERS)` finds on the model (glow.c:1240); + `MAXIMUM_GLOW_MARKERS` is 5, so the range is 0..5 and the count is tag/model data. +- Both call sites guard on `glow->number_of_markers > 1`, never `> 3`: `glow_trailing_particle_new` + at glow.c:1167, and the `glow_normal_particle_update_position` loop at glow.c:1375/1381. +- January's own assert inside the function is `glow->number_of_markers > 1` (glow.c:1083, January line + 1083, literal `glow->number_of_markers > 1`), not `> 3`; it is one of the three asserts in the + relocation census. +- January hand-wrote dedicated `case 2:` and `case 3:` arms with roughly 600 bytes of bespoke + interpolation. A path the author believed unreachable does not get that. + +The 2- and 3-marker paths are first-class reachable paths and the indeterminate reads are a live +original defect, not dead code. No further wave should hunt a precondition. + +## The blocker set narrows from three to two + +The w1 ledger listed three blockers; the third is now admitted lane policy and must not be avoided: + +- laws_w2 section B, "Admitted, so don't avoid them": *point-to-vector `(real_vector3d *)` casts*. +- laws_w3 section B, "Newly ADMITTED": *`(real_point2d const *)&point3d` casts into 2D helpers + (32 source sites, HCEA)*. + +What remains is a single policy question: may this one function reproduce two reads of an indeterminate +value from address-taken automatic arrays? + +## Cross-build corroboration (evidence only, never a January byte proof) + +HCEA's disasm-verified reconstruction of the shipped Xbox build +(`...\halocea-review\src\blam\objects\widgets\get_particle_world_position.c`, 0x8380BB00) records the +same two quirks independently of this campaign: the side-vector control points are never filled on the +2-/3-marker paths, and the 3-marker interval-1 path reads `knot1` from the never-written knot scratch. + +## Candidate, re-verified at this HEAD (`6ebd05a96`) + +`scratch/workers/n3_glow/owner_candidate_n3a.c` — a byte-identical copy of +`scratch/workers/glow/owner_candidate.c`, which is the **current real file** with only this one body +replaced and two `/* BUG (preserved for exact matching): ... */` comments added: + +- `gate.py source/objects/widgets/glow --source ... --all --forbid-emitted-symbol _point_from_line3d` + → **exact 25 / residual 0 / unwritten 0**; `_get_particle_world_position` EXACT at 1744. It closes + the whole object and needs no header or config change (the packet w1 wanted for + `_glow_trailing_particle_new` has landed; that row is exact at HEAD with the unchanged real body). +- point guard: pass. +- `scratch/parkcheck.py`: `DRIFT _get_particle_world_position 2528 55 → (1744, 43, 84720a5067d22512)` — + drift only on the reopened park itself, to the target, which is the expected reopen signature. +- owner census vs `build/base` and `build/split`: candidate-only `[]`, base-only `[]`. +- `tools/fake_match_scan.py`: 0 review leads. + +## Final real-file state + +`exact 24 residual 1 unwritten 0 (of 25 listed)`; guard pass; `parks 1 drift 0`; owner census +candidate-only `[]` / base-only `[]`; fake scan 0 leads; `git diff --stat` empty. + +## Owner-ruling proposal (re-raised with the v3 measurement) + +Admit, for `_get_particle_world_position` only, the reproduction of two original defects under the +docs' `/* BUG (preserved for exact matching): ... */` form, then replace the parked body with the body +in `scratch/workers/n3_glow/owner_candidate_n3a.c` and unpark it in a dedicated commit: + +1. the 2- and 3-marker paths give **no** value to `sides[0..3]`, which the unconditional third + `nonuniform_cubic_spline_vector3d` call then reads; +2. on the 3-marker `parent_marker_index == 1` path the midpoint knot is written over `knots[2]` instead + of `knots[1]` (`marker_time_index[1]` loaded at +0x2EA, midpoint stored at +0x5C6 into `[ebp-8]`, + `[ebp-0xc]` read unwritten at +0x5D2), so `knots[1]` is never set. + +Both are reads of an indeterminate value from an address-taken automatic array, which brief section 5 +lists among the undefined-behaviour exclusions. Supporting evidence: the frame and relocation censuses +above; the v3 measurement that the cheapest defined fill is +592 bytes at January's own frame and +relocation census; HCEA's independent shipped-build reconstruction naming both quirks; the refuted +precondition branch; and a candidate that is strict EXACT, closes the object 25/25, loses no sibling, +adds no owner and scans clean. Value: **1,744 padded bytes**, the last non-exact row in `glow.obj`. + +If the owner declines, one optional zero-credit action is measured and ready: re-baseline the park to +`scratch/workers/n3_glow/v2.c` (2464 / 55, frame `0xb8`, 24 siblings unchanged, owner census clean, +fake scan clean). It is strictly closer than the current 2528 / 55 base and removes an invented +`position_vectors[4]` staging array that exists only to dodge a cast the lane has since admitted. It +carries zero strict credit and, being a parked-body edit, is the orchestrator's call; it is offered, +not claimed. + +## Reopen criterion (restated, narrowed) + +An owner policy ruling admitting the two preserved defects above. The precondition branch is refuted +and the cast branch is admitted, so no further defined-C repair and no further January-side evidence +can move this row. diff --git a/docs/object_matching_logs/hud_unit_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/hud_unit_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..c2452609 --- /dev/null +++ b/docs/object_matching_logs/hud_unit_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,110 @@ +# hud_unit.obj — opus5 next-150K lane, wave n3a (structural park pass), 2026-09-15 + +Unit: `source/interface/hud_unit.c`. Worker notes: `scratch/workers/n3_hud_unit.md`. +Scratch: `scratch/workers/n3_hud_unit/` (`w1.c/.obj` control, `v1/d1/d2/d3/d4` probes, `w1.aln`, +`w1.full.txt`, `score.py`). + +## Baseline and final state + +| | baseline (HEAD `6ebd05a96`) | final | +| --- | --- | --- | +| gate | `== exact 21 residual 1 unwritten 0` | `== exact 21 residual 1 unwritten 0` | +| `_point_from_line3d` guard | passes | passes | +| parkcheck (`build/base/source/interface/hud_unit.obj`) | `parks 1 drift 0` | `parks 1 drift 0` | +| fake_match_scan | 0 leads | 0 leads | +| real file modified | — | **no** (`git diff --stat` empty) | + +Nothing landed. Nothing was proposed for the orchestrator. The single non-exact row +`_hud_render_unit_interface` is PARKED and was investigated in scratch only. + +## `_hud_render_unit_interface` — NOT-LANDED, park premise reclassified + +Target 3504 padded / 3493 meaningful / 102 relocs / sha16 `b82d575e1154cad1`. +Park base (real file) 3472 / 102, gate tag `[size 3472!=3504, sha]`, struct-diff **127 blocks / 308 insns**. +Frontier body `scratch/workers/hud_unit_messaging/hud_unit_candidate.c` (identical to +`scratch/workers/w3c_hud_unit/w1.c`), re-gated at this HEAD: **3504 / 102, tag `[sha]` only**, +struct-diff **41 blocks / 56 insns**, all 21 sibling rows still EXACT. +**The `[size]` half of the park tag therefore describes the stale park BASE, not the frontier; it is +not evidence of a structural source gap.** + +### Census performed (method mandated by the wave brief) + +1. **Relocation census by target** (`relocdiff --count-by-target --allow-structural`, w1 vs January): + 102 / 102, **every target row equal** across 38 distinct targets in identical order + (`_local_player_count` 12, `_hud_draw_static_element` 8, `_fast_ftol_C` 6, `_game_time_get` 5, …). + The only deltas are relocation ADDRESS shifts after the first code divergence (reloc 29, + 0x30b vs 0x30e) and the known `--source` literal-COMDAT naming artifact. + ⇒ no missing call, no missing global, no missing/extra re-fetch ⇒ **no missing statement**. +2. **Frame census** (January `sub esp,0x424`, ours `sub esp,0x420`): aggregates deepest first — + January `stack_buffer(512) unit_indices(72) unit_hud_indices(72) health_meter(104) + absolute_placement(36) overcharge_meter(104) color(20)`; ours `stack_buffer(512) health_meter(104) + unit_indices(72) unit_hud_indices(72) absolute_placement(36) overcharge_meter(104) color(20)`. + Exactly two deltas: `health_meter` ranked 2nd instead of 4th, and January owns **one extra 4-byte + scalar** (35 vs 34 dwords in −0x8c…−4), which is the entire frame gap. + That scalar is a compiler staging cell, not a source local: at January `+0x55e fst dword [ebp-0x44]` + ours emits `+0x573 fst dword [ebp-8]`, reusing the `draw_flags` home that January has spilled + (`+0x3ac mov [ebp-8],ebx`, `+0x601`, `+0x630`) because it freed EBX for the `color[]` pointer IV. +3. **Local-set and type census — new evidence this wave.** `DIA2Dump.exe -sym hud_render_unit_interface` + on the HCEX SHIP PDB (brief §10.5) lists `unit_count`, `player_index`, `long unit_indices[0x12]`, + `long unit_hud_indices[0x12]`, `auxilary_flags`, `aux_activated_when_disabled_flags`, + `float auxilary_values[0x1]`, static `long overcharge_count`, `overcharge_meter`, `health_meter` + (both `struct meter_hud_element_definition`), `unsigned long color[0x5]`, + `struct hud_absolute_placement_definition absolute_placement`, `union point2d corner`. + **Our reconstruction matches 1:1 in names, counts and types**; no local is missing or invented + (`stack_buffer`/`return_eip` are January's debug stack-guard pair, absent from the 2011 build). + `short value_scale` is independently 16-bit signed on both sides (16-bit `test`, `movsx` before + `fild`) and matches the field type at `unit_hud_interface_definition.h:119`. + +Instruction totals are 1062 vs 1062 and every diff block is a register/slot substitution. +⇒ The census **refutes** every source-defect hypothesis the structural pass looks for (missing +statement, extra/missing local, wrong width, wrong control flow) and **confirms** the park's own +recorded verdict: register/x87 colouring plus one frame-rank decision. + +### Probes (5, scratch-only, none landed, real file untouched) + +| # | shape | measurement | +| --- | --- | --- | +| w1 | control: prior best body re-gated at this HEAD | 3504/102 `[sha]`, 41 blocks / 56 insns, frame 0x420 | +| v1 | `health_meter` declared one scope out (B13, the `health_dont_show` if) | 3520/102 (**+16 B worse**), frame 0x420, rank unchanged | +| d1 | diagnostic rename `health_meter`→`zz_meter` (never a landing; tests whether the rank is name-hash driven the way `.bss` order is) | 3504/102, identical 41/56, rank unchanged ⇒ **rank is NOT name-driven** | +| d2 | `health_meter` declared in B1 (outer if, after `auxilary_values`) | 3504/102; order becomes `ui, uhi, abs, och, health, color` — the index arrays reach January's ranks 2-3 but `health_meter` overshoots to rank 6; 42/58 | +| d3, d4 | `health_meter` declared in B6 (`hud_definition` block) / B5 (`while` body) | identical layout to d2; 41/56 and 42/58 | + +**Law candidate (offered, not landed):** VC7's frame ranking of same-alignment aggregates is neither +refs/size (w3c v2), nor lexical depth by one level (v1), nor the local's name (d1); it flips between +"deepest" and "next-to-last" on whether the aggregate is declared inside the innermost if-chain that +uses it (d2/d3/d4), and no setting of that switch reproduces January's intermediate rank. + +### Rejected without building (with reason) + +- Hand pointer walk / alias over `color[]` to force the IV — R16 and laws_w2 A5 counter-law. +- `volatile`, barriers, operand permutations for x87 order, redundant stores — prohibited (brief §7); + this residual is precisely the class such devices would fake. +- `if (body_vitality < max_cutoff)` — refuted by the bytes: January `+0x727 test ah,1` tests C0 alone + (`!(a >= b)`); an ordered `<` compiles to `test ah,5`. Already ruled a semantic regression 20260904. +- `color[]`/`overcharge_meter` declared with initializers at the top of B9 — refuted by the bytes: + January initialises `color` (`+0x46e`) after `_game_engine_running` (`+0x3e1`) and after the + `overcharge_meter` copy (`+0x430`), so the nested block is January's own C89-forced shape. +- `health_meter` as a declaration initializer — impossible in C89 here, and January's `rep movsd` + (`+0x70e`) follows the `value_scale` test (`+0x6eb`), which is our current form. +- A1 static linkage, A23/A24/A28/A40 detectors — do not fire (statics already static and covered by the + 2026-09-15 census; all helpers out-of-line on both sides; the only parameter has no packed temp). + +### Reopen criterion (supersedes the w3c wording) + +Reopen only when a source-level lever (a) stops VC7 keeping `hud_unit` in EBX across the shield +`draw_flags` chain — reproducing January's `+0x380 mov edx,[ebp-0x24]` rematerialisation, which alone +yields the `color[]` pointer IV, the `draw_flags` spill and the extra `fast_ftol_C` staging cell +(frame 0x424) — AND (b) ranks `health_meter` between `unit_hud_indices` and `absolute_placement`; +refs/size, lexical scope and the local's name are now all measured inert or overshooting. A C2 +frame-allocator decode is the natural next evidence source. The park's own clause ("reopen only for new +authentic January source/type/compiler-context evidence, not spelling or allocation searches") keeps +this otherwise closed. + +### Orchestrator options (zero credit, not proposed) + +The park BASE could be re-measured to `scratch/workers/n3_hud_unit/w1.c` (3504 / 102 — January's exact +size and relocation count, 41 blocks / 56 insns versus the current base's 127 / 308), which would also +retire the misleading `[size 3472!=3504]` gate tag. This was already offered by waves w1 and w3c and not +taken; R11 applies (strictly-closer body that changes size, not tied to a reopen). No header, config or +`symbols.json` change is needed for this unit. diff --git a/docs/object_matching_logs/input_xbox_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/input_xbox_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..8f1ada80 --- /dev/null +++ b/docs/object_matching_logs/input_xbox_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,206 @@ +# `input_xbox.obj` structural park pass — opus5 next-150K house-clean lane, wave n3a (2026-09-15) + +## Scope, baseline and outcome + +- Worktree `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/next-150k-house-clean-20260915`, + HEAD `6ebd05a96`, lane baseline `ae10935da`. +- Owned source: `source/input/input_xbox.c` only. No header, config, build, tool or other source was touched and + nothing was committed. Both remaining non-exact rows are parked in `config/parked.json`, so every measurement was + made in scratch; the real file is byte-unchanged (`git diff --stat -- source/input/input_xbox.c` empty). +- **Baseline correction.** The wave prompt listed `== exact 24 residual 3` with + `_input_update_analog_button_state` residual. That is stale: the w3d park reopen landed in the lane baseline + commit `ae10935da`, so at this HEAD the measured real-file gate (with the `_point_from_line3d` guard) is + + == exact 25 residual 2 unwritten 0 + + and `config/parked.json` holds only two input_xbox entries. There is no third row and no credit available for it. +- Final real-file gate: identical to the baseline, `== exact 25 residual 2 unwritten 0`. + `scratch/parkcheck.py`: 2 parks, 0 drift. `tools/fake_match_scan.py`: 0 review leads. +- Nothing landed; nothing proposed. Both rows were re-censused from January's bytes and both park premises were + found structurally refuted, but what survives the refutation is a C2 register-allocation tie, which laws_w2 R11 + and brief section 5 both forbid spending further on. + +| function | January padded / relocs / sha16 | real file | best scratch body | outcome | +| --- | --- | --- | --- | --- | +| `_input_get_device_states` | 1232 / 28 / `56316233dc2ab683` | 1280 / 32 / `b7cded6529564446` | 1216 / 28 (`n0`, = w2 `fn/d2.c`) | NOT LANDED — 1 instruction / 1 encoding byte | +| `_input_update_keyboard_devices` | 640 / 49 / `80bcd2f76c52c728` | 640 / 51 | 640 / 49 `[sha]` (`n0`, = w2 `fn/k3.c`) | NOT LANDED — L3 pin + the same L2 tie | +| `_input_update_analog_button_state` | 48 / 0 / `5850a17e99380c54` | **EXACT** | — | SKIPPED-EXACT (landed at `ae10935da`) | + +Ledgers read: all 13 `input_xbox_obj_*.md`, in particular `input_xbox_obj_device_keyboard_pollers_wave_20260904.md` +(owns both park criteria), `input_xbox_obj_opus5_150k_w2_20260914.md` (shapes d1-d6, k1-k4) and +`input_xbox_obj_opus5_250k_w3d_20260915.md` (hd1, e1-e6, hd_d6, kA/kB/kE/kF and laws L1-L3) — w3d ran the identical +structural park-pass method on these same two rows earlier today. Also `scratch/w2/laws_w2.md` (R11 rejects the +`input_xbox d2/k3` zero-credit park remeasures by name), `scratch/w3/laws_w3.md`, and the next-150K worker brief. +`git log --all -- source/input/input_xbox.c`: 23 commits, latest `ae10935da`. +`branch_sweep.py source/input/input_xbox`: 18 unique blobs; the current file is the best (exact 25). + +Worker notes with the full tables: `scratch/workers/n3_input_xbox.md`. +Scratch: `scratch/workers/n3_input_xbox/` (`sub.py`, `mk.sh`, `real.obj`/`real.gate.txt`, the combined structural +base `n0.c`/`n0.obj`/`n0.gate.txt`, `full.txt` = the complete aligned instruction dump). + +--- + +## 1. `_input_get_device_states` — park premise refuted; one instruction remains + +### Recorded park criterion (quoted) + +> Typed January Xbox gamepad/memory-unit hotplug and state poller reconstructed with genuine XDK 3911 API/layout +> declarations, semantic private owners, named device flags and exact natural lookup-table ownership. First natural +> pointer/count implementation retains frame, register, table-hoisting and scheduling differences: 1280 versus 1232 +> padded bytes and 32 versus 28 relocations. This is not a proven narrow compiler tie or a byte-exact claim. … +> Reopen for new authentic ABI/type/control-flow evidence, not equivalent-expression trials. + +### Census (run before any shape) + +**tinfo.** January has 28 relocations. `.bss` `_input_globals` at **+282** (`raw_gamepad_states`), **+316** +(`gamepad_handles`), **+332** (`gamepad_states`); the two lookup tables `_gamepad_analog_button_indices` (@0x259) +and `_gamepad_binary_button_masks` (@0x2c7) are referenced at `symbol+0` — January builds no displaced table base. +There is no relocation to `_update_ticks` or `_input_update_analog_button_state`: January inlines both, as we do. + +**Frame census.** January `sub esp,0x30`; the real body `sub esp,0x44` — five extra cells. January's slots: +`[ebp-8]` insertions, `[ebp-0x10]` removals, `[ebp-0x14]`/`[ebp-0x18]` the memory-unit pair, `[ebp-4]` +`device_change_flags`, `[ebp-0xc]` the hot-plug count, `[ebp-0x30]` the `XINPUT_STATE`; then in the poll loop the +four homed induction variables `[ebp-0xc]` = raw_gamepad_states, `[ebp-4]` = gamepad_states, `[ebp-8]` = +gamepad_handles, `[ebp-0x10]` = count, all four rehomed in the loop tail at 0x492-0x4b4. The real body additionally +materialises and homes two strength-reduction table bases; that is exactly the five-cell surplus. + +**Relocation census by target** (`relocdiff --count-by-target`, real body, 28 vs 32): + +| target | January | ours | +| --- | ---: | ---: | +| `_input_globals+282` | 1 | 0 | +| `_input_globals+284` | 0 | 1 | +| `_input_globals+316` | 2 | 4 | +| `_input_globals+332` | 1 | 2 | +| `_input_globals+340` | 0 | 1 | +| every other target | equal | equal | + +### Verdict on the premise: REFUTED + +The premise attributes the frame, table-hoisting and relocation gaps to "the first natural pointer/count +implementation". They are a source defect, not a property of natural source: the pointer-walk spelling builds +`&array[0].field` bases (`+284`, `+340`) and re-materialises `+316`/`+332`. The w2 body `fn/d2.c` — indexed `for` +loops over `input_globals.[gamepad_index]`, with the gamepad insert/remove work in its own block — +reproduces January's frame **and** its relocation census exactly. Re-measured this wave as `n0` +(`fn/d2.c` + `fn/k3.c` spliced into the current real file, sha256 +`e329841cb9cb7f4320ffe642b0b197ec5743962256f58027996b7c7df2d2c05c`): gate `== exact 25 residual 2 unwritten 0`, +row `residual 1232 _input_get_device_states [size 1216!=1232, sha]` — the `[relocs]` tag is gone and no exact row +is lost. + +### What remains: one instruction + +`alndiff` of `n0.obj` reports target 400 instructions versus ours 385 plus 15 trailing pad nops — 385 aligned +instructions of which exactly **one** differs, a register-role swap inside the binary-button loop's inlined +`update_ticks`: + + T 2c0 mov bl,[ebp-0x2c] 2c3 lea eax,[esi+0x18] 2c6 mov edx,masks 2cb mov esi,8 + T 2d0 test byte [edx],bl 2d2 je 2d4 movzx ecx,[eax] 2d7 inc ecx 2d8 cmp ecx,0xff (81 F9, 6 B) + T 2e9 mov [eax],cl 2eb inc eax 2ec inc edx 2ed dec esi 2ee jne + O 2c0 mov bl,[ebp-0x2c] 2c3 lea ecx,[esi+0x18] 2c6 mov edx,masks 2cb mov esi,8 + O 2d0 test byte [edx],bl 2d2 je 2d4 movzx eax,[ecx] 2d7 inc eax 2d8 cmp eax,0xff (3D, 5 B) + O 2e8 mov [ecx],al 2ea inc ecx 2eb inc edx 2ec dec esi 2ed jne + +January puts the pointer induction variable in EAX and the helper's 32-bit temp in ECX; we reverse them. The entire +1217-versus-1216 meaningful gap (and therefore the 16-byte padded gap) is the one encoding byte between +`cmp ecx,imm32` and the EAX short form `cmp eax,imm32`. Everything else matches, including the `mov bl,[ebp-0x2c]` +wButtons hoist before the loop, the ESI counter, the EDX mask pointer, all four homed IVs and the entire +`fix_dead_zone` tail. + +### Why there is no source lever (evidence, not assertion) + +- In the **analog**-button loop immediately above, our build already matches January byte for byte with the roles + January wants: IV in EAX (`lea eax,[esi+8]`, used at `[eax-8]`, `[eax]`, `[eax+8]`), helper temp in ECX. Only the + **binary** loop — whose body is nothing but the inlined helper call — swaps. +- Nine distinct spellings of that loop have now been measured across three waves and all swap identically: the + pointer/count `while` in the real file today, an indexed `for` (d1/d2), a separate `short` index (e1), the mask + test with operands swapped (e2), `buttons[button_index + FIRST_GAMEPAD_BINARY_BUTTON]` (e3), a `byte *` base with + `[button_index]` (e4, 1248), a one-use pointer local bound before the call (e5), a pointer/count `while` rewrite + (e6, 1248), and the `- FIRST_GAMEPAD_BINARY_BUTTON` index base (d6). `fn/d6.c` reaches 1232 only by acquiring + three new hunks: it loses the BL wButtons hoist (`mov al,[ebp-0x2c]` reloaded inside the loop) and gains a second + EAX/ECX flip at +0x330 — confirmed here by `alndiff` of `hd_d6.obj`. +- Both plausible origins for the IV (a genuine source-level pointer variable, and a strength-reduced index + expression) give the same swap, so the "which temp was created first in the IL" explanation has no source handle. +- **New hypothesis tested and refuted this wave.** Under the A1 static-linkage law, a `static` `update_ticks` could + inline with different IL. `scratch/pdb200-current/pdb_symbols.json` contains the January public `_update_ticks` + (and `_fix_dead_zone`), so the helper has external linkage exactly as our source declares it. Dead end; recorded + so no later wave re-spends it. + +Classification: **C2 register-allocation tie** (w3d law L2), not structural. Zero new source shapes were spent this +wave: the w2/w3d budget of 13 is already spent and brief section 5 caps post-tie work at two. + +**Not proposed.** Re-baselining the park from 1280/32 to 1216/28 is a zero-credit park remeasure that also changes +size — rejected by laws_w2 **R11** by name for this exact body (`input_xbox d2/k3`) and reaffirmed in laws_w3 B. +No coupled A8 re-baseline is required either: the real file is untouched and `parkcheck.py` reports 0 drift. + +**Reopen criterion.** A measured source lever that moves which of EAX/ECX the C2 allocator gives to a pointer +induction variable versus an inlined helper's 32-bit temp, in a loop whose body is only that helper call — not an +equivalent-expression permutation. Then re-gate `scratch/workers/n3_input_xbox/n0.c` (equivalently w3d `hd1.c`), +which is one instruction from strict exact. + +--- + +## 2. `_input_update_keyboard_devices` — relocation premise refuted; pin + the same tie remain + +### Recorded park criterion (quoted) + +> Typed January debug-keyboard hotplug, key-tick aging and SDK keystroke queue/ring handling using the genuine XDK +> keystroke type, semantic modifier/key constants and exact private ASCII/virtual-key tables. Natural source reaches +> the 640-byte target extent but retains scheduling and relocation differences (51 versus 49); no exact or general +> equivalence claim. … Reopen only for new authentic source or ABI/control-flow evidence. + +### Census + +**Frame.** No `sub esp` gap; every slot agrees. `[ebp-8]`/`[ebp-4]` are insertions/removals in the hot-plug block; +the `struct key_stroke key` then occupies `[ebp-8]` (modifier_flags), `[ebp-7]` (ascii_code), `[ebp-6]` (key_code), +and the `XINPUT_DEBUG_KEYSTROKE` sits at `[ebp-4]`. No slot is missing or extra on either side. + +**Relocations by target** (49 vs 51): `_input_globals+568` (`key_ticks`) **5 vs 6** and `_input_globals+672` +(`key_latches`) **2 vs 3**; every other target equal. January walks **one** induction variable over `key_ticks` and +reaches `key_latches` through the constant displacement `+0x68` (`mov cl,[eax+0x68]`, `movzx ecx,[eax]`). + +### Verdict on the premise: REFUTED for the relocation half + +The 51-versus-49 gap is not scheduling; it is a `long key_code` loop that keeps a second array base alive. w2's +`fn/k3.c` (a `short key_code` `for` loop indexing both arrays, with the ascii store as a ternary) reproduces +January's single induction variable and measures **640 / 49** — January's exact size and exact relocation count. +Re-measured in `n0` this wave: `residual 640 _input_update_keyboard_devices [sha]`. The park's "extent already +reached" half stands; only `[sha]` is left. + +### What remains + +`alndiff` against `n0.obj`: target 193 instructions, ours 197, in two hunks. + +1. **Byte-constant unification (w3d law L3).** Ours emits `push ebx` in the prologue plus `mov ebx,0xff`, then + `cmp eax,ebx` / `mov eax,ebx` in the tick loop and `mov [ebp-7],bl` for the ascii `NONE` arm. January + shrink-wraps `push ebx` to +0x27 and `pop ebx` to +0xd7 (EBX is needed only in the hot-plug block) and + rematerialises the constant three times: `cmp ecx,0xff`, `mov ecx,0xff`, `mov byte [ebp-7],0xff`. w3d's `kA`/`kF` + proved the cause is the *third* use of the byte value 0xFF — two inside the inlined `update_ticks` plus + `key.ascii_code = NONE`; remove it and VC7 rematerialises exactly as January does. The third use is a genuine + `NONE`, so no admissible spelling avoids it. Checked this wave: `struct key_stroke::ascii_code` is `byte` + (`source/input/input.h:188`), so `(byte)NONE` and a bare `NONE` are the same IL constant 255 — consistent with + w3d's cast-free `kB`, where the pin stayed. A `char` field would give a distinct IL constant, but + `mov byte [ebp-7],0xff` is identical for both widths, so there is no January byte evidence for a header packet. +2. **The same L2 EAX/ECX swap** in the inlined `update_ticks`. `kA` shows it survives even when the pin is removed, + so this row cannot close on the pin alone. + +Zero new source shapes were spent (w2 k1-k4 plus w3d kA/kB/kE/kF = 8; `kE` proved the `short key_code`). + +**Not proposed** (laws_w2 R11: the `k3` park remeasure was already rejected as zero credit). + +**Reopen criterion.** (a) a source lever that stops VC7 unifying a byte constant across a loop and a later byte +store — or January-side evidence that `key_stroke::ascii_code` is `char` — **and** (b) the L2 allocator lever the +`_input_get_device_states` row needs; then re-gate `scratch/workers/n3_input_xbox/n0.c`. + +--- + +## 3. Conclusion for the orchestrator + +1. `input_xbox.obj` is **25 exact / 2 residual / 0 unwritten** at this HEAD, not 24/3. Please correct the board row + for `_input_update_analog_button_state`; it is exact and unparked since `ae10935da`. +2. Both remaining park premises are structurally refuted, and both structural fixes already exist in scratch and + measure at January's frame and relocation counts (`n0.c`). Neither is landable or proposable today: R11 forbids + the zero-credit remeasure, and the surviving difference in both rows is the same C2 allocator role swap. +3. `_input_get_device_states` is the single cheapest large row known in this object — **one encoding byte** + (1232 padded) behind a lever that nine measured spellings have failed to move. If any future wave measures a + source lever for "pointer IV versus inlined-helper temp" register roles, this row and half of + `_input_update_keyboard_devices` close immediately from `n0.c`. diff --git a/docs/object_matching_logs/main_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/main_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..f2725ff4 --- /dev/null +++ b/docs/object_matching_logs/main_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,142 @@ +# `main.obj` opus5 next-150K lane, wave n3a - structural park pass (2026-09-15) + +Worker `n3a:main`. Unit `source/main/main.c`. **Real file unchanged** (`git diff --stat -- source/main/main.c` +empty). Per-function report blocks: `scratch/workers/n3_main.md`. Candidates and micro-labs: +`scratch/workers/n3_main/` (`f1.c`, `f2.c`, `f4.c`, `s1.c`, `s2.c`, `w1.c`, `lab/sf.c`, `lab/sf2.c`, `lab/wc.c`). +The previous wave's candidates (`scratch/workers/w3d_main/`) survive and were reused, not re-derived. + +## Baseline and final + +`gate.py source/main/main --all --forbid-emitted-symbol _point_from_line3d`: +**baseline == final == `exact 88 residual 6 unwritten 1`**; `_point_from_line3d` guard passed; +`scratch/parkcheck.py` reports 5 parks / drift 0; `tools/fake_match_scan.py` 0 review leads. +Nothing landed. Every target is PARKED (or owner-blocked) and none reached strict EXACT. + +Duplicate prevention: read `main_obj_opus5_250k_w3d_20260915` (the immediately preceding wave on this unit), +`main_obj_opus5_150k_w1_20260914`, `main_obj_opus5_100k_waveD_20260914`, +`main_obj_natural_state_and_render_packet_20260909`, `main_obj_lifecycle_and_fuzzy_reconstruction_20260909`, +`main_obj_jonas_window_count_rejection_20260828`, `main_obj_jonas_crash_policy_closeout_20260826`, +`main_obj_tiny_geometry_input_closeout_20260901`, plus `scratch/workers/w3d_main.md`. + +## Per-function outcomes + +| Function | Padded | Baseline (parked) | Best measured | Verdict | +| --- | ---: | --- | --- | --- | +| `_main_frame_rate_debug` | 544 | 528 / 51 | **544 / 52** (`f1.c`: size AND relocation count both == January) | source form refuted by a new law | +| `_main_get_window_count` | 64 | 64 / 5, objdiff 79.2 % | micro-lab `lab_long`: 61 meaningful / 19 insns / 5 relocs == January | park premise REFUTED; owner-blocked | +| `_compute_subframe_counts` | 112 | 112 / 4, objdiff 94.3 % | unchanged | park premise CONFIRMED (19 spellings) | +| `_main_game_render` | 448 | 464 / 29 | w3d `gr_v4.c` 464 / 29 | census re-verified; no new lever | +| `_main_update_time` | 1440 | 1440 / 118 | w3d `ut_v10.c` 1440 / **117** | re-baseline still pending from w3d | +| `_main_crash` | 16 | UNWRITTEN | - | OWNER-BLOCKED (NULL-deref UB) | + +## `_main_frame_rate_debug` - the missing relocation is closed, and the residual is now proved unreachable + +Census (re-verified): frame `sub esp,0x214` identical; from 0x70 to the end (86 instructions) w3d's `frd_v2.c` is +byte-identical to January; the only relocation row missing was a fourth `_global_screenshot_count+902` +(`framerate_sample_index`). January's decoded control flow holds **two distinct copies** of +`sample_index = global_screenshot_count.framerate_sample_index;` - one at 0x56 reached by the `!framerate_reset` +edge (falling into the second `debug_frame_rate` test) and one at 0x6a reached by the `framerate_reset && +debug_frame_rate` edge (falling straight into the sample body, the redundant test threaded away) - laid out +**after** the clear block. + +**New measured law (n3a-1, identical-arm merge).** With everything else in the function held fixed, VC7 13.00.9254 +merges two textually identical assignment arms of the same if/else tree into a single block in every spelling +except the one where both copies are `then` arms: + +| shape | source | size / relocs | copies kept | +| --- | --- | --- | ---: | +| `f1.c` | `if (!reset) S else if (debug) S else CLEAR` | **544 / 52** | 2 | +| `f2.c` | `if (reset && !debug) CLEAR else if (!reset) S else S` | 544 / 51 | 1 | +| `f4.c` | `if (!reset) S else if (!debug) CLEAR else S` | 544 / 51 | 1 | +| w3d `frd_v2/v4/v5/v6` | nested both ways, `&&`, `\|\|` | 544 / 51 | 1 | + +Block layout follows source arm order in all of them (confirms laws_w3 A44). That settles the row: January's +layout puts the clear block first, which forces `S` into the outer `else` position, and an `else`-arm `S` always +merges. Keeping two copies (f1) forces them **before** the clear block. The two requirements are mutually +exclusive in C, so no source shape in this family reaches January's bytes. `f1.c` is nonetheless the first body +whose size AND relocation count both equal January's; it is **not** proposed for landing because its duplicated +`else if` arm is not a spelling a programmer would write. + +## `_main_get_window_count` - the recorded park premise is refuted + +Recorded criterion (`config/parked.json`): *"Ordinary source produces a 79.210526 percent residual because VC7 +reorders the repeated local_player_count calls and branch tails ..."*. That is not what the bytes say. + +January returns a **32-bit** value on all three paths: `mov eax,1` (0x12), `mov eax,4` (0x2e) and +`call _local_player_count; movsx eax,ax; ret` (0x34..0x3c). Our `short main_get_window_count(void)` emits +`mov ax,1`, `mov ax,4` and a tail `jmp _local_player_count` - reproduced exactly by micro-lab `lab_short` +(`scratch/workers/n3_main/lab/wc.c`). Therefore **January's definition returns `long`** (laws_w2 A3c return-width +detector applied to constant returns). `lab_long` is 61 meaningful bytes / 19 instructions / 5 relocations, +identical to January on size and on relocation identity; the single remaining difference is that January keeps the +merged `return 1;` block at the first return's source position and branches backward into it, while our build sinks +it to the function end (laws_w3 A43 cross-jump-survivor family, a recorded negative; `lab_long_single`, the +single-exit result-variable form, is byte-identical to `lab_long`). + +The fix is blocked, and the block was measured rather than assumed: +- `long` on the definition alone does not compile - `scratch/workers/n3_main/w1.c` gives + `error C2371: 'main_get_window_count' : redefinition; different basic types`, `main_runtime.h(17)`. +- January's **callers declare it `short`**: in `build/split`, `_interface_draw_screen` calls it twice and both + times compares `cmp ax, 1`; `__rasterizer_window_end` does the same at +0x34. A `long` prototype makes those + 32-bit compares, and `_interface_draw_screen` (928), `_rasterizer_dynamic_geometry_begin` (256) and + `_rasterizer_screen_effect_set_texture_transforms` (2128) are EXACT today. + +So January's own source carried a cross-TU declaration mismatch. A header-only change trades 3,312 exact bytes for +at most 64, which brief section 7 forbids outright. Filed as type-fidelity evidence and an owner question; the +exact proposed text and the complete consumer list are in `scratch/workers/n3_main.md`. + +## `_compute_subframe_counts` - park premise confirmed and narrowed + +Census: no frame; 4 relocations identical; exactly one block differs - January materialises the loop-body +predicate (`cmp esi,edi; setl al; test al,al; je`) where ours folds it into `jge`. 19 spellings were measured +(two in-tree candidates plus two micro-lab batches, `lab/sf.c` and `lab/sf2.c`). Nothing natural materialises the +predicate: `long`/`boolean` temporaries in and out of the loop, `!= FALSE`, `== TRUE`, `| 0`, `?: TRUE/FALSE`, +a `(boolean)` cast, the negated reversed comparison, `short` counters and an inlined `static __inline` predicate +all fold to `jge` (the inline predicate additionally emits a candidate-only COMDAT, R14). The three spellings that +do materialise are all inadmissible: `(a0)` (two `setcc` rows), `(boolean)(a1) do {} while (...)`, +because VC7 rotates the loop itself and constant-folds the preheader test to `cmp ebx,1; jle`. + +## `_main_game_render` and `_main_update_time` + +Both censuses were re-run and reproduce w3d's exactly, so nothing new was spent on them. + +`_main_game_render`: frame identical, one relocation row differs (`_observer_get_camera` 1 vs 2), instructions +0..66 byte-identical; the two remaining differences are one defect - January stores the `observer` call result to +its home (`mov [ebp-0x14],eax` at 0xfc) which makes the shared tail six instructions, so VC7 does not tail-duplicate +it into the film-playback arm; ours forwards EAX (five instructions) and duplicates. HCEA +(`blam/main/main_game_render.c`, a flat else-if chain with a call in every arm - refuted by w3d's gr_v2 at 480/30) +and Stian (`main/main.c:4224`, one shared call and `camera = NULL;` at the loop top - what `gr_v4.c` already has) +were re-read this wave; neither reads the camera after the loop, so w3d's `lab_d` mechanism has no source support +and would be a synthetic lifetime anchor. + +`_main_update_time`: w3d's `ut_v10.c` remains 1440 / **117** (size and relocation identity both equal to January, +27 register-allocation blocks left) against the parked 1440 / 118. The zero-credit re-baseline proposal from +w3d is still outstanding. + +## Proposals + +1. **No park reopen** - nothing reached strict EXACT. +2. Still outstanding from w3d (not re-litigated here): the zero-credit park re-baselines of `_main_update_time` + to `scratch/workers/w3d_main/ut_v10.c` (1440/117) and `_main_frame_rate_debug` to + `scratch/workers/w3d_main/frd_v2.c` (544/51). `scratch/workers/n3_main/f1.c` is closer still on relocations + (544/52) but is withheld on credibility grounds. +3. Owner question only (does not pay for itself): the `long main_get_window_count(void)` return-type fidelity + packet described above. +4. No header or config change is requested by this wave. + +## Reopen criteria + +- `_main_frame_rate_debug`: a source form that keeps two distinct `framerate_sample_index` load blocks AND lays + them after the clear block. Law n3a-1 says that combination is unreachable from C; reopen only on a new + compiler mechanism, not another CFG spelling. +- `_main_get_window_count`: an owner ruling permitting the `long` definition with a `short` view preserved in + interface.c, rasterizer_xbox_draw_primitives.c and rasterizer_xbox_screen_effect.c; then only the merged + `return 1` block placement (A43) remains. +- `_compute_subframe_counts`: a natural spelling - no representation cast, no invented second use, no bitwise + `&` of two comparisons - that makes VC7 emit `setl al; test al,al; jcc` for a simple `<` in a bottom-tested + loop, or authoritative January source. +- `_main_game_render`, `_main_update_time`, `_main_crash`: unchanged from w3d. diff --git a/docs/object_matching_logs/motion_sensor_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/motion_sensor_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..862eb5d9 --- /dev/null +++ b/docs/object_matching_logs/motion_sensor_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,173 @@ +# `motion_sensor.obj` — opus5 next-150K lane, wave n3a (2026-09-15), STRUCTURAL PARK PASS + +Unit `source/interface/motion_sensor`. Worker scope: `source/interface/motion_sensor.c` only. +All five non-exact rows are PARKED, so every measurement here is a scratch candidate or a shadow-tree probe. +**The real file is unchanged** (`git diff --stat -- source/interface/motion_sensor.c` is empty). +Final real-file gate: `== exact 14 residual 5 unwritten 0` (identical to baseline); the `_point_from_line3d` +guard passes; `scratch/parkcheck.py` reports `parks 5 drift 0`; `tools/fake_match_scan.py` reports 0 leads. + +Worker notes and every probe: `scratch/workers/n3_motion_sensor.md`, `scratch/workers/n3_motion_sensor/`. + +## Baseline + +| Function | January padded / relocs | Parked body | Gate tag | +| --- | ---: | ---: | --- | +| `_blip_begin` | 112 / 10 | 112 / 10 | sha | +| `_motion_sensor_update` | 1168 / 43 | 1136 / 44 | size, relocs, sha | +| `_render_blip` | 400 / 18 | 384 / 17 | size, relocs, sha | +| `_render_motion_sensor` | 768 / 40 | 768 / 40 | sha | +| `_update_motion_sensor` | 864 / 39 | 816 / 37 | size, relocs, sha | + +Duplicate prevention: the three prior ledgers (`..._opus5_250k_w3c_20260915.md` — wave w3c, the same day — +`..._opus5_150k_w1_20260914.md`, `..._jonas_census_tick_one_shot_20260829.md`), +`lane_reconciliation_batch5_fuzzy_ui_world_20260909.md`, laws_w2 and laws_w3 in full, +`git log --all -- source/interface/motion_sensor.c` (last body change `800179a20`, byte-equal to the current file) +and `branch_sweep` (11 blobs; the current file is the best at `exact 14`). + +## 1. Structural census (run fresh, before any shape) + +### Frame census + +| Function | January | parked body | best scratch body | +| --- | --- | --- | --- | +| `_update_motion_sensor` | `sub esp,0x2c0` | `sub esp,0x2c4` (+4, one extra cell) | `w3c/u7.c` = `0x2c0`, slot set identical | +| `_motion_sensor_update` | `sub esp,0x284` | `sub esp,0x270` (-20, different short-array topology) | `w1/v13.c` = `0x284`, slot set identical | +| `_render_blip` | `sub esp,0xc` | `sub esp,0xc` | identical; the gap is not the frame | + +### Relocation census by target + +`_motion_sensor_update` — January owns `_blip_type_get` ×1, `_object_get_and_verify_type` ×1, `_tag_get` ×1 and +`_object_try_and_get_and_verify_type` ×2 where we call `_motion_sensor_blip_set_type_and_size` ×1 and +`_object_try_and_get_and_verify_type` ×1; we additionally own two surplus `_local_player_get_player_index`, one +`_datum_get` and one `_player_data`. **Both builds emit two `_game_engine_running` calls**, so the recorded park +criterion ("January emits the otherwise-discarded `game_engine_running` call") is refuted: the residual is the +inlined blip type/size helper plus a duplicated local-player fetch. + +`_render_blip` — January owns `__real@3f800000` ×2, `__real@3fe6666666666666` (double 0.7) and `__real@3dd6774d`; +we own one `__real@3f800000`, the widened `__real@3fe6666660000000` and `__real@3dd67750`. + +`_update_motion_sensor` — January owns two `__real@00000000` loads we lack; everything else is address shift only. + +### Owner census (candidates vs `build/base`, checked against `build/split`) + +`w3c/bb1.c` clean. `w3c/rb6.c` swaps two literals for January's own. **`w3c/u7.c` and `w1/v13.c` each add +`_distance_squared3d` and `_vector_from_points3d`** — `real_math.h:1177` defines `distance_squared3d` as +`magnitude_squared3d(vector_from_points3d(a, b, &v))`, so the pair is inseparable from any call. The pending +laws_w3 R14 `_vector_from_points3d` ruling therefore gates **2,032 padded bytes in this unit**, not the 1,168 the +w3c ledger recorded. + +### Candidate re-verification at this HEAD + +`w3c/bb1.c` → `exact 15 residual 4` (`_blip_begin` strict EXACT). `w1/v13.c` → `exact 16 residual 3` +(`_blip_begin` + `_motion_sensor_update` strict EXACT). `w3c/u7.c` → `_update_motion_sensor` **864 / 39, [sha] only**. +`w3c/rb6.c` → `_render_blip` **384 / 18** against the real headers. + +## 2. Outcomes + +### `_motion_sensor_update` (1168 / 43) — OWNER-BLOCKED, 0 shapes spent + +The structural gap is solved (`w1/v13.c` is strict EXACT and reproduces January's frame and relocation set exactly). +Both recorded blockers are policy, so no laws_w2/laws_w3 law applies and spending shapes a standing policy forbids +is itself a defect (R13): + +1. January leaves `camera_positions[i]` uninitialised for a local player whose unit is NONE, and the object loop + reads it. Verified independently this wave that the array is not covered by any existing clear: January's single + `csmemset` is `csmemset(&sentinel, 0x62, 0x200)` at `[ebp-0x284]`, the stack-corruption sentinel; the 0x84 bytes + of real locals above it are never written at entry. +2. The owner census gains `_distance_squared3d` + `_vector_from_points3d` (R14). + +### `_update_motion_sensor` (864 / 39) — NOT LANDED; structural gap closed, residual is a reassociation tie + +`w3c/u7.c` reaches **864 / 39 with 285 vs 285 instructions**. `alndiff` leaves exactly one hunk, 8 bytes at +`0x16b..0x178` in loop 1: January associates the squared length as `(j*j + i*i) + k*k`, ours as `(i*i + k*k) + j*j` +(laws_w2 A22 / laws_w3 A35(2); January = the S4 ranking, ours = S5). The leaf bytes at `0x14b..0x16a` are identical +and loop 2 already matches, so the leaves and their ages are equal — the summation lives inside +`magnitude_squared3d` (real_math.h:1123), which a worker may not edit. + +**The recorded park criterion is refuted.** January's two `__real@00000000` relocations are not "unresolved x87 +scheduling": they are the `k*k` term of `distance_squared3d(&reference_position, &object_position)` where +`reference_position.z = object_position.z`. Copy forwarding folds `z-z` to the zero literal while the multiply that +was already built survives — a source shape, reproduced exactly by u7. + +No shapes spent this wave: the difference is pure x87 term order (the orchestrator's instruction is not to grind +such ties), w1 and w3c together spent 15 shapes on it with a clear tie verdict, and an exact body would still be an +R17 conditional reopen behind the R14 ruling. + +### `_render_blip` (400 / 18) — NOT LANDED; E7 is now measured NOT to be count-neutral + +`w3c/rb6.c` is 384 / 18 against the real headers and **400 / 18** under a shadow `real_math.h` carrying the laws_w3 +**E7** `rotate_vector2d` body (re-measured this wave with `sgate2.py`). The remaining 16 bytes are entirely the +helper's body: January emits the j row into a temporary and copies it back with an integer move AFTER the i store, +which the repo body (`result->i` first, then a second read of `v->i` — the in-place aliasing bug) cannot produce. + +**New measured negative.** Six shadow-header probes (`scratch/workers/n3_motion_sensor/sh{A,B,D,E,G,H}`): + +| shadow `real_math.h` | `bitmap_drawing` | `game_engine_race` | +| --- | --- | --- | +| A — two temps, j first (E7 text) | `_bitmap_copy` LOST (2784/69 both sides, sha only) | — | +| B — one temp (`real j`), i stored inline | `_bitmap_copy` LOST | `_race_engine_player_update` LOST (160/9), `_race_touch_flag` LOST (336/28 vs 30) | +| G — one temp caching the input (`real i = v->i`) | `_bitmap_copy` LOST | — | +| H — two temps, i first | `_bitmap_copy` LOST | — | +| D — ORIGINAL body + 1 extra prototype | `7 of 7 EXACT` | — | +| E — ORIGINAL body + 2 extra prototypes | `7 of 7 EXACT` | — | + +Neither `bitmap_drawing.c` nor `game_engine_race.c` contains a single textual reference to `rotate_vector2d`, yet +every correct body flips them while pure declaration additions do not. This **refutes the declaration-count +attribution in laws_w3 A41 for these victims and confirms the body-IL attribution**, and it settles E7's open +question: **no count-neutral compensation exists**, so a duplicate-prototype cleanup cannot rescue the packet. +E7 is +400 (this row) against -3,280 unless the three victim rows are re-closed in their own units under the header. + +### `_blip_begin` (112 / 10) — PARK REOPEN PROPOSED (re-verified; duplicates the w3c proposal) + +`scratch/workers/w3c_motion_sensor/bb1.c` = the current real file plus the January four-slot ABI and a disclosure +comment. Whole-TU gate at this HEAD `== exact 15 residual 4`; no baseline-EXACT row lost; guard passes; owner census +empty in both directions; fake-match scan 0 leads. + +January evidence re-derived this wave: the body asserts `reference` from **ESI** (`test esi,esi; jne` with the +`"reference"` literal), reads `in_multiplayer` at `[ebp+0xc]` and `local_player_index` at `[ebp+0x10]`, and never +touches `[ebp+8]`; the only caller does `lea esi,[ebp-0x10]; push esi; push eax; push 0; call _blip_begin; +add esp,0xc`, so the literal zero is argument two with `reference` enregistered. + +Independent name search (new this wave): HCEA `halocea-review/src/blam/rasterizer/dx9/blip_begin.c` is the later +build's **three**-parameter `void blip_begin(const real_point2d *center, uint8_t in_multiplayer, +int16_t local_player_index)` and its "4th num_blips param" line is an explicit decompiler DEVIATION note, not a +name; a whole-tree grep of `_research-stian-halo`, `stian-current`, `reference/halocea` and +`public-donor-census-20260829` finds no `blip_begin` at all. `long 0` and `real 0.0f` emit identical bytes. +`long unused` plus the disclosure comment remains the only honest spelling (precedents +`actor_perception_find_sense_position`, `profile_rasterizer_stalls`). + +**Coupled park re-baseline required in the same commit:** `_render_motion_sensor` 768 / 40 → **784 / 40**, +sha `de9701add36aae94` (laws_w2 A8 callee-ABI cascade). + +### `_render_motion_sensor` (768 / 40) — SKIPPED-EXHAUSTED + +Not a target of this wave. w1 spent 15 shapes, w3c ran A5/A12/A23/A24/A25 as detectors and none fires; the residual +is the C2 scheduler interleaving the sensor-index integer statement with the x87 weight chain. Listed only because +the `_blip_begin` reopen re-baselines it. + +## Proposals + +1. **Park reopen — `_blip_begin` (112 B).** `scratch/workers/w3c_motion_sensor/bb1.c`, coupled with the + `_render_motion_sensor` re-baseline to 784 / 40 sha `de9701add36aae94`. Needs the owner ruling on the never-read + second parameter's name and type. (Same candidate the w3c ledger proposed; re-verified unchanged at this HEAD, + with an independent, now-exhaustive donor-tree name search.) +2. **Owner ruling — `_vector_from_points3d` surplus COMDAT (R14), scope corrected.** It gates `_motion_sensor_update` + (1,168) *and* `_update_motion_sensor` (864) in this unit — 2,032 B — on top of sun_glow's 2,352. +3. **Owner ruling — `_motion_sensor_update` uninitialised `camera_positions` read.** Exact body `w1/v13.c`. +4. **laws_w3 E7 re-evaluation.** E7 cannot be made count-neutral (measured above). If the orchestrator still wants + the 400 B, E7 must ride with a worker owning `source/bitmaps/bitmap_drawing.c` and `source/game/game_engine_race.c` + who re-closes `_bitmap_copy`, `_race_engine_player_update` and `_race_touch_flag` under the E7 header. +5. **parked.json evidence text.** The recorded criteria for `_motion_sensor_update` (the "otherwise-discarded + `game_engine_running` call") and `_update_motion_sensor` ("zero-real relocations reflect unresolved x87 + scheduling") are both refuted by this census and should be replaced by the causes recorded here. + +## Reopen criteria + +- `_blip_begin`: owner ruling on the unused parameter's name and type. +- `_motion_sensor_update`: owner rulings on the uninitialised read AND the `_vector_from_points3d` census. +- `_update_motion_sensor`: the `_vector_from_points3d` ruling PLUS a spelling in which the folded-zero `k` term is + not reassociated into the first partial sum (start from `w3c/u7.c`). +- `_render_blip`: E7 landed together with re-closures of its three measured victims, plus a lever that moves + `fmul dword [esi+0x2d0]` three integer instructions later inside its basic block. +- `_render_motion_sensor`: a spelling that emits `sub eax, dword ptr [ebp-0x18]` as a memory operand and finishes + the integer sensor-index statement before the x87 weight chain. diff --git a/docs/object_matching_logs/player_effects_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/player_effects_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..162ace58 --- /dev/null +++ b/docs/object_matching_logs/player_effects_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,138 @@ +# `player_effects.obj` — opus5 next-150K lane, wave n3a (2026-09-15), STRUCTURAL PARK PASS + +## Result + +| Measure | Baseline (HEAD) | Final | +| --- | --- | --- | +| Focused gate | 25 exact / 4 residual / 0 unwritten | 25 exact / 4 residual / 0 unwritten | +| `_point_from_line3d` guard | pass | pass | +| Parks (`scratch/parkcheck.py`) | 3 active | 3 active, drift 0 | +| `tools/fake_match_scan.py` | 0 leads | 0 leads | +| `git diff --stat -- source/effects/player_effects.c` | — | empty | + +**Nothing landed, by design: the census closed the question instead.** The real file was never edited. + +**Baseline correction for the lane board.** The wave packet lists this unit at `exact 24 / residual 5` +with `_player_effect_get_screen_flash` still `residual 800 [size 816!=800, sha]`. The measured gate at this +HEAD is `exact 25 / residual 4`: `_player_effect_get_screen_flash` is already strict EXACT in the real file +(landed by wave w3c, `player_effects_obj_opus5_250k_w3c_20260915.md`). The packet's row list is one wave +stale. + +## Target: `_player_effect_get_camera_effect_matrix` (1312 padded / 1303 meaningful / 49 relocs) — PARKED + +### The park premise, and what the census did to it + +`config/parked.json`, class `unclassified`, records: *"Target and candidate carry the same ordered 49 +relocation identities and the same 0x48-byte frame … **the candidate is 1280 padded bytes against +January's 1312**."* + +Candidate `scratch/workers/n3_player_effects/v0.c` — the **current** real file with **only** this parked +body replaced by the w1 `pe_m6` shape (m1 `set_real_point3d` scripted translation, m2/m6 direct +`temporary_duration` / `camera_shake.duration` arguments, m3 two-pass impulse translation) — gates at +**1312 / 49 [sha]**, i.e. **January's exact padded size and relocation set**, with the whole-TU `--all` +listing identical row for row to baseline (`exact 25 residual 4`, no sibling loss, guard passes). + +So **the recorded size premise is refuted**: the 32-byte gap was two source defects (the `real duration` +locals, and the one-pass `position.x = direction.i*translation + jitter.i*scale` form), both fixable in +admissible C. + +### Frame census — January vs candidate + +Both `sub esp, 0x48`. Slot-by-slot reference counts are identical for every slot +(`[ebp+8]` 18, `[ebp+0xc]` 6, `[ebp-4]` 17, `[ebp-8]` 27, `[ebp-0xc]` 1, `[ebp-0x10]` 1, `[ebp-0x14]` 2, +`[ebp-0x18]` 3, `[ebp-0x1c]` 3, `[ebp-0x48]` 6) **except `[ebp-0x20]` (`effect_matrix.position.x`): +January 1 reference, ours 3.** Ours lacks no slot and adds none — no missing local, no extra local, no +width gap. + +### Relocation census by target + +`relocdiff --count-by-target --allow-structural`: `49 target, 49 candidate, 0 differing row(s)`. Every +call and global matches by target and count. **No missing statement.** + +### Therefore + +The census **refutes the park's size premise** and at the same time **proves no structural source defect +remains**: no missing call, no absent or surplus frame slot, no wrong declared width, no wrong control +flow. What is left is 390 vs 389 instructions in two x87 clusters: + +* **A — inlined `cross_product3d(global_up3d, &effect->direction, &axis)` (4 instruction slots).** January + `+0x2cf fld [ebx+8]; fmul [eax]` and `+0x2d6 fld [ebx+8]; fmul [eax+4]`; ours loads the `global_up3d` + leaf first in exactly those two products. The other four products and the k,j,i term order are + byte-identical. A35(1) verbatim. +* **B — impulse translation (1 net instruction).** January keeps `translation * effect->direction.i` on the + x87 stack across the `y` and `z` stores (`fld st(1)` … `fxch st(1)` … `fadd st(1)` … `fstp st(0)`); ours + stores `position.x` and reloads it. `y` and `z` use a memory `fadd` in **both** builds, which is what + proves the two-pass source shape. + +### Shapes measured this wave (4, all scratch, all reverted) + +| id | shape | result | +| --- | --- | --- | +| `v0.c` | current real file + the w1 m1/m2/m3/m6 body | **1312 / 49**, clusters A + B only, siblings unchanged | +| `v1.c` | `position.x = position.x + effect->jitter.i * scale;` (×3) instead of `+=` | byte-identical to v0 | +| `v2.c` | pass 1 reordered `y, z, x` | y/z stores reach January's offsets; **x still spills and reloads** | +| `v3.c` | `scale_vector3d` + `scale_vector3d` into a temp + `add_vectors3d`, with the admitted point→vector casts | **frame 0x48 → 0x54**; January's frame has no room for a temporary vector — helper routing through a temp is structurally refuted | +| `v4.c` | v0 **minus m1** (scripted translation as three direct `z, y, x` stores) | **cluster A vanishes (the cross product becomes byte-exact)** while the scripted block degrades to eval/store ×3 (393 insns vs 390) | + +### The blocker, stated precisely + +**(a) The two blocks are mutually exclusive.** January's scripted branch evaluates all three translation +products and *then* stores x, y, z (`+0x204..+0x229`) — only the `set_real_point3d` grouping reproduces +that (A4); the direct-store spelling interleaves eval and store (v4, and the parked body). But that +grouping is exactly what flips the two `cross_product3d` leaf loads in the **other arm of the same +if/else** — a distant count-driven tie inside one function (the declaration-count oracle, not leaf age: +the two blocks never execute together). v0 vs v4 measures the exclusive-or directly at this HEAD, which is +the first time it has been reproduced since w1. The only third grouping (three named product locals) is +refuted by the frame census, exactly as v3 was. + +**(b) January never stores the x product.** `+=` (v0), `x = x + e` (v1), "x last" (v2) all reload from +memory, and a temp vector costs frame (v3). Every source form with January's property is asymmetric — one +component as a single combined expression, or a named `real` for x alone — i.e. a shape written to +reproduce bytes rather than source a programmer would write. None was tried, under house rule 8 and the +no-fake-matching gate. + +**No reopen is proposed** (a reopen needs strict EXACT; an R11 size-changing "strictly closer" re-baseline +is not proposable). The park is untouched. + +**Reopen criterion, sharpened.** The park's size, frame, slot and relocation premises are all satisfied by +`scratch/workers/n3_player_effects/v0.c` (1312 / 49, four differing instruction slots). Reopen needs +**(a)** a source form that groups three evaluations before three stores in the scripted branch *without* +the `set_real_point3d` inline, or an admissible count-neutral compensation for the flip it causes, **and +(b)** a natural source form in which `translation * effect->direction.i` is never stored to `position.x` +before the jitter add. + +## Rows not spent + +| Function | Padded | Classification | Why not spent | +| --- | ---: | --- | --- | +| `_player_effect_add_continuous_effect` | 320 | x87 evaluation-order tie, no structural delta | 5 shapes already spent (w1 ×3, w3c ×2); both leaves are the same memory operand so A35 cannot discriminate; outside the structural mandate | +| `_player_effect_start` (parked) | 736 | A35(1) offset-0 leaf-age tie, 2 instructions | identical size, frame, relocations and instruction count; no admissible untried shape (w1, w3c) | +| `_player_effect_update_camera_impulse` (parked) | 752 | x87 term order in inlined helpers | identical size, frame, relocations and instruction count; A24/A6/A31/A35/A46 run as detectors by w3c, none fires | + +## Observation for the lane (no tree change requested) + +Three of this unit's four residuals — `_player_effect_get_camera_effect_matrix` cluster A, +`_player_effect_start`, `_player_effect_update_camera_impulse` — are one defect class: the operand load +order of terms inside inlined `real_math.h` `cross_product3d` / `dot_product3d` bodies, with **no** +structural delta anywhere (identical frames, identical relocation sets, and identical instruction counts +in two of the three). That is a systemic inline-IL question (A35 / A41), not three independent per-site +source defects; per-site spellings are now spent across four waves (w1, the 0904 closeout, w3c, n3a). + +## Proposals + +None. No header, config, symbols.json or parked.json change is requested. No park reopen is proposed. + +## Verification performed + +- `python -B tools/campaign/gate.py source/effects/player_effects --all --forbid-emitted-symbol + _point_from_line3d` → `exact 25 residual 4 unwritten 0`, before and after (real file never edited). +- `--source scratch/workers/n3_player_effects/v0.c --all` → identical row for row to baseline; the target + row moves `[size 1280!=1312, sha]` → `[sha]` at 1312 / 49. +- `python -B -m tools.campaign.alndiff` and `relocdiff --count-by-target --allow-structural` for v0…v4; + per-slot `[ebp±N]` censuses via `scratch/workers/n3_player_effects/slots.py`. +- `python -B scratch/parkcheck.py source/effects/player_effects ` → `parks 3 drift 0`. +- `python -B tools/fake_match_scan.py source/effects/player_effects.c` → 0 review leads. +- No ninja, configure, pytest or git state command was run; no header, config or other source file was + touched. + +Details, byte evidence and the full census tables are in `scratch/workers/n3_player_effects.md`. diff --git a/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..b717146d --- /dev/null +++ b/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,218 @@ +# `rasterizer_xbox_environment.obj`: next-150K house-clean lane, wave n3a (2026-09-15) + +## Scope and provenance + +- Translation unit `source/rasterizer/xbox/rasterizer_xbox_environment.c`; target + `build/split/source/rasterizer/xbox/rasterizer_xbox_environment.obj` (the January split COFF is byte authority). +- Lane `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/next-150k-house-clean-20260915`, + frozen baseline `ae10935da`. Structural park pass, wave n3. +- Worker-only measurements: `tools/campaign/gate.py`, `tools.campaign.tinfo`, `tools.campaign.relocdiff`, + `tools.campaign.alndiff`, `scratch/parkcheck.py`, `tools/fake_match_scan.py`, plus a micro-TU lab compiled with this + unit's own `build.ninja` cflags and CL (`scratch/workers/n3_rasterizer_xbox_environment/lab.py`). No ninja, + configure, pytest, header, config or tool edits; no other source file touched. +- Prior ledgers read in full: `_codex_small_exact_20260902`, `_diffuse_texture_reconciliation_20260909`, + `_lightmap_draw_fuzzy_20260912`, `_opus_reflections_reconciliation_20260909`, `_opus5_100k_20260914`, + `_opus5_100k_waveC_20260914`, `_opus5_150k_w1_20260914`, `_opus5_250k_w3c_20260915`; worker notes + `scratch/workers/rasterizer_xbox_environment.md` and `scratch/workers/w3c_rasterizer_xbox_environment.md`. +- Laws applied as detectors: WORKER_BRIEF section 5/9, `scratch/w2/laws_w2.md` (A1-A22, R1-R12, D), + `scratch/w3/laws_w3.md` (A23-A45, R13-R19, C, D). +- Worker notes for this wave: `scratch/workers/n3_rasterizer_xbox_environment.md`; scratch dir + `scratch/workers/n3_rasterizer_xbox_environment/`. + +## Baseline and final gate (identical; nothing landed) + +`python -B tools/campaign/gate.py source/rasterizer/xbox/rasterizer_xbox_environment --all --forbid-emitted-symbol _point_from_line3d` + +**exact 42, residual 2, unwritten 0 of 44** at baseline and at the end of the wave. Point guard pass; +`scratch/parkcheck.py` 1 park, drift 0; `tools/fake_match_scan.py` 0 leads; owner census vs +`build/base/.../rasterizer_xbox_environment.obj` 91 = 91, no candidate-only and no lost owner; +`git diff --stat -- source/rasterizer/xbox/rasterizer_xbox_environment.c` empty. + +| Row | Target size/relocs/sha16 | Real file | Status | +| --- | --- | --- | --- | +| `__rasterizer_environment_lightmap_draw` | 4016 / 248 / 7818e559325c1524 | 3984 / 246 / 02f716db92e8099f | residual [size, relocs, sha] | +| `_rasterizer_environment_specular_spot_light_begin` | 960 / 64 / e0331f6b94a74f9c | 944 / 64 / d103843ee5f01bad | residual, PARKED | + +`__rasterizer_environment_diffuse_light_begin` (w1 park reopen) and `_IDirect3DDevice8_SetTexture@12` are inherited +exact and were not revisited. + +## `__rasterizer_environment_lightmap_draw` - NOT LANDED (1 shape; the search space is now closed on the C side) + +This row is **not** in `config/parked.json`; it is an ordinary residual. The applicable recorded criterion is the +w3c one: "January or donor evidence of a real intensity/brightness term or a double/cast intermediate - OR an +owner/policy ruling on the laws_w3 C3 x87-staged-argument family that also covers stack-local leaves." + +### Relocation census by target + +Every relocation target agrees except two, and both deficits are in the same struct: + +| Target | January | real file | +| --- | --- | --- | +| `_pixel_shader+32` = `final_combiner_inputs_abcd` | 9 | 8 | +| `_pixel_shader+36` = `final_combiner_inputs_efg` | 2 | 1 | + +**Cause proven.** Our two illumination arms (`illumination->map.index == NONE` and the animated arm) end with the +identical two-statement tail `final_combiner_inputs_abcd = 0x0A0F000C; final_combiner_inputs_efg = 0x1C011800;`, and +VC7 cross-jumps those tails into a single copy. January does not merge them: in January the animated arm's constant +block *ends* at `+32`/`+36` (0xd1d, 0xd27) and is followed by the five `real_*_to_pixel32` calls, so the two arms +share no tail. That is precisely wave B's recorded correction "all constant `pixel_shader` stores before the five +`real_*_to_pixel32` calls". A missing relocation here is therefore a store-ORDER defect, not a missing statement. + +### Frame census + +January `sub esp,0x218`; ours `sub esp,0x218`. A complete `[ebp±N]` census of both bodies gives **132 distinct slots +on each side with identical per-slot reference counts**. The per-slot instruction lists are identical too: for +`[ebp-4]` both sides have the same 11 references and for `[ebp-0xc]` the same 3, differing in exactly one instruction +each. **No local is missing, extra, differently sized or differently scoped.** The census refutes every +missing-statement / extra-slot / wrong-width / wrong-control-flow hypothesis for this row. + +### Shape n1 (wave B reproduced on the current baseline) + +`scratch/workers/n3_rasterizer_xbox_environment/n1.c` = the real file plus only wave B's four structural corrections +(stage-0 MAG/MIN/MIP filters written inside each test-pattern arm; genuine XDK `PS_TEXTUREMODES(...)` for both +`texture_modes` stores; all constant `pixel_shader` stores before the five `real_*_to_pixel32` calls; the +`specular_mask` case placed after the `bump_edge` case). Gate: **4000 / 248 [size, sha]**, whole TU 42 exact / +2 residual / 0 unwritten, point guard pass. Relocation count and every relocation target now equal January. +Not landed: it is a non-exact body replacing an already-written residual (brief sections 6 and 8), and the w1 +verifier required exactly these hunks reverted (R13). + +### The entire remaining gap + +947 vs 939 instructions / 16 padded bytes, in two instruction pairs: + +``` +January 0xb88 fld dword [ebp-4] ; add esp,0xc ; ... ; push ecx ; fstp dword [esp] +ours 0xb88 mov ecx,[ebp-4] ; ... ; push ecx +January 0xba6 fld dword [ebp-0xc] ; add esp,0x10 ; ... ; push ecx ; fstp dword [esp] +ours 0xba0 mov edx,[ebp-0xc] ; ... ; push edx +``` + +January stages the `t` argument of both `point_from_line3d` calls through the x87, which forces it to flush the two +pending cdecl cleanups so `[esp]` is the argument slot; we integer-copy and merge five cleanups into one +`add esp,0x44`. Everything after that is offset and allocation cascade. + +### NEW MEASURED LAW: VC7 float-argument staging (15 controls) + +A micro-TU that reproduces the block in shape (`rep1.c`, `rep2.c`, `rep3.c` with +`scratch/workers/n3_rasterizer_xbox_environment/lab.py`, compiled with this unit's own cflags and CL) was used to +enumerate the rule. The baseline probe emits our exact integer-copy shape; a `double`-parameter control emits +`fstp qword [esp]`, so the detector is sound. + +Integer-copied (no FPU staging) for every one of: a plain `real` local defined by a call return; named `1.0f - v` +off-scale locals; a field of a local struct; an element of a local `real[2]`; an address-taken local; a `real` global; +a `real` parameter; a parenthesised `(v)`; declare+initialize; a value with an extra later fp use; a helper whose +pointer return is discarded; a value with no other fp use at all; a local *defined* by an fp expression and passed +unchanged; a tag-block field read through the pointer; an element of a `real const *` parameter. + +FPU-staged only for: an fp-VALUED argument expression, and a directly nested call result. + +> **Law.** VC7 integer-copies every plain float lvalue argument - local, parameter, global, struct field, array +> element or address-taken local, however it was defined. Only an fp-valued expression (or a call result consumed +> directly in the argument list) is staged `push reg; fstp dword [esp]`. + +This also corrects the w3c framing that the 68 other January `fld [reg+off] -> fstp [esp]` sites are "pointer field" +staging: a pointer field on its own does not do it. + +### Consequence and classification + +January's `t` argument must be an fp expression whose emitted code is a single `fld dword [ebp-N]` - i.e. an +expression that c2 folds to the bare local value. Every such expression is an identity (`v*1.0f`, `v+0.0f`, +`(real)v`, `+v`): the w1 `intensity = 1.0f` family, rejected by the w1 verifier as fake arithmetic, bound by R13 and +by this wave's orchestrator note, and separately R7 for the cast forms. The one non-identity form that does stage +through the FPU - a directly nested `periodic_function_evaluate` call - is refuted by January's call order +(scale, scale, point, point; nesting would emit scale, point, scale, point). + +**Classification:** unresolved source form - x87 argument staging - with the C-side search space now closed. Not a +register tie; not a missing statement. + +**Reopen criterion (refined).** Reopen only on (a) an owner/policy ruling admitting a named self-illumination +intensity of 1.0 - the January-side argument being the two `push 0x3f800000` constants at the plasma +`scale_vector3d` calls, the only constant-1.0 pushes among January's twelve out-of-line `_scale_vector3d` calls - or +(b) donor/January evidence of a genuine non-identity factor or double intermediate. Do **not** spend further shapes +on plain-lvalue spellings of the argument: fifteen controls in this wave say they cannot work. Start from +`scratch/workers/n3_rasterizer_xbox_environment/n1.c` (4000/248, relocation- and frame-exact). + +## `_rasterizer_environment_specular_spot_light_begin` - NOT LANDED (park locked; 1 shape) - PARK CLASS REFUTED + +Park's recorded criterion (`config/parked.json`, class `register-allocation`): *"... January is 960 padded bytes and +the candidate 944, with all 64 relocations shared ... The remaining difference is compiler vector/local allocation +and scheduling, not absent renderer behavior; bounded helper and declaration-shape probes did not close it. ... +Reopen only for authoritative January source/local records or a natural same-compiler donor that explains the vector +lifetime schedule."* + +### Frame census - the park premise does not survive it + +January `sub esp,0x84`; the parked body `sub esp,0x74`. January's slots are vertex constants `[-0x84,-0x34)`, an +unreferenced 4 bytes at `-0x34`, forward `-0x30`, cross result `-0x24`, up `-0x18`, and an unreferenced 12 bytes at +`-0xc`: exactly `real_matrix4x3 {scale; forward; left; up; position}` with `scale` and `position` never written. The +parked body declares three separate `real_vector3d` locals, so its slots pack with no gaps and it is one aggregate +short. **The 16-byte gap is a wrong declared local TYPE - a structural source defect - not "compiler vector/local +allocation and scheduling".** The recorded class `register-allocation` is refuted. + +### Relocation census + +64 / 64 identical by target for both the parked body and the corrected candidate. No call or global is missing or +extra on either side. + +### Shape s1 + +`scratch/workers/n3_rasterizer_xbox_environment/s1.c` = the current real file with only this body replaced by w1's +`s3a` shape (`real_matrix4x3 matrix; matrix.forward = light->forward; matrix.up = light->up; +normalize3d(cross_product3d(&light->forward, &light->up, &matrix.left));`, vertex constants read from `matrix.*`). +Gate: **960 / 64 [sha]**, frame `sub esp,0x84`, whole TU 42 exact / 2 residual, point guard pass. Size, relocations +and frame are January-exact; 247 vs 248 instructions. It supersedes `s3a.c`, which also carries the rejected +`intensity` tree and the pre-reopen diffuse body. + +### What is left + +Three commutative x87 operand-order pairs inside the shared `real_math.h cross_product3d` inline, plus their +downstream allocation (`eax = &light->forward = esi+0x10`, `ecx = &light->up = esi+0x1c`): + +``` +j term January fld [ecx] ; fmul [eax+8] ours fld [eax+8] ; fmul [ecx] + January fld [ecx+8] ; fmul [eax] ours fld [eax] ; fmul [ecx+8] +k term January fld [ecx+4] ; fmul [eax] ours fld [eax] ; fmul [ecx+4] +``` + +plus one eax/ecx/edx rotation in the `light->position` copy, `lea ecx` vs `lea edx` at 0x1b5, and one extra +`fxch st(1)` at 0x1ea. The i term already matches byte for byte on both sides, and January itself is not uniform +(5 of 6 products load `up` first, ours 2 of 6), which is the fingerprint of leaf age rather than of a source operand +order. Three independent pins block a source lever: + +- **argument order** is pinned - the matching i term computes `up.k*forward.j - forward.k*up.j` = + `cross(forward, up).i`, so swapping the arguments (and dropping the `vertex_constants[2]` negations, which is + mathematically equivalent) would break a currently-exact hunk; +- **operand addresses** are pinned by w3c's `s3b` proof (matrix members make VC7 read the local slots, while January + reads `light`-relative addresses); +- the **integer struct-copy schedule** is byte-identical on both sides, so the two member copies are pinned as well. + +Buying the last `fxch` with a commutative swap of `cone_scale * (radius * 0.5f)` is R3. + +**Classification:** the structural half is closed in scratch; the residual is an x87 commutative leaf-age tie inside +a shared header inline, with no TU-local handle. No park-reopen is proposed - the brief requires strict EXACT and +`s1.c` is `[sha]`. + +**Reopen criterion (unchanged from w3c):** (a) a measured VC7 law explaining the `fld` order for pointer-read +commutative operands, or (b) an owner-approved `real_math.h cross_product3d` change measured across its consumers, +noting that the current body is REQUIRED by the exact sibling `__rasterizer_environment_diffuse_light_begin`. + +## Rejected / do-not-repeat (added by this wave) + +- Any plain-lvalue spelling of the `point_from_line3d` `t` argument in `lightmap_draw` (15 measured controls). +- Nesting the `periodic_function_evaluate` call inside the `point_from_line3d` argument list - the only non-identity + FPU-staging form - refuted by January's call order. +- Re-landing wave B's four structural corrections alone (4000/248): brief sections 6/8 plus the w1 verifier's + full-revert requirement (R13). +- Swapping `cross_product3d`'s arguments in the spot light: breaks the currently-matching i term. + +## Proposals + +- **parked.json evidence/class correction (no byte effect, orchestrator-owned).** For + `_rasterizer_environment_specular_spot_light_begin`, change `"class"` from `register-allocation` to + `x87-operand-order` and amend the evidence text: the 16-byte gap is a `real_matrix4x3` local whose `scale` and + `position` are never written (frame census: January `sub esp,0x84` vs the parked body's `0x74`, January's + unreferenced 4 bytes at `-0x34` and 12 bytes at `-0xc`), closed in scratch at 960/64 with frame parity by + `scratch/workers/n3_rasterizer_xbox_environment/s1.c`; the remaining difference is the commutative `fld`/`fmul` + leaf order inside the shared `real_math.h cross_product3d` inline. No source, header, symbols.json or config change + arises from this wave, and the park stays locked. +- No header or config need. The real file is unchanged. diff --git a/docs/object_matching_logs/s3tc_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/s3tc_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..7b9ebdb1 --- /dev/null +++ b/docs/object_matching_logs/s3tc_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,205 @@ +# `s3tc.obj` structural park pass (opus5 next-150K lane, wave n3a, 2026-09-15) + +Unit `source/bitmaps/s3tc/s3tc.c`, baseline `ae10935da`. **All six non-exact rows are parked** +(`config/parked.json`), so all work was scratch-only and **the real source file is unchanged** +(`git diff --stat` empty). No exact credit is claimed by this wave. + +Prior passes read in full: `s3tc_obj_opus5_250k_w3c_20260915.md`, `s3tc_obj_opus5_150k_w1_20260914.md`, +`player_profile_s3tc_batch_reconciliation_20260907.md` (the batch that installed the parks) and the four +`s3tc_obj_jonas_*` ledgers. `git log --all` 9 commits; `branch_sweep` best history blob `dc1eb5bd` +(same 12/6/0, sizedelta 464) — re-read this wave and used as evidence, see section 1. + +## Baseline and final gate + +| gate | result | +| --- | --- | +| baseline (`--all`, `_point_from_line3d` guard) | exact 12 / residual 6 / unwritten 0; guard passed | +| final real file | identical: exact 12 / residual 6 / unwritten 0; guard passed | +| park drift (`scratch/parkcheck.py`, real object) | 6 parks, drift 0 | +| fake-match scan (real file and the best candidate) | 0 review leads each | +| real file modified | **no** | + +## Per-function outcomes + +| function | target | best scratch this wave | outcome | +| --- | --- | --- | --- | +| `_DecodeBlockRGB__single_pixel` | 496 / 10 | **496 / 10 strict EXACT** (`d2.c`); `d1.c` = 1 instruction away | PARK-REOPEN-PROPOSED | +| `_DecodeBlockRGB` | 448 / 2 | 448 / 2 **EXACT**, no body change | PARK-REOPEN-PROPOSED (coupled) | +| `_EncodeBlockRGBColorKey` | 2192 / 36 | 2192 / 36 `[sha]` (w3c `e7_probe.c`, re-verified) | OWNER-BLOCKED (structure solved) | +| `_DecodeBlockAlpha3__single_pixel` | 432 / 1 | 432 padded / 427 meaningful / 1 (w3c `a1.c`) | NOT-LANDED (A43 register web; park premise survives) | +| `_RGBToColor` (coupled, not a target) | 96 / 0 | 96 / 0 **EXACT** | PARK-REOPEN-PROPOSED (policy ruling, unadjudicated since w1) | +| `_Quantize` (coupled, not a target) | 528 / 30 | 528 / 30 `[sha]`, re-bases to `57685b731729ce83` | park remeasure needed in the reopen commit | + +## 1. `_DecodeBlockRGB__single_pixel` — one instruction from exact; January's assert TEXT recovered + +### Census + +Frame: January `sub esp,0x10`, ours `sub esp,0x10` — **identical**. The 16 bytes are +`struct s3tc_color colors[4]` on both sides, so January's local palette is 4 entries too; the 64-byte clear is +not an oversized local but a write through the OUTPUT parameter +(`mov eax,[ebp+0xc]; push 0x40; push edi; push eax; call _csmemset`, `edi` being the NULL `source` reused as the +fill byte). + +Relocations, by target, against `scratch/workers/s3tc/reopen_rgbtocolor.c`: `_csmemset` 1/1, `_RGBToColor` 1/1, +`_display_assert` 2/2, `_system_exit` 2/2, file-name literal 2/2 — equal. Two rows differ, both assert-text +literals: + +| | mangled name | length | text | +| --- | --- | --- | --- | +| January | `??_C@_0N@CLDKFKKD@u?$DO?$DN0?5?$CG?$CG?5u?$DM?$DN4?$AA@` | `_0N` = 13 | `u>=0 && u<=4` | +| ours | `??_C@_0BA@MDFLADHG@u?5?$DO?$DN?50?5?$CG?$CG?5u?5?$DM?54?$AA@` | `_0BA` = 16 | `u >= 0 && u < 4` | + +(and the same pair for `v`). **Census verdict: the park premise survives only for the memset. Its assert clause +is refuted.** + +### New evidence + +1. **Stringization spacing law.** MSVC `#expr` deletes whitespace around the macro argument and collapses each + internal run to a single space, but inserts none where the source had none. A `??_C@` assert literal therefore + names the author's spelling character-for-character — here: no spaces around `>=` / `<=`, one space around + `&&`. This is byte evidence about source text, and it is invisible to `gate.py`'s tags. +2. **Independent second proof of the operator:** `alndiff` shows `T 180 jle` vs `O 180 jl` and `T 1ae jle` vs + `O 1ae jl`. Signed `jle` is `u <= 4`. Literal and opcode agree. +3. **The clear is a copy-paste, not a magic number.** `0x40` = `S3TC_BLOCK_PIXELS * sizeof(struct s3tc_color)`, + the verbatim expression of the sibling `DecodeBlockRGB` **in the same TU, four functions up** + (`source/bitmaps/s3tc/s3tc.c:586`), where the output parameter really is + `struct s3tc_color colors[S3TC_BLOCK_PIXELS]`. R19's "same-TU precedent" requirement for an original-bug + admission is met. +4. **Historical corroboration.** `branch_sweep` blob `dc1eb5bd` (the pre-2026-09-07 body) already carried + `memset(color, 0, S3TC_BLOCK_PIXELS * sizeof(struct s3tc_color));` (line 652) and `u >= 0 && u <= 4` + (lines 689-690) — the right operator but with spaces. No body in this repository's history has ever + reproduced January's literal; this wave is the first. +5. **HCEX.pdb (SHIP, `DIA2Dump -sym`)**: `pblockSrc` (const struct S3TCBlockRGB \*), `colorDst` + (struct S3TC_COLOR \*), `short u`, `short v`, local `struct S3TC_COLOR[0x4] clut`. Corroborates the 4-entry + palette and short coordinates. A pointer parameter cannot distinguish `T *p` from `T p[16]`, so HCEX neither + confirms nor refutes the clear size. + +### Measurements + +| candidate | whole-TU gate | this row | +| --- | --- | --- | +| `scratch/workers/s3tc/reopen_rgbtocolor.c` | exact 14 / residual 4 | 496 / 10 `[sha]`, 5 differing instructions | +| `scratch/workers/n3_s3tc/d1.c` (+ January assert text) | exact 14 / residual 4 | 496 / 10, **0 differing relocation rows, 1 differing instruction** (`push 0x40` vs `push 4`) | +| `scratch/workers/n3_s3tc/d2.c` (+ the 64-byte clear) | **exact 15 / residual 3** | **strict EXACT 496 / 10** | + +Zero sibling loss in both: all 12 baseline-exact rows remain exact. Guard passes. `fake_match_scan d2.c` = 0 +leads (same as HEAD). Owner census: no new owner — the two new `.rdata` literals replace the two wrong ones. +`parkcheck` on `d2.obj`: 4 parks drift (3 become exact = the reopen; `_Quantize` re-bases to 528/30 sha +`57685b731729ce83`). + +### Reopen criterion (two independent halves) + +- **(a) assert text** — restore `match_assert("c:\\halo\\SOURCE\\bitmaps\\s3tc\\s3tc.c", 773, u>=0 && u<=4);` + and the `774, v>=0 && v<=4` line. This is **not** UB: an assert reads its operands and writes nothing, a weaker + predicate cannot by itself create an out-of-range shift, and both January callers pass 0..3. `docs/assertions.md` + requires not losing "original diagnostic text ... failure polarity, or source locations", so the 2026-09-07 + repair is itself the deviation. Closes 4 of the 5 differences with no policy question. +- **(b) the clear** — `memset(color, 0, S3TC_BLOCK_PIXELS * sizeof(struct s3tc_color));` writes 60 bytes past a + 4-byte object. A genuine overrun: needs the original-bug ruling plus a + `/* BUG (preserved for exact matching): */` comment at the statement. Closes the 5th. + +Together with the `_RGBToColor` ruling this object gains **1,040 padded bytes** (96 + 448 + 496). + +## 2. `_DecodeBlockRGB` — inline boundary, not scheduling (re-verified) + +The park text ("remaining instruction selection and scheduling are non-exact") is refuted: the `[size]` + +`[relocs]` gap is an inline boundary owned by `_RGBToColor`. With the Microsoft/S3 reference body January's +auto-inliner inlines the first call and calls the second out of line — 2 relocations, not 3 — and this function +becomes strict EXACT **with no edit to its own body**. Re-verified at this HEAD (`exact 14 residual 4`). +Blocked only on the coupled `_RGBToColor` policy ruling (the never-assigned `color.rgba[3]` inside an aggregate +copy; `rgb` and `color` share the single `push ecx` slot, so any initialiser destroys the merge). + +## 3. `_EncodeBlockRGBColorKey` — structure solved, exactness owner-blocked (0 shapes spent) + +Independent census at this HEAD: January `sub esp,0x1a8` (424 B) vs real file `sub esp,0x19c` (412 B), 739 vs +914 instructions, 36 vs 36 relocations. Re-verified w3c's two candidates: `e6.c` (do-while + aggregate axis, no +original forms) = **2224 / 37** — one relocation over, because a fourth `_AllSame` site fails to cross-jump; +`e7_probe.c` = **2192 / 36, `[sha]` only** (size and relocation count both exact). + +The park's "provenance for the projection-loop index width" clause is superseded by w3c's do-while unroller law +plus the `struct s3tc_fcolor` axis home. What remains is three target-proven ORIGINAL forms: +(a) uninitialised `long axis`; (b) `if (len2 == 0.0f && cOpaque == 16)` — the guard that merges the two `AllSame` +tails, whose preserved path computes `0/0`, `__ftol2(NaN)` = `0x80000000` and indexes `mapRGB3[0x80000000]`; +(c) no `block->bitmap = 0;` before the selector loop. (a) and (b) are UB; (c) is behaviour-neutral but explicitly +required by the park text. + +No shape was spent: (b) is the only route to the 36th relocation and is inseparable from the NaN path, because a +non-UB spelling would have to call `AllSame` with identical arguments in both cases, which changes behaviour. + +## 4. `_DecodeBlockAlpha3__single_pixel` — the 11 bytes are integer promotions; park premise survives + +Frame: `sub esp,0x10` on both sides; `[ebp-0x10 .. -0x2]` is the same `word alpha[8]` on both sides — no missing +or extra slot. Relocations 1 vs 1 (`_DecodeBlockRGB__single_pixel` at @001d). The first **123 instructions are +byte-identical at identical offsets**. + +The whole `[size]` tag is four integer promotions, accounted to the byte: ours emits `movsx ecx,di` (3) + +`movsx edx,word [ebp+0x10]` (4) in arm A and `movsx edx,di` (3) + `movsx ecx,word [ebp+0x10]` (4) in arm B = 14 B; +January hoists ONE `mov ecx,dword [ebp+0x10]` (3 B) above the branch and uses EDI directly. +**14 - 3 = 11 = 443 - 432 exactly.** January may read the raw parameter dwords only because `3*(4*v+u)` is needed +modulo 2^16, i.e. the shift/index variable is 16-bit; `short u` / `short v` are byte-proven by the shared signed +`cmp di,2`, so the parameters are not the lever. + +**New measurement (corrects w1):** w3c's `a1.c` (`short shift`) is **427 meaningful** bytes, not 432 — the gate's +"432" is 16-byte padding. Removing the 11 promotion bytes exposes an opposite 5-byte defect: our arms then end +with the identical `shl eax,8; or eax,edx` pair, which VC7 cross-jumps to the join, while January keeps a copy in +each arm because its index `lea eax,[ecx+edi*4]` must come last (it reuses the temp register EAX freed by +`or edx,eax`). So the narrow width does not close the size envelope — it overshoots it. + +The residual is one register web: January's bitmap accumulator is EDX and the `b[0]` temp EAX; ours are swapped, +which also moves the index into ECX mid-arm and delays the `color` load past the table read. Eight prior shapes +(w1 x5, w3c x3) failed to move it; this is laws_w3 **A43** (cross-jump survivor), a recorded negative class. +0 shapes spent this wave. + +Evidence search: HCEX.pdb has the function (RVA 0xD922A8, len 0x1D0) with `pblockSrc` +(const struct S3TCBlockAlpha3 \*), `colorDst` (struct S3TC_COLOR \*), `short u`, `short v` — corroborating the +park's retained signature — but no locals (enregistered scalars are absent from a SHIP PPC build), so no `shift` +width. The Microsoft Windows Server 2003 reference (`research-cache/windows-server-2003-dxtn`, `5c6fe3db`) +contains only `ref8/common/dxtn.cpp` and `d3d/ref/rast/refs3tc.cpp`, both block-only — no single-pixel helper and +no encoder — so the "authentic Microsoft source" half of the reopen criterion is now measured unreachable from +this corpus. + +## Rejected / not proposed + +- Any `_RGBToColor` form that initialises `color.rgba[3]` (w1 proved it breaks the slot merge). +- `axis` uninitialised, `len2 == 0 && cOpaque == 16`, dropping `block->bitmap = 0` — all re-confirmed, all + policy-gated; no shape spent. +- `short shift` as a standalone park remeasure for `_DecodeBlockAlpha3__single_pixel` (R11 plus the park's + artificial-width clause; and it is now measured strictly *shorter* than the target, not equal). +- Landing anything in the real file: all six rows are parked. + +## Orchestrator proposals + +1. **Park reopen, `_DecodeBlockRGB__single_pixel` (496 B), candidate `scratch/workers/n3_s3tc/d2.c`**, with the + intermediate `scratch/workers/n3_s3tc/d1.c` isolating the admissible half. Two rulings: (a) restore January's + assert text (recommended independently of (b): it is required by `docs/assertions.md`, is not UB, and closes + 4 of 5 differences); (b) admit the copy-pasted 64-byte clear under the original-bug policy with a + `/* BUG (preserved for exact matching): */` comment and the same-TU precedent at s3tc.c:586. +2. **Park reopen (re-surfaced, still unadjudicated since w1): `_RGBToColor` + `_DecodeBlockRGB` (544 B)**, + candidate `scratch/workers/s3tc/reopen_rgbtocolor.c`, re-verified at this HEAD. `d2.c` contains it, so a + single commit can land all three rows (1,040 B) and must also remeasure the `_Quantize` park to + 528 / 30 / `57685b731729ce83`. +3. **`config/parked.json` evidence updates (text only, zero bytes).** + - `_DecodeBlockRGB__single_pixel`: replace "Require coordinates 0..3, not the original inclusive 4" with + January's proven text `u>=0 && u<=4` / `v>=0 && v<=4` (mangled-literal and `jle` proofs); keep only the + clear as the open question. + - `_EncodeBlockRGBColorKey`: drop the projection-loop index-width clause (superseded) and narrow the criterion + to rulings on (a), (b), (c). + - `_DecodeBlockAlpha3__single_pixel`: record that the Microsoft 2003 corpus is measured to contain no + single-pixel helper, and that the width alone yields 427 meaningful bytes (overshoot), so the reopen needs + the EDX-accumulator distinction as well. +4. **Lane-wide sweep (new, validated on this unit): assert / diagnostic literal spelling.** + A wrong assert string is invisible to `gate.py` tags and appears only in `relocdiff --count-by-target` or + `alndiff` relocation annotations. `scratch/workers/n3_s3tc/assertlit2.py` (read-only, ~20 s over all objects) + compares the `??_C@` literal symbol sets of `build/split` and `build/base` and reports **39 objects that emit + a literal January does not have**; output in `scratch/workers/n3_s3tc/assert_literal_sweep.txt`. s3tc.obj + appears with exactly the 2 literals this wave repaired, which validates the detector. Same-class spot checks: + `bitmap_group` ("space_between>=0" vs "space_between >= 0"), `rasterizer_xbox_screen_effect` + ("combiner_count<=RASTERIZER_MAXIM..." vs the spaced form, plus 4 `IDirect3DDevice8_SetVertexData2f` + diagnostics of different length), `network_connection` (PROTECTED; the same defect in the opposite direction), + and a shared `ai/encounters.h` assert set in `encounters`/`ai`/`ai_debug`/`actors`. The `real_math.h` trio + (`projection>=_x && projection<=_z`, `~(sign&~1)`) in 7 objects is the systemic select-any header-inline class + (A30), not a defect. +5. **Laws addition (n3a).** MSVC `#expr` stringization preserves the source's internal token spacing, so a + January `??_C@` assert literal names the original expression character-for-character (operator choice AND + whitespace). Combined with the jcc opcode it is a two-proof recovery of assert source text, and the + `_0` field gives the exact character count before any candidate is built. diff --git a/docs/object_matching_logs/sound_manager_obj_opus5_next150_n3a_20260915.md b/docs/object_matching_logs/sound_manager_obj_opus5_next150_n3a_20260915.md new file mode 100644 index 00000000..84a99421 --- /dev/null +++ b/docs/object_matching_logs/sound_manager_obj_opus5_next150_n3a_20260915.md @@ -0,0 +1,187 @@ +# `sound_manager.obj`: opus5 next-150K lane, wave n3a (structural park pass, 2026-09-15) + +## 1. Scope and provenance + +- Translation unit `source/sound/sound_manager.c`; target `build/split/source/sound/sound_manager.obj`. +- Lane worktree `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch + `opus/next-150k-house-clean-20260915`, frozen baseline `ae10935da`. XDK VC7 13.00.9254 with the repository + flags; no flag changed or passed. +- **Nothing was written to `sound_manager.c`, to any header, or to `config/*.json`.** No ninja, configure, pytest + or git write command was run. `git diff --stat -- source/sound/sound_manager.c` is empty. +- Worker notes `scratch/workers/n3_sound_manager.md`; candidates, objects and listings in + `scratch/workers/n3_sound_manager/`. +- Evidence: January split-object bytes, relocations and section order (authoritative); the in-TU EXACT donor + `_refresh_sounds`; HCEA `reference/halocea/src/sound_refresh_looping.c` (later build, semantics only); + `config/parked.json`; all eleven earlier `sound_manager_obj_*` ledgers; `scratch/w2/laws_w2.md` and + `scratch/w3/laws_w3.md` applied as detectors. + +## 2. Baseline and final state (identical) + +`python -B tools/campaign/gate.py source/sound/sound_manager --all --forbid-emitted-symbol _point_from_line3d` +reports `== exact 58 residual 7 unwritten 0 (of 65 listed)` at both ends; the guard passed; +`scratch/parkcheck.py` on the real object reports `parks 5 drift 0`; `tools/fake_match_scan.py` reports 0 leads. +`branch_sweep` finds no historical blob above 58 exact. + +| Function | Target padded / relocs | Real file | Tag | Park | n3a result | +|---|---:|---|---|---|---| +| `_refresh_listener` | 496 / 25 | 496 / 25 | sha | - | skipped-exhausted | +| `_sound_dispose_from_old_map` | 240 / 21 | 272 / 23 | size, relocs, sha | - | skipped-exhausted (census re-confirmed) | +| `_sound_find_like_channel` | 272 / 16 | 272 / 16 | sha | P | skipped-exhausted | +| `_sound_refresh_looping` | 1136 / 43 | 1088 / 42 | size, relocs, sha | P | **park re-baseline proposed: 1120 / 43** | +| `_sound_set_definition_end` | 208 / 9 | 208 / 9 | sha | P | owner-blocked (mechanism now proven) | +| `_update_channel_for_impulse_sound` | 528 / 29 | 528 / 29 | sha | P | skipped-exhausted | +| `_update_channel_for_looping_sound` | 1360 / 61 | 1376 / 61 | size, sha | P | **park re-baseline proposed: 1360 / 61** | + +## 3. Result + +Nothing landed in the real file (both live targets are parked bodies). The wave produced three new byte-proven +structural findings, two of which refute the recorded park premises outright, and a combined scratch candidate +`scratch/workers/n3_sound_manager/cand.c` that improves both parked rows with zero regressions. + +## 4. `_update_channel_for_looping_sound` - park premise REFUTED, size and relocations now exact + +The park entry says "No unique source-level mechanism explains the residual". Two do. + +**4.1 If/else arm order (closes the whole 16-byte size gap).** January `0x452 mov ecx,[ebp-0x1c]; 0x455 test +byte [ecx],2; 0x458 jne 0x4e8` places the `_looping_sound_fake_impulse_sound_bit` arm FAR (0x4e8, after the +function tail, ending `jmp 0x482`) and makes the `sound_definition_next_permutation(definition, ..., NONE)` arm +the fall-through at 0x45e. The retained body emits the mirror image. VC7 lays if/else arms in source order +(laws_w3 A39/A44; A27 is the accepted precedent for reading polarity off arm layout), so January's source writes +the next_permutation arm first: + +```c +if (!TEST_FLAG( + looping_definition->flags, + _looping_sound_fake_impulse_sound_bit)) +{ + permutation_index = sound_definition_next_permutation( + definition, + sound->pitch_range_index, + NONE); +} +else +{ + sound->type = _sound_stop_track; + looping_sound->ordered_sounds_finished = TRUE; +} +``` + +Alone (`u1.c`) this takes the row from 1376/61 to **1360/61** - size and relocation count exact. + +**4.2 `limited_pitch` is not a separate local (closes the whole frame permutation).** Frame census: both sides +`sub esp,0x40`, eight 4-byte slots, none missing or extra, but three rotate: + +| slot | January | retained body | +|---|---|---| +| [ebp-4] | `sound` | `sound` | +| [ebp-8] | `pitch` / limit result (shared) | `channel` | +| [ebp-0xc] | `channel` | `scale` | +| [ebp-0x10] | `scale` | `pitch` / limit result | +| [ebp-0x14] .. [ebp-0x20] | primary_sound_index, track, looping_definition, looping_sound | same | + +January `fstp [ebp-8]` @0xdd stores `pitch` and `fst [ebp-8]` @0x2b2 stores the `limit_pitch` result into the +SAME cell: one source variable with five references, which outranks `channel`. With two separate locals VC7 ranks +each alone and the cell falls to fourth. Clamping in place removes the local: + +```c +pitch = limit_pitch( + pitch, + channel_get(sound->playing_channel_index)->pitch * pitch_range->natural_pitch, + definition->maximum_bend_per_second); +properties.pitch = pitch * pitch_range->playback_rate; +``` + +With 4.1 (`u2.c`) every frame slot matches January: **1360 / 61 / `cb7925e4a26b9464`**, 418 vs 420 instructions. + +**4.3 What is left (2 instructions, owner-blocked).** `@0x2ea` January `xor ecx,ecx; mov cx,word [edi+0x8e]; +push ecx` vs our `movzx ecx, word [edi+0x8e]; push ecx`, and `@0x37e` January's extra `xor eax,eax` before +`call _channel_get_state`. Both are laws_w3 A34 zero-extension into a full push slot, the fingerprint of a 16-bit +PARAMETER. `u3.c` proves an inline `(word)` cast at the call site is byte-inert, so there is no in-file lever. +The size is already exact, so these four bytes are absorbed by padding. + +## 5. `_sound_refresh_looping` - park premise REFUTED, relocations and block layout now exact + +**5.1 The missing relocation is a source defect, not "a compiler-merged duplicate fade-call tail".** Confirmed +and adopted from the w3c finding: deleting the `real fade_in_time;` phi local and writing the two +`sound_start_fade` calls restores 43/43 with matching per-target counts and January's 0x14 frame. + +**5.2 NEW: the `goto begin_stop;` spelling inverts the loop block layout.** The retained body emits the stop +block FIRST (ours @0x1e0 `cmp word [ebx+0x52],2`, main path jumped to @0x2d2); January falls through into the +main path @0x1db and jumps forward to the stop block @0x30a. Replacing the label and goto with the equivalent + +```c +if (refresh_state != _looping_sound_refresh_stop && !loop->ordered_sounds_finished) +{ + ... +} +else if (loop->state != _looping_sound_refresh_stop) +{ + ... +} +``` + +reproduces January instruction-for-instruction from 0xaf to the end, and deletes a `goto`. + +**5.3 NEW: the early exits do not return `result`.** January shares ONE frame cell `[ebp-1]` between `result` +(`sete byte [ebp-1]` @0x10) and `new_looping_sound` (`mov byte [ebp-1],0` @0x80, `,1` @0xa5), which forces +`source` into EBX (`mov ebx,[ebp+0x10]` @0xc, `push ebx` @0x93) - the retained body colours `result` into BL +instead and has to reload `[ebp+0x10]`. Measured: any early exit that names `result` (`a2`, `a6`, and w1's S1) +gives `result` its own cell `[ebp-2]` and loses the colouring; spelling both early exits +`return !new_looping_sound;` (`a1.c`) reproduces January's shared cell and EBX exactly. HCEA corroborates the +expression (`reference/halocea/src/sound_refresh_looping.c:117-125`, annotated as the original `return v16==0`). + +`a1.c` = **1120 / 43 / `86e26bb3e59f7590`**, 365 vs 378 instructions, with only two facts left: January's +`mov cl,1` @0x84 (the last flag-safe slot before `jne 0xaf`) and its private `mov al,cl` epilogue @0x451 for the +stop-and-not-found return, where our build constant-folds that return and cross-jumps it into the `datum_delete` +`mov al,1` epilogue. That is one tail-merge decision, laws_w3 A43 class. `a5` and `a7` refute the merged-exit and +extra-local explanations. + +## 6. The other rows + +- **`_sound_set_definition_end`** (park, 208/9): 72 vs 73 instructions, the single difference being the same + `xor eax,eax; mov ax,word [esi+0x8e]` vs `movzx`. `sd1.c` (a `word` local modelled on the EXACT in-TU donor + `_refresh_sounds`, sound_manager.c:3233) yields `mov ax,word [...]` plus a compensating `movzx eax,ax`, so it + is a clean negative. The mechanism is now proven: the January header declared a 16-bit third parameter for + `sound_definition_find_pitch_range_by_pitch` while the exact `sound_definitions.c` definition is `long`. The + owner already ruled that prototype an ABI lie (w1 4.4, w3c 5); per laws_w3 R13 the ruling binds and **no packet + is proposed**. Recorded for the owner's original-bug adjudication only. +- **`_sound_dispose_from_old_map`** (active residual, 272/23 vs 240/21): fresh census re-confirms the w3c + finding - January's wait loop is unrotated (`0xa6 jmp 0x82` back to a single header), ours duplicates the whole + 10-instruction header into the latch (+12 instructions, +32 bytes, +2 relocations). 11 loop spellings across 6 + lanes are measured inert and no w2/w3 law fires on header duplication. No shape spent. +- **`_refresh_listener`, `_sound_find_like_channel`, `_update_channel_for_impulse_sound`**: measured ties with + their levers spent (w3c section 5). No shapes spent. + +## 7. Proposals (orchestrator; zero strict credit) + +Candidate `scratch/workers/n3_sound_manager/cand.c` = the current real file plus exactly the two reopened bodies +(`a1` + `u2`). Measured: `== exact 58 residual 7 unwritten 0`, row-for-row identical to baseline except the two +target rows; `_point_from_line3d` guard passed; `parkcheck` drift only on the two intended parks; owner census +identical to `build/base` (no new candidate-only owner, 22 candidate-only vs target on both sides); fakescan 0. + +1. Re-baseline `config/parked.json` `_sound_refresh_looping` 1088/42/`4820b25c...` -> **1120/43/`86e26bb3e59f7590`** + and land `a1`'s body. Supersedes the w3c `rl1` proposal (also 1120/43 but still block-inverted). +2. Re-baseline `_update_channel_for_looping_sound` 1376/61/`5fa29769...` -> **1360/61/`cb7925e4a26b9464`** and + land `u2`'s body; reclassify the park from `unclassified` to the 16-bit-parameter owner-ruling class. + +Both are strictly closer on every measured axis and both delete a local rather than adding anything. + +## 8. Reopen criteria (replacing the recorded ones) + +- `_sound_refresh_looping`: a source fact that stops VC7 constant-folding the stop-and-not-found + `return !new_looping_sound;` and cross-jumping it into the `datum_delete` `return TRUE;` epilogue - i.e. + anything that yields `mov cl,1` in the block ending at `jne` plus a private `mov al,cl` epilogue. Everything + else in `a1.c` is byte-proven. Do not re-spend w1 S1-S8, waveC R1-R10, w3c rl1-rl3, or n3a a1/a2/a5/a6/a7. +- `_update_channel_for_looping_sound`: an owner ruling on the width of + `sound_definition_find_pitch_range_by_pitch`'s third parameter, plus a form that zero-extends + `sound->playing_channel_index` into the full EAX before `channel_get_state`. Everything else in `u2.c` is + byte-proven. Do not re-spend the frame-permutation or block-order searches, or `u3`'s cast. +- `_sound_set_definition_end`: unchanged (same owner ruling). +- `_sound_dispose_from_old_map`: an EXACT January sibling whose `while (call() < real)` loop keeps a single header + copy, with a nameable source cause. + +## 9. Disposition + +`sound_manager.obj` stays at 58/65 strict exact; this wave claims no bytes. It leaves two actionable zero-credit +park re-baselines that together take the two largest residual rows from 1088/42 and 1376/61 to 1120/43 and +1360/61, and it reduces both to a single named compiler decision each. diff --git a/source/sound/sound_manager.c b/source/sound/sound_manager.c index e66b63e9..c5f78197 100644 --- a/source/sound/sound_manager.c +++ b/source/sound/sound_manager.c @@ -2620,32 +2620,52 @@ boolean sound_refresh_looping( _sound_start_track); } } - else if (refresh_state == _looping_sound_refresh_stop) - { - goto begin_stop; - } - if (loop->ordered_sounds_finished) + if (refresh_state != _looping_sound_refresh_stop && + !loop->ordered_sounds_finished) { - goto begin_stop; - } - - sound_definition_index = track->loop_sound.index; - if (alternate && track->alternate_loop_sound.index != NONE) - { - sound_definition_index = track->alternate_loop_sound.index; - } - - if (sound_definition_index != NONE) - { - if (*playing_sound_index != NONE && - (refresh_state != _looping_sound_refresh_start || - !TEST_FLAG(track->flags, _fade_in_at_start_bit))) + sound_definition_index = track->loop_sound.index; + if (alternate && track->alternate_loop_sound.index != NONE) { - sound_get(*playing_sound_index); + sound_definition_index = track->alternate_loop_sound.index; + } - if (alternate != loop->alternate && - TEST_FLAG(track->flags, _fade_in_alternate_bit)) + if (sound_definition_index != NONE) + { + if (*playing_sound_index != NONE && + (refresh_state != _looping_sound_refresh_start || + !TEST_FLAG(track->flags, _fade_in_at_start_bit))) + { + sound_get(*playing_sound_index); + + if (alternate != loop->alternate && + TEST_FLAG(track->flags, _fade_in_alternate_bit)) + { + long new_sound_index = + update_potentially_audible_looping_sound( + sound_definition_index, + looping_sound_index, + track_index, + _sound_loop_track); + + if (new_sound_index != NONE) + { + sound_start_fade( + _sound_fade_mode_linear, + track->fade_out_duration, + new_sound_index, + *playing_sound_index); + *playing_sound_index = new_sound_index; + } + } + else if (!new_looping_sound) + { + sound_set_definition_begin( + *playing_sound_index, + sound_definition_index); + } + } + else { long new_sound_index = update_potentially_audible_looping_sound( @@ -2656,65 +2676,34 @@ boolean sound_refresh_looping( if (new_sound_index != NONE) { - sound_start_fade( - _sound_fade_mode_linear, - track->fade_out_duration, - new_sound_index, - *playing_sound_index); + sound_get(new_sound_index); + + if (refresh_state == _looping_sound_refresh_start) + { + if (TEST_FLAG(track->flags, _fade_in_at_start_bit)) + { + sound_start_fade( + _sound_fade_mode_linear, + track->fade_in_duration, + new_sound_index, + NONE); + } + } + else + { + sound_start_fade( + _sound_fade_mode_linear, + sound_inaudible_fade_out_time, + new_sound_index, + NONE); + } + *playing_sound_index = new_sound_index; } } - else if (!new_looping_sound) - { - sound_set_definition_begin( - *playing_sound_index, - sound_definition_index); - } - } - else - { - long new_sound_index = - update_potentially_audible_looping_sound( - sound_definition_index, - looping_sound_index, - track_index, - _sound_loop_track); - - if (new_sound_index != NONE) - { - real fade_in_time; - sound_get(new_sound_index); - - if (refresh_state != _looping_sound_refresh_start) - { - fade_in_time = sound_inaudible_fade_out_time; - } - else if (TEST_FLAG( - track->flags, - _fade_in_at_start_bit)) - { - fade_in_time = track->fade_in_duration; - } - else - { - *playing_sound_index = new_sound_index; - continue; - } - - sound_start_fade( - _sound_fade_mode_linear, - fade_in_time, - new_sound_index, - NONE); - *playing_sound_index = new_sound_index; - } } } - - continue; - - begin_stop: - if (loop->state != _looping_sound_refresh_stop) + else if (loop->state != _looping_sound_refresh_stop) { if (fade_time != 0.f) { @@ -2740,16 +2729,16 @@ boolean sound_refresh_looping( *playing_sound_index); } - sound_definition_index = track->stop_sound.index; - if (alternate && - track->alternate_stop_sound.index != NONE) + if (track->stop_sound.index != NONE) { - sound_definition_index = - track->alternate_stop_sound.index; - } + sound_definition_index = track->stop_sound.index; + if (alternate && + track->alternate_stop_sound.index != NONE) + { + sound_definition_index = + track->alternate_stop_sound.index; + } - if (sound_definition_index != NONE) - { if (TEST_FLAG( track->flags, _fade_out_at_stop_bit)) @@ -2786,7 +2775,8 @@ boolean sound_refresh_looping( loop->alternate = alternate; loop->state = refresh_state; - result = FALSE; + + return FALSE; } } @@ -2867,25 +2857,23 @@ static void update_channel_for_looping_sound( } else { - real limited_pitch; - pitch_range = TAG_BLOCK_GET_ELEMENT( &definition->pitch_ranges, sound->pitch_range_index, struct sound_pitch_range); - limited_pitch = limit_pitch( + pitch = limit_pitch( pitch, channel_get(sound->playing_channel_index)->pitch * pitch_range->natural_pitch, definition->maximum_bend_per_second); - properties.pitch = limited_pitch * pitch_range->playback_rate; + properties.pitch = pitch * pitch_range->playback_rate; if (sound->type == _sound_loop_track && (sound->fade_start_time == sound->fade_stop_time || sound->fade_interpolation_end != 0.f) && sound_definition_find_pitch_range_by_pitch( definition, - limited_pitch, + pitch, (word)sound->pitch_range_index) != sound->pitch_range_index && channel->sound_index == *primary_sound_index && !sound_manager_globals.idling) @@ -2943,20 +2931,20 @@ static void update_channel_for_looping_sound( 0xA1C, TEST_FLAG(definition->flags, _sound_definition_linked_permutations_bit)); - if (TEST_FLAG( + if (!TEST_FLAG( looping_definition->flags, _looping_sound_fake_impulse_sound_bit)) - { - sound->type = _sound_stop_track; - looping_sound->ordered_sounds_finished = TRUE; - } - else { permutation_index = sound_definition_next_permutation( definition, sound->pitch_range_index, NONE); } + else + { + sound->type = _sound_stop_track; + looping_sound->ordered_sounds_finished = TRUE; + } } if (permutation_index != NONE)