From aa2ada3f523e59dce4df147f78ca005a75592a9e Mon Sep 17 00:00:00 2001 From: Jonas Volman Date: Tue, 15 Sep 2026 16:25:03 -0700 Subject: [PATCH] Complete game_engine title string body (zero credit) and record wave w3c batch 1 _game_engine_generate_title_string was structurally incomplete (1088/70 against January's 1152/73) and skipped January's common null-termination tail on an unrecognised win result. The rewritten single-exit body reaches January's size, relocation count and frame layout with two team-name wchar_t[8] buffers instead of the union spelling, and 391/391 instructions with one transposed push. It is still [sha] residual, so it carries ZERO byte credit; the reopen criterion is in the ledger. Wave w3c batch 1 (8 groups, 36,384 spendable padded bytes) landed no strict exact function. The per-object ledgers record the measured blockers, the rejected _bitmap_2d_smooth reopen (it loses the exact _bitmap_2d_uncompress_from_mipmap row) and the proposals raised. Full ninja, parks 280/0/0, stable diff vs scratch/claude250k-start.json: gained 0, regressions 0; fake scan 0 leads. Co-Authored-By: Claude Opus 5 --- ...ctor_combat_obj_opus5_250k_w3c_20260915.md | 167 +++++++++++++++ ...p_utilities_obj_opus5_250k_w3c_20260915.md | 156 ++++++++++++++ ...game_engine_obj_opus5_250k_w3c_20260915.md | 171 +++++++++++++++ ...s5_250k_house_clean_checkpoint_20260915.md | 73 +++++++ ..._statistics_obj_opus5_250k_w3c_20260915.md | 143 +++++++++++++ ...environment_obj_opus5_250k_w3c_20260915.md | 142 +++++++++++++ ...und_manager_obj_opus5_250k_w3c_20260915.md | 197 ++++++++++++++++++ ...lens_flares_obj_opus5_250k_w3c_20260915.md | 110 ++++++++++ ...t_functions_obj_opus5_250k_w3c_20260915.md | 142 +++++++++++++ source/game/game_engine.c | 188 ++++++++--------- 10 files changed, 1388 insertions(+), 101 deletions(-) create mode 100644 docs/object_matching_logs/actor_combat_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/bitmap_utilities_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/game_engine_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/opus5_250k_house_clean_checkpoint_20260915.md create mode 100644 docs/object_matching_logs/rasterizer_frame_statistics_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/sound_manager_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/structure_lens_flares_obj_opus5_250k_w3c_20260915.md create mode 100644 docs/object_matching_logs/ui_widget_game_data_input_functions_obj_opus5_250k_w3c_20260915.md diff --git a/docs/object_matching_logs/actor_combat_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/actor_combat_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..556600b2 --- /dev/null +++ b/docs/object_matching_logs/actor_combat_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,167 @@ +# `actor_combat.obj`: second pass (opus5 250K lane, wave w3c, 2026-09-15) + +## Scope and provenance + +- Lane `opus/250k-house-clean-20260915`, HEAD `0483bde89`, worktree + `C:\halo-worktrees\opus5-150k-house-clean-20260914`. Worker `w3c:actor_combat`; the only file this worker + could modify is `source/ai/actor_combat.c`. **It is unchanged** (`git diff --stat -- source/ai/actor_combat.c` + is empty). No header, config, build or tool file was touched. +- Target: `build/split/source/ai/actor_combat.obj`. Compiler XDK VC7 13.00.9254, repository flags via + `tools/campaign/gate.py`. +- Worker notes with the per-function report blocks: `scratch/workers/w3c_actor_combat.md`. + Scratch candidates and objects: `scratch/workers/w3c_actor_combat/` (`g1-g4`, `p1-p6`, `r1-r3`, `score.py`). +- Evidence used: January target COFF (disassembly, relocations, per-function relocation census), + HCEA `halocea-review/src/blam/ai/actor_aim_grenade.c` (semantics/topology only), `scratch/atlas/actor_combat.obj.txt`, + `config/parked.json`, and the prior ledgers listed in the notes file. +- Laws applied as detectors: WORKER_BRIEF 9, `scratch/w2/laws_w2.md` (A4, A5, A6, A11, A20, A24 precursor, + A30 in w3), `scratch/w3/laws_w3.md` (A24, A30, A31, A35, R13, R16, E18). + +## Baseline and final (real file) + +`python -B tools/campaign/gate.py source/ai/actor_combat --all --forbid-emitted-symbol _point_from_line3d` + +| | result | +| --- | --- | +| baseline | `== exact 28 residual 6 unwritten 0 (of 34)`; guard passed; parkcheck `parks 4 drift 0` | +| final (real file, unchanged) | identical listing; guard passed; parks 4/4 undrifted; `tools/fake_match_scan.py` 0 leads | + +No row is newly exact. Nothing landed; this wave produced evidence, a refined reopen criterion for two parks, +and one owner-ruling request. + +## Per-function outcomes + +| function | January padded / relocs | state | outcome | +| --- | --- | --- | --- | +| `_actor_aim_grenade` | 544 / 22 (ours 576) | residual `[size]` | NOT-LANDED; 4 shapes; g4 closes the output block, frame + facing-address CSE remain; owner ruling requested | +| `_actor_combat_plan_grenade_trajectory` | 288 / 5 | PARK | scratch-only; **A24 confirmed**: 18 hunks -> 6, frame 0x34 -> 0x30 exact; not exact, no reopen | +| `_actor_combat_reaim_grenade` | 448 / 16 | PARK | scratch-only; 3 shapes, all byte-identical; no reopen | +| `_actor_aim_projectile` | 720 / 27 | PARK | SKIPPED-EXHAUSTED (2-instruction copy-pipelining tie; shared with plan) | +| `_actor_combat_find_nearby_target` | 352 / 12 | PARK | SKIPPED-EXHAUSTED (x87 last-use fold; new counter-example found, no lever) | +| `_actor_start_burst` | 1968 / 67 | residual `[sha]` | SKIPPED-EXHAUSTED (14 spellings across 3 lanes; w1 reopen criterion unmet) | + +## `_actor_aim_grenade` (the only non-parked structural row) + +Three independent facts now explain the 32-byte gap. + +1. **Aim-speed x87 CSE — solved.** January loads `actor->control.grenade_current_aim_speed` once + (`fld [edi+0x6c8]`) and spends it as `fmul st(1)` twice plus a folded `fmul [aim_vector.k]`. Our source + multiplied each component by the field, reloading the 6-byte operand three times. Candidate `g4` + (`real aim_speed = actor->control.grenade_current_aim_speed;` then `aim_speed*aim_vector.i/j/k`, matching + HCEA's `throw_speed`) reproduces January's final block instruction for instruction and takes the differing + target lines from 78 to 73. It cannot land alone (a written residual lands only when strict EXACT). +2. **Facing-vector address CSE — open.** January materialises `lea ecx,[edi+0x174]` and reads the facing vector + as `[ecx]` / `[ecx+4]` in the 2D dot and the 2D cross, then `mov edx,ecx` for the 12-byte + `new_aim_vector` copy. Ours emits seven 6-byte `[edi+0x174..0x17c]` operands. A block-local + `real_vector3d const *facing_vector` (g1) and a genuine inlined `cross_product2d` call with the admitted + `(real_vector2d const *)` cast (g2) are both byte-identical to the current file: VC7 constant-folds the + field address in every spelling we can write, so the register form has some other cause. +3. **Frame 0x24 vs 0x28.** January shares `[-0x24..-0x19]` between `desired_grenade_target` and `aim_vector`, + and packs the `(counterclockwise ? 1 : -1)` fild temp into the dead `aim_vector2d.j` cell `[-8]`. Ours gives + the fild temp its own cell and hands the dead retarget-point cells to `new_aim_vector` instead. + +HCEA's lift of the same function uses ONE working triple (`direction_x/y/z`): assigned from the retarget point +in the prop branch, overwritten by the refreshed aim vector on the on-foot path, and multiplied by the throw +speed at the end. That is precisely January's shared `[-0x24..-0x19]` words, and it also explains the +vehicle-plus-retarget behaviour recorded by `actor_combat_obj_grenade_vehicle_semantic_withdrawal_20260905.md`. + +**I did not build that arrangement.** The 20260905 audit records an explicit hold ("Do not restore the +point/vector cast or equivalent explicit position-as-velocity carrier"), and laws_w3 R13 makes older holds +binding on later waves. + +### Owner ruling asked for (no header/config text) + +Rule on whether `_actor_aim_grenade` may use a single working direction vector — the variable that receives +the prop body position (+0.2 z) for the retarget call and is then overwritten by +`actor->control.grenade_current_aim_vector` on the on-foot path — spelled with the already-admitted +`(real_point3d const *)` / `(real_vector3d *)` cast at the retarget call and a +`/* BUG (preserved for exact matching): ... */` note for the vehicle path (where January, like the current +tree, reads the working words without writing them when no retarget happened). + +- For: HCEA's independent lift has exactly this shape; it is the only hypothesis that explains January's frame + without an allocator coincidence; the current tree *already* contains the same uninitialised-read path, so + the ruling does not add undefined behaviour, it only changes which variable carries the value. +- Against: brief section 5 forbids representation punning, and the 20260905 audit withheld semantic acceptance + pending a release-time `vehicle_index == NONE` lifecycle proof. +- If allowed: replay g4's `aim_speed` local on top and re-gate; expect the frame to close to 0x24, leaving only + the facing-address CSE (item 2) to adjudicate. 544 padded bytes are at stake. + +## `_actor_combat_plan_grenade_trajectory`: A24 confirmed on a pointer parameter + +The park is recorded as `register-allocation` with the criterion "authoritative source or a natural +same-compiler stack-lifetime donor". The w3 law A24 (parameter-home offer) is exactly such a donor and it +fires here: January's `&aim_gravity` is `lea edx,[ebp+0x10]`, the incoming home of the `grenade_target` +pointer parameter, which is enregistered into EDI on the entry path; ours allocates a seventh cell +(frame 0x34) and shifts every local by 4. + +| shape | result | +| --- | --- | +| `p1` aim_gravity declared first among the scalars | byte-identical (18 hunks / 20 lines) | +| `p2` declarations in the callee's out-parameter order | byte-identical | +| `p3` nested `if`s instead of the `&&` chain | byte-identical | +| **`p4` entry copy `real_point3d const *grenade_target_position = grenade_target;` (2 genuine uses)** | **frame 0x30, all slots match, 18 hunks -> 6, 20 -> 5 lines; 288/5 sha `7454798a13f3419b`** | +| `p5` p4 + the ignore-index store moved after the aim-vector assignment | same 6/5 (store order between fields of one base is canonicalised) | +| `p6` p4 + declaration-order store block | worse (8/6/6) | + +What remains under p4 is a 12-byte-copy scheduling tie: January loads `aim_vector.i` and `.j` before the first +store, interleaves the `grenade_current_ignore_object_index` store between `.i` and `.j`, and sinks the `.k` +store past the speed and lob stores. Store-order permutations are measured inert, so no source statement order +reproduces it. + +Not proposed as a reopen: p4 is not strict exact, and a bare pointer rename of a parameter is R16-adjacent +(A24's landed precedent `real v_max = base_velocity;` had a semantic role that this alias lacks). The park stays +locked and undrifted. + +**Refined reopen criterion** (for `config/parked.json` if the orchestrator wants it recorded): the frame half +is solved by an A24 parameter-home copy; the remaining blocker is VC7's load-ahead pipelining of a 12-byte +struct copy, which is *the same* tie as the whole residual of `_actor_aim_projectile`. One donor closes both +(288 + 720 = 1,008 padded bytes). + +## `_actor_combat_reaim_grenade` + +January's frame 0x2c packs `aim_gravity` into the dead `aim_vector2d.j` cell `[ebp-0xc]`; ours (0x30) gives it +its own cell and additionally ranks `aim_ticks` above `aim_vector2d` where January's ranking follows the +refs/size rule. Three scope/declaration shapes were measured, all byte-identical to the park body: +`r1` (declare `aim_vector2d` inside the `projectile_aim` block — laws_w2 A6 nested-block packing side law), +`r2` (hoist `aim_velocity`/`aim_gravity` to function scope), `r3` (swap the inner declaration order). With w1's +`g1` (gravity as the `?:` argument temp) that is four inert probes; the packing decision has no source lever we +can find. Park unchanged, criterion unchanged. + +## `_actor_aim_projectile` and `_actor_combat_find_nearby_target` + +- `aim_projectile`: 6 hunks / 5 lines, all of it the two transposed instructions of the + `actor_debug_info->field_98 = weapon_vector;` copy (January hoists the `.j` load above the `lea` and sinks the + `.j` store past the `field_88` byte store). The park already spent the statement-order matrix; no w2/w3 law + fires (A20 is satisfied, A31(c) needs a parameter-based base). Same behaviour as the plan residual above. +- `find_nearby_target`: unchanged 2-instruction x87 tie (January keeps `distance` live to an explicit + `fstp st(0)`; VC7 folds the last use). New context: the *inverse* of this decision appears in + `_actor_aim_grenade`'s magnitude block (January folds, ours pops), so operand order does not control the + fold; both spellings exist in this one object with opposite outcomes. No shape spent. + +## `_actor_start_burst` + +Skipped as exhausted: 5 shapes in w1, 9 in the 100K lane and the PDB lane's flips/scoping/`*=` matrix are all +on do-not-repeat lists, and the recorded reopen criterion (a spelling that spills `limit*1.5f` to memory before +the compare yet still sources the origin-radius store from the x87) is not produced by any w2/w3 law. The +"write the capped expression twice" idea is already the shape in the tree. + +## Hygiene observation (not landed; laws_w3 E18) + +`g2` removes the per-TU `#define cross_product2d cross_product2d_inline` rename *and* the hand-written TU-local +`cross_product2d` definition, calling the genuine `real_math.h` helper at the one site whose expression is a 2D +cross product (`_actor_aim_grenade`'s `counterclockwise`). All 34 rows stay byte-identical, `_cross_product2d` +stays EXACT (now as the header's select-any COMDAT), and the A30 rename defect disappears. Evidence that +January's source really called the helper there: `_cross_product2d` is an owner of January's object +(file offset 0000F9B0) with **zero** call relocations anywhere in the object — a non-static `__inline` emits its +surplus COMDAT only when it is used, so the call site exists and VC7 inlined it. Not landed because brief +section 8 requires an empty file diff in a unit where nothing lands; the orchestrator can take it as a +hygiene hunk. Caveat: it moves the COMDAT's section position earlier in the object (section 8 vs 39; January +has it at 15, adjacent to the trig wrappers and `_add_vectors3d`, which matches the current TU-local position). + +## Reopen criteria (summary) + +- `_actor_aim_grenade`: owner ruling on the single working direction vector (then replay `g4`), or a donor for + VC7's struct-field address CSE (`lea` + `[reg]` operands) without a helper call. +- `_actor_combat_plan_grenade_trajectory` / `_actor_aim_projectile`: a donor for VC7's load-ahead pipelining of + a 12-byte struct copy. The plan frame is solved by an A24 parameter-home copy (`p4`). +- `_actor_combat_reaim_grenade`: a donor that packs a scalar local into the dead half of an in-scope aggregate. +- `_actor_combat_find_nearby_target`, `_actor_start_burst`: unchanged. diff --git a/docs/object_matching_logs/bitmap_utilities_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/bitmap_utilities_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..db03a3d6 --- /dev/null +++ b/docs/object_matching_logs/bitmap_utilities_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,156 @@ +# `bitmap_utilities.obj` second pass — opus5 250K lane, wave w3c (2026-09-15) + +## Result + +The real file `source/bitmaps/bitmap_utilities.c` is **unchanged** (`git diff --stat` empty). Final real-file gate is +identical to baseline: `== exact 43 residual 5 unwritten 0`, `_point_from_line3d` guard passes, park drift 0, +owner census identical to `build/base`, fake scan 0 leads. + +The wave produced one **strict-exact scratch body** and one **strictly-closer scratch body**, both blocked from +landing by a single sibling regression, plus a new measured VC7 law and a TU-context diagnosis. + +| Function | Padded | Baseline | This wave (scratch) | Status | +| --- | ---: | --- | --- | --- | +| `_bitmap_2d_smooth` | 1136 | 1120/27 `50140a12` (park) | **1136/27 `90b74220` = target, strict EXACT** | reopen proposed, BLOCKED | +| `_bitmap_3d_smooth` | 1824 | 1792/28 `dc74abcc` (park) | 1824/28 `f8881250`, 608 of 610 instructions identical | strictly-closer re-baseline | +| `_bitmap_2d_alpha_bleed` | 560 | 560/24, 1 reload transposition | 5 shapes, all byte-identical or worse | tie budget spent | +| `_bitmap_2d_sharpen` | 1152 | 1184/29, 47 hunks | no shape spent; 4 new byte facts recorded | keystone, criterion unmet | +| `_bitmap_sharpen` | 352 | 352/23, normalized sha **equal** | none possible | tooling/owner | + +Scratch candidates: `scratch/workers/w3c_bitmap_utilities/` (`m4.c` = 2d_smooth reopen only; `m7.c` = + 3d_smooth; +`m10.c` = m7 + the 16-site assert-cast authenticity fix; `h1.c` = HEAD + that fix alone). +Worker notes with every shape and measurement: `scratch/workers/w3c_bitmap_utilities.md`. + +## Baseline and duplicate prevention + +Baseline rows (January padded / tags): `_bitmap_2d_alpha_bleed` 560 [sha]; `_bitmap_2d_sharpen` 1152 +[size 1184!=1152, sha]; `_bitmap_2d_smooth` 1136 [size 1120!=1136, sha]; `_bitmap_3d_smooth` 1824 +[size 1792!=1824, sha]; `_bitmap_sharpen` 352 [reloc-identity]. All five are parked; parkcheck drift 0 at baseline. + +Read first: all eight `bitmap_utilities_obj_*` ledgers (w1 150K, smooth/sharpen completion, selective donor +reconciliation, the four jonas ledgers), the w1 worker notes `scratch/workers/bitmap_utilities.md` with its +do-not-repeat list, the 150K checkpoint, `scratch/w2/laws_w2.md` (A1-A22, R1-R12) and `scratch/w3/laws_w3.md` +(A23-A45, R13-R19). `git log --all --oneline -- source/bitmaps/bitmap_utilities.c`: 13 commits, none newer than the +w1 landing; w1's `branch_sweep.py` found no blob better than current. The three functions w1 reopened +(`_bitmap_2d_shrink`, `_bitmap_3d_shrink`, `_bitmap_2d_uncompress_from_mipmap`) are inherited exact and are not +re-claimed here. No C4013/C4133 site exists in this unit (`scratch/gate4/c4013.txt`). + +## New law: NAMED-TEMPORARY NORMALIZER (refines laws_w2 A12 / A5; answers the w1 reopen criterion) + +**Law.** For a loop-invariant round-and-shift normalisation `(sum + (1 << (2*n - 1))) >> (2*n)`, VC7 assigns the +registers and the two spill slots from the *expression structure*, not from the declaration: + +- written as **one expression** (the natural spelling, repeated per channel or through a function-like macro), the + `2*n` CSE is created first and stays in its own register in place — January's + `movsx eax,di; add eax,eax` ... `mov esi,1; shl esi,cl`, slots shift `-0x24`, rounding `-0x28`; +- written as **two named `long` locals** (`shift = 2*n; rounding = 1 << (2*n - 1);`), the doubling is moved out with + `lea esi,[eax+eax]` and the `1 <<` temp takes EAX — registers **and** slots swapped, whatever the order of the + declarations or of the assignments. + +**Controls measured this wave.** Declaration-order swap `long rounding, shift;` vs `long shift, rounding;` is +byte-identical (m1 vs m2), so *the slot follows the register, not the declaration*; the macro form alone on the +otherwise unchanged parked body already flips the preheader to January's exact register/slot assignment (m3), which +isolates the lever from the rest of the restructuring. w1's measured negatives stand and are explained by the law: +`rounding = 1 << (shift - 1)` and "no rounding variable" both fall back to 1120 because they still name a normaliser. + +**Co-factors, each proven necessary** (removing either loses exactness): one `short x, y;` shared by both passes, +declared at the `if (width >= filter_size && height >= filter_size)` block (m5 = 1136 [sha]); and the coefficient +read placed **after** the pixel load inside the tap loop (m6 = 1136 [sha]). Both were already evidenced by w1. + +**Closures.** `_bitmap_2d_smooth` 1136/27, `alndiff` 376/376 instructions with zero non-artifact hunks. +The same three-part treatment of `_bitmap_3d_smooth` takes it from 1792/28 with 49 diff blocks to 1824/28 (January's +size and relocation count) with `alndiff` 610/610 and only two hunks. + +## The blocker: a TU-context coin, and the 16 casts that currently buy an exact row + +Every exact spelling of `_bitmap_2d_smooth` removes five declarators from the function, and that flips +`_bitmap_2d_uncompress_from_mipmap` (880, EXACT at baseline, **not** parked) to residual. Measured in five contexts +(m4, m7, m10, p1, p2), and the flip also occurs when only `_bitmap_3d_smooth` is reopened (m8). No spelling of +uncompress recovers it: u1-u4 permute every declaration order/grouping of its locals and are byte-identical. + +Four rows of this object differ from January by **nothing except the order of two independent register reloads at a +join**, and in each case ours is the exact reverse of January: + +| function | site | January | ours | +| --- | --- | --- | --- | +| `_bitmap_2d_alpha_bleed` | +0x191 | `ecx=[ebp-0x10]` (x), `eax=[ebp-0x20]` (x*4) | reversed | +| `_bitmap_2d_uncompress_from_mipmap` | +0x33a | `edi=[ebp-0x24]` (height), `ebx=[ebp+0x10]` (mipmap index) | reversed | +| `_bitmap_3d_smooth` (m7) | +0x522 | `edx=[ebp-0x40]` (-filter_size), `esi=[ebp-8]` (rounding) | reversed | +| `_bitmap_3d_smooth` (m7) | +0x601 | `esi=[ebp-8]` (rounding), `eax=[ebp-0x20]` (shift) | reversed | + +Nine body-local shapes across three functions (alpha_bleed a1-a3/a5, uncompress u1-u4, 3d_smooth n2-n3) were +byte-identical: there is no body lever. The coin is set by TU-wide text — w1's definition-order reorder flipped +uncompress on; the 2d_smooth reopen, the 3d_smooth reopen, an unrelated alpha_bleed probe (a4) and the assert-cast +authenticity fix each flip it off. + +**Critically, HEAD's uncompress exact row is bought with text January's own assert literals refute.** Sixteen +`match_vassert(file, line, , "")` sites exist in +this TU; the literal is January's stringised original, so January's source had no cast. Candidate `h1.c` = HEAD with +those sixteen sites rewritten as `match_assert(file, line, )` — nothing else changed — is +`42 exact / 6 residual` with park drift 0: the only row that moves is `_bitmap_2d_uncompress_from_mipmap`. So the +880-byte exact row at HEAD depends on sixteen reconstruction artifacts, and any authentic cleanup of this TU costs it. + +**Emission-order evidence for the underlying gap.** The candidate's code-section order still differs from January's. +January defers `_bitmap_2d_height_map`, `_bitmap_3d_height_map` and all three `*_vector_map` statics past the colour +conversions and emits `_rgb_colors_interpolate`/`_and_scale` next to last; our object emits every static in plain +source order. The deferred set is exactly the two statics that call the `real_math.h` helpers (`cross_product3d`, +`normalize3d`, `fast_ftol`) plus their transitive same-TU callers — `_bitmap_cm_height_map`, which calls none of +them, is not deferred — and our object additionally **defines** the eight select-any COMDATs +(`_fast_ftol, _square_root, _scale_vector3d, _magnitude_squared3d, _magnitude3d, _normalize3d, _cross_product3d, +_valid_real`) that January references undefined. That is the header-inline linkage property (laws_w3 A30/A41 and the +header-inline-linkage law), which a worker cannot change from this `.c`. + +## `_bitmap_2d_sharpen`: new byte facts (no shape spent) + +The w1 reopen criterion ("evidence for why January keeps `temporary_pixels` out of EDI") is still unmet, and no law +in laws_w2/laws_w3 predicts which pointer wins a callee-saved register (A24 does not fire: no January temporary sits +in a parameter home here). Four facts were recovered for the next attempt: + +1. January's prologue is `push ebx; mov ebx,[ebp+8]` and it shrink-wraps `push edi` to `+0xf1` inside the + allocation-success block: **`bitmap` owns EBX from entry, EDI is the `y`/`byte_index` web, and + `temporary_pixels` is never enregistered** (`mov [ebp-0x24],eax` at `+0xe4`, reloaded at `+0x158`). Ours is the + mirror image (`push edi; mov edi,[ebp+8]`, `mov ebx,eax` for `temporary_pixels`, `push ebx` at `+0xc4`). +2. The private ABI passes `negative_table` in ESI (`+0x87 test esi,esi`, ESI never pushed); both builds agree. +3. Loops 2 and 3 run as down-counters over `previous_row + byte_index` with `[ebp-0x28] = destination_row - + previous_row` (store `+0x312 mov [ebx+edx],cl`); ours picks `destination_row + x` as the IV representative, which + follows from fact 1 rather than from the kernel term order (w1 already refuted term order with a 417-order sweep). +4. The interior bound is a **16-bit** compare (`+0x25a cmp di,cx`) against `(4*width)-4` and loop 3 reuses that slot + `+= 4`, so the row bounds are short-typed; loop 1 rematerialises `4*bitmap->width + byte_index` from `[ebp+8]` + every iteration (`+0x23b mov ebx,[ebp+8]`), i.e. no long-lived row-size register. + +This function is the only remaining structurally wrong body between `_bitmap_2d_smooth` and +`_bitmap_2d_uncompress_from_mipmap` in compile order and is therefore the most likely keystone for the coin class. + +## `_bitmap_sharpen`: park evidence refreshed + +`relocdiff` reports `size 352/352, relocations 23/23, sha equal`. Only two relocation **spellings** differ, both +resolving to the same image address: `+0x00a6` January `_bitmap_sharpen_negative_table+510` vs ours +`_bitmap_sharpen_positive_table-2` (3260670), and `+0x00bb` January `_extract_data+42` vs ours +`_bitmap_sharpen_negative_table-2` (3260158). The park evidence text is **stale**: it names the second January anchor +`_bss_0031bed4+42`; at this HEAD csplit spells it `_extract_data+42` (a symbol owned by another object). Because the +machine code is byte-identical, no source spelling can change the relocation's target address — this is purely +csplit's nearest-preceding-symbol choice (laws_w3 A45) and needs the comparator-side resolved-equal admission, an +orchestrator action with zero strict credit. + +## Rejected / not proposed + +- Landing `m4`/`m10` as-is: violates admission checklist item 2 (`_bitmap_2d_uncompress_from_mipmap` would go + exact -> residual). Reported as a blocked park-reopen instead. +- The 16-site assert-cast authenticity fix as a standalone hygiene landing: it costs the same exact row, and + brief section 8 forbids a byte-inert-only diff in a unit where nothing lands. +- Compensating for the coin by perturbing unrelated exact bodies (merging/splitting declarations elsewhere): that is + steering with no authenticity basis; not attempted beyond diagnostics. +- Any further body shapes on the four reload-transposition rows: nine measured-identical shapes say no body lever + exists. + +## Reopen criteria + +- `_bitmap_2d_smooth` (1136) and `_bitmap_3d_smooth` (1824): land the bodies in + `scratch/workers/w3c_bitmap_utilities/m10.c` together with whatever keeps `_bitmap_2d_uncompress_from_mipmap` + exact in the same context — most plausibly a correct `_bitmap_2d_sharpen` — or with an owner ruling that accepts + the exchange on the grounds that the HEAD row depends on sixteen casts January's assert literals refute + (net +1136 gained, -880 lost, and the 16-site cleanup comes for free). +- `_bitmap_2d_sharpen` (1152): a source-level reason for `bitmap` owning EBX and `temporary_pixels` staying in + memory; start from w1 `s5f.c` plus the four facts above. +- `_bitmap_2d_alpha_bleed` (560): TU-context only; do not spend body shapes again. +- `_bitmap_sharpen` (352): comparator-side resolved-equal admission. diff --git a/docs/object_matching_logs/game_engine_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/game_engine_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..e4c4a757 --- /dev/null +++ b/docs/object_matching_logs/game_engine_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,171 @@ +# `game_engine.obj` opus5 250K house-clean lane, wave w3c (2026-09-15) + +## Scope and provenance + +- Translation unit: `source/game/game_engine.c` (the only file this worker edited). +- Target: `build/split/source/game/game_engine.obj` (January 2002 Xbox debug split) - the byte authority. +- Compiler: XDK 3911 VC7 13.00.9254, checked-in `build.ninja` flags; every candidate compiled through + `tools/campaign/gate.py --source` only. No ninja, configure, header or config edit. +- Lane: `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/250k-house-clean-20260915`. +- Ledgers read in full: `game_engine_obj.md` (GE01-GE31), `_codex_checkpoint`, + `_fable_salvage_admission_20260902`, `_fable_small_families_20260901`, the three `_jonas_*` ledgers, + `_opus5_fresh_graphs_20260914`, `_opus5_100k_waveC_20260914`, `_opus5_150k_w1_20260914`, plus the w1 + worker notes (`scratch/workers/game_engine.md`) and the waveC/w1 scratch candidates. +- Laws applied as detectors over every non-exact row and the park: `scratch/w2/laws_w2.md` (A1-A22, R1-R12) + and `scratch/w3/laws_w3.md` (A23-A45, R13-R19). +- Per-function worker blocks: `scratch/workers/w3c_game_engine.md`. Harness: + `scratch/workers/w3c_game_engine/probe.py`; standalone compiler lab `scratch/workers/w3c_game_engine/lab.py`. + +## Baseline and result + +`python -B tools/campaign/gate.py source/game/game_engine --all --forbid-emitted-symbol _point_from_line3d` + +```text +baseline == exact 174 residual 6 unwritten 0 (of 180 listed) guard passed +final == exact 174 residual 6 unwritten 0 (of 180 listed) guard passed +``` + +Row-by-row diff of the two `--all` listings, the only change: + +```text +- residual 1152 _game_engine_generate_title_string [size 1088!=1152, relocs 70!=73, sha] ++ residual 1152 _game_engine_generate_title_string [sha] +``` + +No new strict-exact row (**newly_exact is empty**). Park drift 0/1, owner census PASS (41 inherited +candidate-only names equal `build/base`, none new, none missing), `tools/fake_match_scan.py` 0 leads, +`git diff --check` clean. + +| Function | Outcome | Padded / relocs | +|---|---|---| +| `_game_engine_generate_title_string` | **FUZZY-LANDED** (zero credit, structural closure) | 1152 / 73 (target 1152 / 73) | +| `_drawline` | SKIPPED-EXHAUSTED (new mechanism recorded) | 112 / 7 | +| `_game_engine_post_rasterize_post_game` | NOT-LANDED (blocked on `_drawline`) | 2528 / 148 | +| `_game_engine_get_starting_location_rating` | SKIPPED-EXHAUSTED (EBX/EDI mirror) | 96 / 6 | +| `_default_starting_location_rate_function` | SKIPPED-EXHAUSTED (EBX/EDI mirror) | 176 / 10 | +| `_game_engine_should_spawn_player` (parked) | SKIPPED-EXHAUSTED (constant-register pinning) | 256 / 9 | + +## Accepted control: the title-string team buffers are two `wchar_t[8]` + +The previous body carried a hand-made storage union +(`union { wchar_t team_names[22]; struct postgame_statistic_entry entry; } branch_storage;` with +`&branch_storage.team_names[14]` for team 0) and per-arm `return`s. It was 1088/70 against a 1152/73 target +- 64 bytes and three call relocations short. The landed body removes the union entirely. + +January's frame facts (`alndiff`, target offsets): + +| Offset | Object | Evidence | +|---|---|---| +| `[ebp-0x330]` | `score_string[256]` | `lea eax,[ebp-0x330]` at 0x3d9 and 0x44b | +| `[ebp-0x130]` | `life_string[128]` | `lea eax,[ebp-0x130]` at 0xac/0xbd, terminator `mov word [ebp-0x32],0` | +| `[ebp-0x30]` | team 1 name buffer | `lea eax,[ebp-0x30]; push eax; push 1; call [ecx+0x54]` at 0x2d1 | +| `[ebp-0x20]` | `struct postgame_statistic_entry entry` (28 B) | 0x3ce, `rep movsd` ecx=7 at 0x3e8, 0x453 | +| `[ebp-0x14]` | team 0 name buffer | `lea edx,[ebp-0x14]; push edx; push 0; call [eax+0x54]` at 0x2c2 | +| `[ebp-4]` | `secondary_string` | stored at 0x22, reloaded at 0x31a/0x363/0x3aa/0x448 | + +Frame `sub esp,0x330` = 512 + 256 + 44 + 4, so the two team buffers plus the entry cell occupy exactly +44 bytes with the entry cell (28 B) overlapping them. Measured allocator behaviour for this block pair: + +- two **16-byte** buffers: the first one used is placed at `-0x14` (packed over the top of the + free-for-all `entry` cell) and the second below the entry cell at `-0x30` - **exactly January**; +- `[8]`+`[14]` and `[14]`+`[8]` (waveC G2a/G2b - declaration order is inert): the 28-byte buffer shares the + 28-byte `entry` cell at `-0x20` and the 16-byte buffer drops to `-0x30`, i.e. the two teams swap slots; +- `[14]`+`[14]` (waveC G2c): frame 0x33c. + +So `wchar_t team0_name[8]; wchar_t team1_name[8];` is the unique measured fit, and it is the natural +spelling: two same-sized buffers, one per team, each handed straight to `game_engine->format_team_name`. + +The control flow is waveC's G2i shape, which the SINGLE-EXIT LAW (brief section 9) and January's bytes +require: every postgame arm falls into a shared `ustrncpy` + `title_string[79] = 0` + epilogue tail that +VC7 tail-duplicates into each arm (the previous per-arm `return` body could not produce the three extra +`ustrncpy`/terminator copies, which is where the 64 bytes and 3 relocations were missing). HCEA +`game/game_engine_generate_title_string.c` uses the same `switch (did_win)` over the tri-state result. + +Result: 1152/73, `relocdiff --count-by-target` 73/73 with 0 differing rows, frame and every stack slot equal +to the target, **391/391 instructions with a single transposed pair**: + +```text +target 448 mov edx,[ebp-4] | 44b lea eax,[ebp-0x330] | 451 push edx | 452 push eax +ours 448 mov edx,[ebp-4] | 44b push edx | 44c lea eax,[ebp-0x330] | 452 push eax +``` + +That is the last free-for-all `usnprintf`. January emits `mov; push; lea; push` (our order) at both team +`usnprintf` sites (0x31a, 0x3aa); the free-for-all site is the only one whose next argument is a +register-ABI static call (`get_place_string(&entry)`, `lea eax,[ebp-0x20]; call`). A standalone lab TU +(`scratch/workers/w3c_game_engine/l2.c`, `l3.c`) reproduces our order and shows VC7 hoisting a different +argument-input load into the same `mov`/`push` gap, so the remaining difference is a gap-filling scheduler choice on +an identical DAG, not a structural or store-order fact. + +Landed under brief section 6 (structurally incomplete row, zero credit): the body is complete, natural, +strictly closer in every measure, and no sibling, park, owner or fake-scan status changes. + +## Experiment matrix (this wave) + +| ID | Function | Shape | Result | Decision | +|---|---|---|---|---| +| p1 | generate_title_string | G2i control flow + `team0_name[8]` / `team1_name[8]` | **1152/73, 391/391, 1 insn** | landed (as p2) | +| q1 | generate_title_string | p1 + `&score_string[0]` argument | identical | rejected | +| q3 | generate_title_string | p1 + swap `life_string`/`score_string` declaration order | identical (slots unchanged) | rejected | +| q4 | generate_title_string | p1 + `score_string` declared in the free-for-all block | identical (frame preserved) | rejected | +| p2 | generate_title_string | p1 minus a stray blank line | identical | **landed** | +| inc1 | whole unit | `#include "main/console.h"` (the C4013 at game_engine.c:3806) | all 180 rows identical | not landed (see below) | +| d1 lab | drawline | 12 ordinate spellings in one lab TU | 11 straight-line spellings identical to ours | rejected | +| l1-l4 lab | generate_title_string | free-for-all call shape in a standalone TU | reproduces our push order | diagnostic | + +## Residual classification + +- `_game_engine_generate_title_string`: argument-schedule tie (one transposed pair). Structural debt closed. +- `_drawline`: **new mechanism.** January computes `row*9` twice (`lea edx,[esi+esi*8]; shl edx,1` for `y0`, + then `lea eax,[esi+esi*8]; lea ecx,[eax+eax+0x1a]` for `y1`); ours keeps `row*18` alive in EAX and adds + 0x1a. Twelve arithmetic spellings compiled in one lab TU (`d1.c`: `(row+1)*18+8`, `row*18+26`, + `18*row+26`, `row*9*2`, `2*(row*9)+26`, named `short` ordinate, reversed statement order, + `(short)(row*18)+26`, `(short)((row+1)*18)+8`, `row*18+18+8`, `(row*2+2)*9+8`) all emit our bytes, so the + arithmetic spelling is not the lever. The real difference is where the `justification` argument load + (`mov edx,[ebp+0xc]`) is scheduled: January puts it *between* the two ordinate stores, which frees the + register holding `row*18` and forces the rematerialisation; ours issues it first, right after + `offset_rectangle2d` returns. Class: argument-load scheduling / rematerialisation tie. +- `_game_engine_post_rasterize_post_game`: six inlined `drawline` copies carry the same rematerialisation + gap, plus the EBX/EDI colouring of the hoisted 350/500 and 0/1 constants and the `entry_count` spill + order. Best body remains waveC P22 (`scratch/workers/game_engine/pr_p22re.c`, 2528/148, 751/752 + instructions, frame 0x670 and every slot equal); it is not strict exact and the row is `[sha]` only + (size and relocations already match), so section 6 does not admit a fuzzy landing. +- Rating pair: private-ABI mirror only - January keeps `player_index` in EBX, ours in EDI (plus one + `mov eax` vs `mov ecx` in the callee). 46/46 and 72/72 instructions, everything else equal. +- `_game_engine_should_spawn_player` (parked): January widens the boolean TRUE store to `mov ebx,1` and + then spends EBX as the 32-bit compare operand for `respawn_timer == 1`; ours writes `mov bl,1` and + rematerialises `cmp eax,1`. Recurring constant-register pinning; no w2/w3 law explains it. + +## C4013 census item (orchestrator lead) + +`game_engine.c(3806): warning C4013: 'console_printf' undefined`. The genuine owner header is +`source/main/console.h` (`void console_printf(boolean clear, const char *format, ...);`). The call sits in +`_game_engine_update_teleporter`, which is **already strict EXACT** with the implicit declaration, and the +measured include (`scratch/workers/w3c_game_engine/inc1.c`, `#include "main/console.h"` before +`"main/main.h"`) leaves **all 180 rows identical**. It therefore unlocks nothing and is not landed here; +it is available as a byte-inert hygiene hunk if the orchestrator wants the warning gone (laws_w3 E18 class). + +## Do-not-repeat (adds to the w1/waveC lists) + +- generate_title_string: q1 (`&score_string[0]`), q3 (buffer declaration order), q4 (free-for-all-block + `score_string`) - all byte-identical to p1; do not re-spin team-buffer sizes: `[8]`+`[8]` is the fit, + `[8]`+`[14]`, `[14]`+`[8]`, `[14]`+`[14]` and `team_names[22]` are measured. +- drawline: the twelve `d1.c` lab spellings; no arithmetic spelling defeats the CSE. +- rating pair, should_spawn: no shapes spent; the recorded ties are unchanged and no law fires. + +## Reopen criteria + +- `_game_engine_generate_title_string`: a measured VC7 control that moves the `lea` of a stack array above + the `push` of a spilled pointer when the next argument is a register-ABI call. Apply it to + `scratch/workers/w3c_game_engine/p2.defs`; everything else is already byte-identical. +- `_drawline` / `_game_engine_post_rasterize_post_game`: a control that delays a stack-parameter argument + load past a store to an escaped local (freeing the ordinate register). Close `_drawline` first, then + re-gate P22. +- Rating pair: a callee register-pressure lever that moves an incoming private-ABI parameter from EDI to EBX. +- `_game_engine_should_spawn_player`: a measured control where a boolean TRUE store is materialised as a + 32-bit constant register reused by a later int compare. + +## Disposition + +Real file stays 174/180 strict exact (no credited bytes this wave). `_game_engine_generate_title_string` is +now structurally complete (size, relocations, frame, slots and 390 of 391 instructions equal to January) and +free of the storage union; the object stays `NonMatching` and active. diff --git a/docs/object_matching_logs/opus5_250k_house_clean_checkpoint_20260915.md b/docs/object_matching_logs/opus5_250k_house_clean_checkpoint_20260915.md new file mode 100644 index 00000000..f7441102 --- /dev/null +++ b/docs/object_matching_logs/opus5_250k_house_clean_checkpoint_20260915.md @@ -0,0 +1,73 @@ +# Opus5 250K house-clean lane: checkpoint ledger (2026-09-15) + +Continuation of `opus/150k-house-clean-20260914` (HEAD `0483bde89`, +89,085 strict meaningful) on branch +`opus/250k-house-clean-20260915`, in the same worktree `C:\halo-worktrees\opus5-150k-house-clean-20260914`. +Lane base snapshot: `scratch/claude150k-before.json` (commit `12f7375d4`, 7,149 functions / 1,244,970 meaningful). +Target: +250,000 strict meaningful (`scratch/lane_manifest.py`: `TARGET = 1494970`, remaining computed against 250,000). + +## 1. Startup proof (2026-09-15, at 0483bde89) + +| check | expected | observed | +| --- | --- | --- | +| tracked tree / HEAD | clean / 0483bde89 | clean / 0483bde89 | +| `ninja` (incl. parked-function check) | pass | pass; "Validated parked compiler ties: 280" | +| stable diff `claude250k-start.json` vs `claude150k-before.json` | gained 144 90112; regressions 0; 8,245 / 7,263 | gained 144 90112 bytes; regressions 0; 8,245 functions, 7,263 exact | +| `tools.parked_functions` | 280 / 0 / 0 | 280 active / 0 stale / 0 invalid | +| `tools.audit_object_admission` | 0 contradicted / 7 rejected / 7 candidates | 0 / 7 / 7 | +| `pytest --basetemp scratch/pytest-250k-start` | 1151 passed, 5 skipped | 1151 passed, 5 skipped, 26 subtests passed | +| `lane_manifest.py` strict_new_meaningful | 89,085 | 89,085 (144 fns, 90,112 padded; 4 non-strict accepted / 2,195 excluded) | + +## 2. Owner rulings (2026-09-15, asked at startup; strategy step A) + +The owner took the strict option on every held item. Nothing below is landed or credited: + +- collision_bsp `_collision_surface_test_sphere` (880; unit-local SSE `__asm` distance macro, p1.c): leave parked. +- env_fog 21-static .bss split (+800) and models 13-static .bss split (+1,296): neither lands. +- editor_flying_camera `is_scripted` linkage-anchor split (304; the w3 ledger says the split alone would also unlock + `_editor_camera_flying_update` 1,408 and `_editor_camera_set_scripted` 432): held. +- The 7 whole-object admission candidates: not admitted. P7 `projection_from_vector3d`: stays reverted, no worker spend. + +These are recorded in `scratch/WORKER_BRIEF.md` section 9b as final for this lane. + +## 3. Pool at HEAD (scratch/gate4, regenerated 2026-09-15) + +`gate.py --all --forbid-emitted-symbol _point_from_line3d` over the 151 units with non-exact rows (no guard for +action_charge / ai_debug / real_math): 239,536 residual + 192,176 parked + 6,088 unwritten padded bytes. The 90 units +that never got a w3 pass hold 110,832 residual + 100,768 parked + 3,144 unwritten. After removing protected units, +`rasterizer_xbox_transparent_geometry`, do-not-spend, owner-declined/standing holds and laws_w2 D do-not-repeat rows, +`scratch/build_wave3c_groups.py` leaves 157,832 spendable padded bytes in 53 groups / 81 units. + +## 4. Strategy step C: C4013 census (zero credit, analysis only) + +`CL /Zs /W3` over all 468 source units (`scratch/gate4/c4013.txt`, 112 C4013 + 23 C4133 lines; +`scratch/gate4/c4013_map.json` maps each site to its caller and gate status). Outside libtiff, only 7 sites are inside +non-exact functions (7,392 padded): rasterizer_xbox_models `_rasterizer_model_draw_environment_shader` (park), +action_vehicle `_action_vehicle_evaluate_seat`, light_volumes `_light_volume_render` (park), hud_weapon +`_render_grenade_hud` (`weapon_prevents_grenade_throwing` has no owner prototype), ai_communication +`_ai_communication_update_speech_timers` (park), action_uncover `_action_uncover_perform` (park), main +`_main_game_render` (park). All 7 were already in the w3 census; the un-run units carry them in their w3c group notes. +The other sites are in functions that are already exact (a prototype there is a regression risk, not a lever). + +## 5. Strategy step D: static-linkage census (negative) + +`scratch/gate4/static_census.py`: 53 external data symbols in January split objects with no January PDB public and no +cross-object relocation, referenced by at least one non-exact function at HEAD (74,880 B of referencing rows, double +counted). A scratch csplit with 50 of them `"static": true` (placeholder `_data_`/`_bss_` names excluded) changed +only the 33 owner objects (`scratch/static_probe/changed_objects.json`). + +- Config only (HEAD objects vs the static split): 0 rows changed in all 33 units. +- C `static` added to the definition (tentative COMMON becomes a defined .bss symbol; storage 3 verified in the + candidate objects) with the matching static split: **0 rows changed** in sound_manager, virtual_keyboard, + stack_walk_windows, actor_perception, weather_particle_systems, rasterizer_xbox_active_camouflage, draw_string, + dead_camera, render_particles, props, shader_transparent_chicago_preprocessor, debug_memory, key_agreement, + bitmap_utilities (both tables), rasterizer_transparent_geometry (5 globals), rasterizer_xbox. +- game_engine_king `king_globals` static with the (single-consumer) header extern removed: gains nothing and loses + `_find_next_hill` (112) and `_king_engine_initialize_for_new_map` (272). Same as the w2 static probe. +- Already `static` in C (config flip only, byte-inert): collision_debug x2, rasterizer_lights x4, player_effects x2, + profile_globals, dsound_error_string. +- Not probed: `extern` layout-anchor declarations inside their own .c (ui_widget_globals_storage, + dsound_effects_image, bink_playback x3, xbox_sound_cache_globals); editor_camera_* (owner-declined split); the + header-extern globals lightning, light_volumes, race (w2 measured them as no effect), director x3 (rows are + R3/A17 ruling-blocked) and game_globals (`_game_load` 208). + +Conclusion: at HEAD the static-linkage law has no remaining closures for these residuals. diff --git a/docs/object_matching_logs/rasterizer_frame_statistics_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/rasterizer_frame_statistics_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..e0339f27 --- /dev/null +++ b/docs/object_matching_logs/rasterizer_frame_statistics_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,143 @@ +# `rasterizer_frame_statistics.obj` — opus5 250K lane, wave w3c (2026-09-15) + +## Verdict + +**No production source change; zero new strict bytes.** All three non-exact rows were re-examined with the +w2/w3 law set as detectors. Two are closed with law-based refutations of their only remaining candidate +shapes; the third (`count_static_vertices`) advanced from "unclassified callee-save schedule" to a fully +decoded two-clause mechanism with two scratch bodies that sit one hunk from exact, but no house-rule-admissible +spelling reaches it. `git diff --stat -- source/rasterizer/rasterizer_frame_statistics.c` is empty. + +## Baseline and final gate + +`python -B tools/campaign/gate.py source/rasterizer/rasterizer_frame_statistics --all --forbid-emitted-symbol _point_from_line3d` + +| Function | Target | Candidate | Tag | Wave outcome | +| --- | ---: | ---: | --- | --- | +| `_eat_my_shorts` (parked) | 32 / 0 | 16 / 0 | size, sha | SKIPPED-EXHAUSTED (A42 refutes the last shape) | +| `_rasterizer_frame_statistics_count_static_vertices` (parked) | 64 / 0 | 64 / 0 | sha | NOT-LANDED (mechanism decoded) | +| `_rasterizer_frame_statistics_draw` | 4176 / 322 | 4176 / 322 | sha | SKIPPED-EXHAUSTED (C2 allocator tie) | + +Baseline and final are both `== exact 7 residual 3 unwritten 0`, guard passed. `tinfo` confirms the object has +exactly these 10 functions, none unwritten and none a `_code_` placeholder, so this wave had no callee-first +work available. `scratch/parkcheck.py` reports parks 2 / drift 0; `tools/fake_match_scan.py` 0 leads; owner +census unchanged (no source change). Inherited exact and never re-touched: +`_rasterizer_frame_statistics_get_fps` (100K lane, 384/21). + +Evidence read: all 10 prior object ledgers, `scratch/workers/rasterizer_frame_statistics.md` (w1), +`scratch/ghidra/out/rasterizer_frame_statistics.obj.decomp.c`, `scratch/atlas/rasterizer_frame_statistics.obj.txt`, +HCEA `blam/rasterizer/rasterizer_frame_statistics_count_static_vertices.c`, `scratch/gate4/c4013.txt`. +Two evidence cautions recorded for later waves: (1) the atlas records for this object are Halo-CE map entries +(`@rasterizer_frame_statistics_count_static_vertices@8`) — the `@N` decoration is the CE build's fastcall and +must not be read as a January calling convention, whose bytes are plain cdecl `[ebp+8]`/`[ebp+0xc]`; +(2) the HCEA body is a retail stub (`return 0;`) and carries no January topology. + +## 1. `_rasterizer_frame_statistics_count_static_vertices` — mechanism decoded, still parked + +January's complete stream (decoded this wave): + +``` +0 push ebp 14 mov cx,[edx] 26 test cx,cx +1 mov ebp,esp 17 cmp cx,1 29 jne 0x2e +3 mov edx,[ebp+8] 1b jne 0x26 2b mov eax,[esi+4] +6 xor eax,eax 1d mov eax,[edx+4] 2e pop esi +8 test edx,edx 20 add eax,2 2f nop +a je 0x30 23 pop esi 30 pop ebp +c push esi 24 pop ebp 31 ret +d mov esi,[ebp+0xc] 25 ret +10 test esi,esi +12 je 0x2e +``` + +January shrink-wraps `push esi` past the `triangle_buffer` test and splits ONE epilogue into two entry points +(0x2e for the paths that pushed esi, 0x30 for the `triangle_buffer == NULL` path); the `nop` at 0x2f is +jump-target alignment for 0x30. Detectors that confirm the current source is right in every other respect: +A42 (the entry `xor eax,eax` before the first test proves the declaration initializer `long vertex_count = 0;`), +A18/A34 (`mov cx,[edx]`, `cmp cx,1`, `test cx,cx` prove the 16-bit `type` field and the strip-then-triangles +order). Ghidra's `return *(int *)(param_1 + 2) + 2;` is the duplicated epilogue at 0x23, not a source return. + +Shapes measured in scratch (`scratch/workers/w3c_rasterizer_frame_statistics/`, all gated with `--source`): + +| Shape | Form | Result | +| --- | --- | --- | +| t1 | `if (!triangle_buffer) { return vertex_count; }` then `if (vertex_buffer) {...}` | 64/0; **push esi sinks exactly like January**, entry eax=0 reused (no remat); early exit laid out inline at 0xc (`jne 0xe; pop ebp; ret`) instead of jumping to the bottom tail — 1 hunk | +| t2 | two separate early returns | 64/0; two inline exit blocks, worse | +| t3 | nested `if (tb) { if (vb) {...} }` | v0 fixed point (push esi at 0xa) — re-confirms the park note | +| t4 | nested + `return vertex_count;` at the end of the tb block + final `return vertex_count;` | **22 of 23 code instructions identical incl. `je`→bottom and `push esi` at 0xc**; the bottom block rematerialises `xor eax,eax` instead of sharing January's tail — 1 hunk (control only: double return) | +| t5 | `if (!tb \|\| !vb) { return vertex_count; }` + flat if/else-if | sink lost, separate `xor eax,eax; pop esi` tail, worse | +| t6 | t4 with the inner return inside the `vertex_buffer` block | sink lost (vb-null path reaches the bottom with esi pushed), worse | + +**Candidate law A46 (two clauses, measured here):** +(a) VC7 sinks a callee-save `push` past the first test only when that test's exit is a *separate return +statement* whose block never touches the register; with `&&` or a nested guard the exit is the shared epilogue +that pops the register, so the push can only sink to the end of the entry block. +(b) A return statement laid out at the BOTTOM whose value is a compile-time constant on that path +rematerialises the constant in its exit block; a return laid out immediately after the definition reuses the +entry definition. + +January needs (a) while keeping the entry `xor eax,eax` live to 0x30 — one exit edge that is separate for +register-save purposes but is not a second `return` statement in the source. t1 satisfies the value half, +t4 satisfies the placement half, and nothing admissible satisfies both: the only bottom-placed forms are +double returns (R1/R12 class), and the park text already records the rejection of a same-size topology change +made "solely to steer code generation" (R13 binds it). + +**Reopen criterion (sharpened, replaces "explain the delayed ESI save"):** an authentic January spelling that +gives the `triangle_buffer`-null path its own register-free exit edge without a second `return` statement +(January source/local records, or an authenticated guard idiom), or evidence that this TU's C2 state rather +than its source picks the sink. Re-measure `t1.c` and `t4.c` first — each is one hunk from exact. + +## 2. `_eat_my_shorts` — exhausted, and the last candidate shape is now positively refuted + +January: `mov ax,[ebp+8]; cmp ax,[ebp+0xc]; jbe 0x11; mov al,1; pop ebp; ret; 0x11: xor al,al; pop ebp; ret`. +`jbe` proves unsigned `word` operands and the `first > second` polarity with TRUE as the fall-through (the +current source is correct); the 8-bit constants prove the `boolean` return. VC7 if-converts every natural +spelling to `seta` (16 B). + +The only 32-byte shape ever produced, w1's `boolean result = FALSE; if (first > second) result = TRUE; +return result;`, puts the zero at the entry and emits a single epilogue. **A42 refutes it from January's own +bytes**: VC7 does not hoist a constant into the entry, so January's `xor al,al` sitting in the branch target +proves there is no initializer at all. A38 gives the same conclusion (an initializer declared in the enclosing +block is emitted between the compare and the jcc). A3/A29 (single exit), A10 (conditional initializer), +A23/A39/A40 (loop/switch/inline-helper levers) and A43 (tail duplication has no source lever) do not fire — +this function has no call, loop, switch or helper. w1's k=1..46 declaration sweep already showed the row is +not TU-state dependent. The reachable natural set is {16 B `seta`, 16/32 B `sbb/neg`, 32 B entry-zero +single-epilogue}; January's form is in none of them. + +Reopen criterion unchanged: authoritative January source/local records, or a natural same-compiler context +that suppresses the setcc if-conversion while keeping both epilogues and the late `xor al,al`. + +## 3. `_rasterizer_frame_statistics_draw` — C2 allocator tie, donor match proven unreproducible + +The residual is 3 sites / 6 instruction rows, all in the opening aggregate sums: 0x6e/0x7e/0x8d (statistics +`+0xB0`/`+0xA0`), 0xca/0xd3/0xd9 (`+0xA8`/`+0xB4`) and 0x10f/0x117/0x197 plus 0x19a/0x1a0/0x1a6/0x1a8/0x1b1 +(`+0xE4`/`+0xD8`, `+0xDC`/`+0xE8`, `+0xE0`). In every case both builds issue the same two loads in the same two +instruction slots and the same semantic operand order in the following `lea`/`add`; only the physical register +each load lands in is swapped (ecx<->eax, edx<->ecx), which renames the destination of the `lea`/`add` and one +store. The other 1,172 instructions are identical, and the remaining alndiff rows are `symbol:` versus +`defined-noncode:.rdata:` descriptions of the same literal owners (a `--source` gating artifact). + +Detectors run, all negative: A35 (leaf age / dot canonicalisation is x87; these are integer register +assignments), A12 (not a loop preheader), A20/A44 (no stores or calls in the hunks), A24 (no parameters), +A6 (frame identical — the `[ebp-0x24]` store matches), A11/A36 (no float constant), A7 (this TU has no implicit +declaration; the only frame-statistics C4013 in the whole build is `rasterizer_xbox_models.c(1723)`, which is +packet E4 and unlocks 0 B). + +New evidence: the historical reconciled donor body (blob `6dac0962`, commit `5d3444f79`) gates +`exact 6 residual 4` at this HEAD — i.e. **the donor draw is residual now** — and the original donor blob +`4168c4a3` (commit `7841c6ac3`, the "exact draw") no longer compiles against the shared owner header. The +20260906 hold's donor match is unreproducible in any form at this HEAD, which confirms it was TU/C2 state and +not source topology, and closes the "import the donor source" avenue for good. + +Classification: C2 register-allocation tie, definition-position driven (C2 POGO attribution: the allocator +record carries no spill-cost field; zero-register pinning residual). The only known lever is declaration +count/order, forbidden by the 20260906 hold and measured by w1 as a lottery with no k in 0..48 reaching zero +hunks. Reopen only with an authentic January include/declaration environment for this TU (a preprocessed +listing or the original header set) or a January source record — and re-measure whenever a predecessor body in +this TU changes, because the pair lottery is coupled to them. + +## 4. Proposals + +No header or config change is requested by this wave. (`E4` — putting `rasterizer_xbox_models.c` on this +object's owner header — remains an owner decision worth 0 B and is not re-proposed.) Candidate law A46 above is +offered for `scratch/w3/laws_w3.md`'s successor; it is measured only on this function and should be narrowed +accordingly. diff --git a/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..d7555d6f --- /dev/null +++ b/docs/object_matching_logs/rasterizer_xbox_environment_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,142 @@ +# `rasterizer_xbox_environment.obj`: opus5 250K house-clean lane, wave w3c (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 the byte authority). +- Lane `opus/250k-house-clean-20260915` (continues `opus/150k-house-clean-20260914`). Worker-only measurements with + `tools/campaign/gate.py`, `tools.campaign.alndiff`, `tools.campaign.tinfo`, `scratch/parkcheck.py`, + `tools/fake_match_scan.py`. No ninja, 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` (wave B), + `_opus5_100k_waveC_20260914`, `_opus5_150k_w1_20260914`; worker notes + `scratch/workers/rasterizer_xbox_environment.md`; the w1 verifier verdict in `scratch/w1/w1-results.json`. +- Laws applied as detectors: WORKER_BRIEF section 9, `scratch/w2/laws_w2.md` (A1-A22, R1-R12, D) and + `scratch/w3/laws_w3.md` (A23-A45, R13-R19, C, D). +- Worker notes for this wave: `scratch/workers/w3c_rasterizer_xbox_environment.md`; + scratch dir `scratch/workers/w3c_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** - point guard passed, `scratch/parkcheck.py` 1 park drift 0, +`fake_match_scan.py` 0 leads, `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 | + +Context: `__rasterizer_environment_diffuse_light_begin` (w1 park reopen) is EXACT at HEAD and +`_IDirect3DDevice8_SetTexture@12` is inherited exact; neither was revisited. The w1 `lightmap_draw` landing was +rejected by its verifier and reverted, so the row is back at the 100K baseline. + +## `__rasterizer_environment_lightmap_draw` - SKIPPED-EXHAUSTED (0 shapes spent) + +**What is left.** Against the wave-B candidate (`scratch/workers/rasterizer_xbox_environment/v0.obj`, 4000/248) the +entire remaining 16-byte / 2-relocation gap is the `t` argument of the two `_point_from_line3d` calls: + +``` +January @b88 fld dword [ebp-4] ; add esp,0xc ; lea eax,[ebp-0x5c] ; push eax ; push ecx ; + lea ecx,[edi+0x19c] ; fstp dword [esp] ; push ecx ; lea edx,[ebp-0x5c] ; push edx ; call +ours @b88 mov ecx,[ebp-4] ; lea eax,[ebp-0x5c] ; push eax ; push ecx ; ... (and one merged add esp,0x44) +``` + +Everything else in the self-illumination block already matches byte for byte: the three +`periodic_function_evaluate` argument stages, both `fld __real@3f800000; fsub [ebp-N]` scale arguments, both plasma +`push 0x3f800000` constants, and - decisively - the **integer** push `mov eax,[ebp-0x50]` of `plasma_value` into +`real_alpha_to_pixel32` in the same function. Same declaration class, same block, integer-copied: the trigger is not +the declaration of the local. + +**New evidence gathered this wave.** + +1. *January-wide census* (re-using the 100K lane's `fldscan.py` over every `build/split` object): of the 71 pure + `fld dword [mem]` -> `fstp dword [esp]` argument stages in the whole January image, only three read a STACK slot - + these two sites and `_ai_test_line_of_fire @70` (`[ebp+eax-0x4e4]`, an indexed stack array element). The other 68 + stage a pointer field (`[reg+off]`). `lightmap_draw` is therefore a member of the unresolved x87-staged-real-argument + family recorded in `laws_w3` C3 (`_object_test_vector`, `_player_examine_nearby_device`, `_ai_test_line_of_fire`, + `_biped_update_moving`), not an isolated puzzle. The family's only known reproduction - a parenthesised argument + produced by a genuine macro - is measured INERT on a stack-local leaf (w1 p1 = 4000), so the `_object_test_vector` + form does not transfer here. +2. *Donor re-check.* Ghidra `scratch/ghidra/out/rasterizer_xbox_environment.obj.decomp.c:1416-1430` shows a bare local + copy for the `t` argument (`local_264 = (undefined4 *)local_8;`). HCEA's later-build + `_rasterizer_environment_lightmap_draw_pp.c:156-213` computes `on*blend + off*(1-blend)` componentwise with **no** + intensity term and copies the plasma colours directly. Both corroborate the w1 verifier's rejection of the constant + factor; neither satisfies the reopen criterion. The punpckhdq and Stian trees contain no body for this object. + +**Detectors run, none fires.** A23 (no loop), A24 (frame 0x218 and all three value slots already identical), +A25/A29 (single return, no join store), A31(a) (the `add esp` flush positions already match), A33 (block scoping +without the factor measured 4000: w1 f2/g2n), A35 (not a commutative product), A36 (the constant is exactly 1.0f), +A38 (inner-block declare+init temporaries measured 4000: w1 f2), A42 (no store to prove). + +**Why no shape was spent.** Every exact form on record (w1 c1/e2/f1/g2 and waveC k16/k17/k22) requires the constant +1.0 identity factor or an explicit cast on a c2-folded expression. The w1 verifier rejected that tree as fake +arithmetic and required a full revert including the wave-B hunks; R13 binds that rejection, and no w2/w3 law or donor +supplies a genuine intensity/brightness term or a double intermediate. Wave B's four structural corrections alone +reach only 4000/248 and cannot land for an already-written residual (brief section 6). + +**Classification.** Unresolved source form (x87 argument staging), not a register tie. + +**Reopen criterion.** Unchanged from w1 - 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. Start from `scratch/workers/rasterizer_xbox_environment/v0.c` (4000/248). + +## `_rasterizer_environment_specular_spot_light_begin` - NOT LANDED (park locked; 1 shape, 2 refutations) + +w1 closed the structural half in scratch: the frame is a `real_matrix4x3 matrix` whose `scale` (-0x34) and `position` +(-0xc) are never written (forward -0x30, left -0x24, up -0x18), and `s3a.c` +(`matrix.forward = light->forward; matrix.up = light->up; normalize3d(cross_product3d(&light->forward, &light->up, +&matrix.left));`) reaches 960/64 with frame parity and 15 small blocks left. + +**Shape p1 (new, evidence-led, refuted).** `source/math/matrix_math.c:337-346` shows the house idiom for building a +basis - `matrix->forward = *forward; cross_product3d(...); matrix->up = *up;` - i.e. the up copy AFTER the cross. +Under A35 that should make `up` the younger leaf and flip the j/k operand order towards January's. +Measured (`scratch/workers/w3c_rasterizer_xbox_environment/p1.c`, `p1.obj`): 960 [sha], but structurally worse than +s3a - the i-term flips to forward-first (`fld [esi+0x14]`), the up copy is pushed past the cross and loses January's +`lea ecx,[esi+0x1c]; mov edx,ecx` twin-pointer form, and the `push ebx` prologue register disappears. This **refutes** +the matrix_math ordering for this function: January's interleaved twin-pointer copies require both copies before the +cross, exactly as s3a spells it. + +**Two controls that pin the source form (no shape spent).** + +- Re-reading w1's `s3b.obj`: taking the cross operands from the matrix members makes VC7 read the LOCAL slots + (`fld [ebp-0x10]; fmul [ebp-0x2c]`), whereas January reads light-relative addresses + (`[eax+..]`, `[ecx+..]`, `[esi+0x20/0x24]`). VC7 does not forward the struct copy, so January's cross arguments are + `&light->forward, &light->up`; every mixed spelling (`&matrix.forward, &light->up` and friends) is excluded by the + same address evidence. +- Sibling control inside this object: the EXACT `__rasterizer_environment_diffuse_light_begin` inlines the same + `real_math.h` `cross_product3d` from local matrix members, and January loads operand b (up) first in **all six** + products. The spot light loads b first in 5 of 6 (the exception is the i-term subtrahend, whose up operand is read + through the light base ESI before `lea eax` exists); s3a loads b first in only 2 of 6. The order tracks leaf age and + pointer availability inside the shared inline; this TU cannot change it without moving the operand addresses away + from January's. + +**Remaining 15 blocks in s3a:** four cross-product `fld`/`fmul` operand orders, one eax/ecx/edx rotation in the +`light->position` copy, one `fxch st(1)` before the last vertex constant, and the `rasterizer_set_pixel_shader` push +placement - the last three all downstream of the operand order (the two x87 stack residents at the tail are +`cone_scale` and `radius*0.5f`, swapped only because of the earlier product order). + +**Classification.** x87 leaf-age / allocator tie inside the shared `real_math.h cross_product3d` inline; the source +spelling is pinned by January's addresses; no TU-local lever remains. Do not re-spend on cross-operand permutations +(9 shapes total across w1 and w3c). + +**Reopen criterion (refined; supersedes w1's).** w1 asked for "a natural cross-product spelling that loads up.i/up.j +first in the j/k terms"; this wave shows no such spelling exists from this TU. Reopen only on (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 k,j,i body is REQUIRED by the exact +sibling `__rasterizer_environment_diffuse_light_begin`, so any such change must keep that row exact. Best scratch body +remains `scratch/workers/rasterizer_xbox_environment/s3a.c` (960/64, frame 0x84, sha16 9c8a4202366a9d91). + +## Rejected / do-not-repeat (added by this wave) + +- The matrix_math.c copy/cross/copy ordering in the spot light (p1): structurally worse; both copies precede the cross. +- Cross operands read from the matrix members or from a mixed member/pointer pair: wrong operand addresses (s3b proof + that VC7 does not forward the struct copy). +- Re-proposing the constant `intensity = 1.0f` factor (or any identity/cast wrapper) in `lightmap_draw`: rejected by + the w1 verifier, corroborated as absent by HCEA and Ghidra; R13 binds. + +## Proposals + +None. No header, config, symbols.json or parked.json need arises from this wave; the park stays locked and the real +file is unchanged. diff --git a/docs/object_matching_logs/sound_manager_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/sound_manager_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..c48c5866 --- /dev/null +++ b/docs/object_matching_logs/sound_manager_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,197 @@ +# `sound_manager.obj`: opus5 250K house-clean lane, wave w3c (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/250k-house-clean-20260915`, + HEAD `0483bde89`. Compiler XDK VC7 13.00.9254 with the repository flags; no flag was 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/w3c_sound_manager.md`; candidates and dumps in + `scratch/workers/w3c_sound_manager/` (`base.obj`, `d1.c/d2.c`, `rl1.c`..`rl3.c` with objects, + `rl_target.txt`, `rl_ours.txt`). +- Evidence: January split-object bytes, relocations and section order (authoritative); HCEA + `cache/sound_dispose_from_old_map.c`, `cache/sound_refresh_looping.c`, + `game/update_channel_for_impulse_sound.c` (later build; semantics and corroboration only); + `config/parked.json` park entries; all ten earlier `sound_manager_obj_*` ledgers and + `scratch/workers/sound_manager.md`; `scratch/w2/laws_w2.md` and `scratch/w3/laws_w3.md` applied as detectors. + +## 2. Baseline (unchanged at the end of the wave) + +`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)`; the guard passed; `scratch/parkcheck.py` +reports `parks 5 drift 0`; `tools/fake_match_scan.py` reports 0 review leads. + +| Function | Target padded / relocs | Base | Tag | Park | +|---|---:|---|---|---| +| `_refresh_listener` | 496 / 25 | 496 / 25 | sha | - | +| `_sound_dispose_from_old_map` | 240 / 21 | 272 / 23 | size, relocs, sha | - | +| `_sound_find_like_channel` | 272 / 16 | 272 / 16 | sha | P | +| `_sound_refresh_looping` | 1136 / 43 | 1088 / 42 | size, relocs, sha | P | +| `_sound_set_definition_end` | 208 / 9 | 208 / 9 | sha | P | +| `_update_channel_for_impulse_sound` | 528 / 29 | 528 / 29 | sha | P | +| `_update_channel_for_looping_sound` | 1360 / 61 | 1376 / 61 | size, sha | P | + +## 3. Result + +Nothing landed. Two of the seven rows are unparked and both are measured ties or provably spelling-inert; three +parks are ties whose known levers are already measured; two parks are blocked on one owner ruling. The wave's +substantive result is on `_sound_refresh_looping`: a new, byte-proven structural finding that fixes the park's own +recorded blocker (the missing relocation) in scratch and sharpens the residual to a single register-colouring fact. + +## 4. `_sound_refresh_looping` - new structural finding (scratch only, park untouched) + +### 4.1 The missing relocation is a source fact, not a merge accident + +`relocdiff --count-by-target` shows January with **four** `_sound_start_fade` relocations and the parked body with +three. The park entry itself describes this as "a compiler-merged duplicate fade-call tail". The merge is visible in +our object: the alternate-fade call site ends `push ecx; jmp 0x3bf` and lands inside the fade-in site's push +sequence. Its cause is the parked source's `real fade_in_time;` local: an if / else-if / else phi that is homed at +`[ebp-0x18]` (the extra frame cell - frame 0x18 against January's 0x14) and lets both arms share one call. + +January's bytes give the authentic shape directly (0x2d2-0x305): + +``` +2d2 cmp word ptr [ebp+0x14], 0 ; refresh_state != _looping_sound_refresh_start +2d7 jne 0x2e7 ; -> inaudible arm +2d9 test byte ptr [edi], 1 ; TEST_FLAG(track->flags, _fade_in_at_start_bit) +2dc je 0x2fb ; -> no fade, straight to the store +2de mov ecx, [edi+8] ; track->fade_in_duration +2e1 push -1 / push esi / push ecx / jmp 0x2f1 +2e7 mov edx, [_sound_inaudible_fade_out_time] +2ed push -1 / push esi / push edx +2f1 push 0 / call _sound_start_fade +2fb mov [ebx+eax*4+0xd4], esi ; *playing_sound_index = new_sound_index +``` + +Each arm builds its own argument triple and the two merge only at `push 0; call`. That is two source-level calls, +no `fade_in_time` local and no `continue`; the store is the common statement after the if/else-if. +HCEA's later build refactored this into one call with a `fade` local, so HCEA is not evidence here - January's +relocation count is. + +Rewriting the block that way (`scratch/workers/w3c_sound_manager/rl1.c`) gives **1120 / 43 / +`035c62df5f0f973d`** from 1088 / 42: the relocation identity is now exact (43/43, per-target counts equal) and the +frame is exactly January's 0x14. All 58 exact siblings are unchanged; only this park drifts; fakescan is clean. + +### 4.2 Exit decode: the remaining 16 bytes are one colouring fact + +January has four epilogues: + +| Address | Form | Reached from | +|---|---|---| +| 0x134 | `mov al,1` | the `component_sound_count == 0` datum_delete path | +| 0x448 | `xor al,al` | the `looping_sound_new` failure **and** the fall-through from the bottom of the enabled block | +| 0x451 | `mov al,cl` (`mov cl,1` hoisted at 0x84, before the found test) | the stop + not-found path | +| 0x45a | `mov al,[ebp-1]` | both disabled tests (`initialized`, `enabled`) | + +`[ebp-1]` carries `result` at entry (`sete byte [ebp-1]` at 0x10) and is reused for `new_looping_sound` +(`mov byte [ebp-1],0` at 0x80, `,1` at 0xa5), so `result` is dead inside the enabled block - only possible for +locals in different nested blocks (laws_w2 A6 side law). HCEA's tail `return 0;` corroborates that the bottom of +the enabled block is an explicit `return FALSE;`, which is what shares the `xor al,al` epilogue with the +new-failed return; and the register-carried TRUE at 0x451 (rather than a second `mov al,1`) indicates a returned +variable constant-folded on that path. + +Our build colours `result` into `BL` instead of spilling it. That single decision produces every remaining +difference: `source` is pushed out of EBX into ESI, ESI is then re-used for `definition_index` so `[ebp+0x10]` has +to be reloaded before `looping_sound_new` (January pushes EBX at 0x93), and every `return` of a register-carried +boolean merges with the disabled-path `mov al,bl`, collapsing January's four epilogues into three (the 16 bytes). + +### 4.3 Shapes measured this wave (5, the whole budget) + +| id | shape | size / relocs | verdict | +|---|---|---|---| +| rl1 | two separate `sound_start_fade` calls, no `fade_in_time` local, no `continue`, common store after | 1120 / 43 | best; relocs + frame exact | +| rl2 | rl1 + early `return result` + bottom `return FALSE` | 1104 / 43 | epilogues merge further | +| rl2a | rl1 + early `return result` only | 1104 / 43 | merges with the disabled-path exit | +| rl2b | rl1 + bottom `return FALSE` only (HCEA-corroborated) | 1104 / 43 | merges with the early FALSE exit | +| rl3 | rl2 + split declaration/assignment of `result` (A38 stack-home probe) | 1104 / 43 | inert | + +### 4.4 Reopen criterion (replaces the w1 criterion) + +A source-level fact that gives `result` a frame home shared with `new_looping_sound` at `[ebp-1]` - that is, +`sete byte [ebp-1]` at entry with EBX left to the `source` parameter - applied on top of `rl1.c` together with +rl2's exits (`return result` in the stop + not-found arm, `return FALSE` at the bottom of the enabled block). +Everything else in `rl1.c` is byte-proven. Do not re-spend the five shapes above, the waveC R1-R10 fade/stop-arm +variants, or the w1 S1-S8 return-materialisation variants. + +### 4.5 Proposal (orchestrator; zero strict credit) + +Re-baseline the `config/parked.json` entry for `_sound_refresh_looping` from `1088 / 42 / 4820b25c...` to +`rl1.c`'s `1120 / 43 / 035c62df...` and land the `rl1.c` body with that edit. It is strictly closer (size +32 +toward 1136, relocation identity exact, frame exact), it resolves the mechanism the park entry itself names, and +it deletes an unnecessary local rather than adding anything. This is not the R11 pattern: it is strictly closer +and it is tied to the sharpened reopen criterion above. + +## 5. The other six rows + +### `_sound_dispose_from_old_map` (active residual, 240 / 21 vs 272 / 23) + +Target and candidate are byte-identical through 0xa6, including the entire ten-instruction wait-loop test +(`call _system_milliseconds; test eax,eax; mov [ebp-8],eax; fild; jge; fadd __real@4f800000; fcomp [ebp-4]; +fnstsw; test ah,5; jp exit`). January closes the loop with one `jmp 0x82` back to that test; our build duplicates +the whole test into the latch (`jnp 0xa1`), which is the +12 instructions, +32 padded bytes and +2 relocations +(one `_system_milliseconds`, one `__real@4f800000`). The same function shows January rotating the preceding fade +loop into a guarded do-while, so the decision is per loop, not per function. HCEA spells the wait loop exactly as +we do. + +New negatives this wave: `for (; cond; ) { sound_idle(); }` (d1) and the A23 for-increment probe +`for (; cond; sound_idle()) { }` (d2) are both byte-identical to the baseline. Together with the four earlier +lanes (while, while + continue, `while (TRUE)` + real local, `for(;;)` + break, inline deadline, HCEA `any_sounds` +flag, captured `paused`, definition position, whole-TU compile-order reorder) this proves the loop **spelling** is +not the lever; the header duplication is decided after the IL. Reopen: an exact January sibling whose +`while (call() < real)` loop keeps a single header copy, with a nameable source cause. + +### `_refresh_listener` (active residual, 496 / 25, 178/178 instructions) + +One transposition at the loop latch: January `mov edi,[ebp-4]; mov esi,[ebp-8]`, ours the reverse (the two loop +IVs reloaded after `matrix4x3_inverse_transform_vector`). Detector pass over laws_w2/laws_w3: A24 does not fire +(no parameter home is involved, both IVs own cells), A12 is about preheaders, A6 does not fire (frames are equal), +A20 needs stores. Declaration order, in-loop declaration, void single exit, the HCEA inverted arm and a whole-TU +compile-order reorder are already measured inert. No shape spent; criterion unchanged. + +### `_update_channel_for_impulse_sound` (park, 528 / 29, 170/170 instructions) + +Three ordering deltas remain inside one basic block: (a) January pre-pushes the +`sound_definition_get_minimum_distance` argument (`mov eax,[esi+8]; push eax`) into the gain product; (b) +`fld [esi+0x88]; fmul [edx+0x30]` against our `fld [edx+0x30]; fmul [esi+0x88]` for +`sound->pitch * pitch_range->playback_rate` (A35 leaf age); (c) January groups the three cone loads and wedges the +`FLT_MAX` store between the cone stores (the A4 "all loads then all stores" fingerprint). Both admissible levers +for (b) are already measured - a source operand swap is canonicalised away (waveC I1) and the named pitch local is +worse (waveC I2, 544) - and the only source form that would produce (c) is an aggregate copy or helper that does +not exist in January's tag layout (an invented owner, R14/R15). Exactness is therefore unreachable through (a)/(c) +alone, so no shape was spent. + +### `_sound_set_definition_end` (park, 208 / 9) and `_update_channel_for_looping_sound` (park, 1360 vs 1376) + +Both carry the same single mechanism, and laws_w3 A34 names it: January loads the `short` field +`sound->pitch_range_index` for a call argument as `xor eax,eax; mov ax,word [esi+0x8e]` (set_definition_end 0x3c; +looping 0x2ea), the fingerprint of a `short` prototype parameter, while `sound_definitions.h` publishes +`long pitch_range_index`, whose C semantics force our genuine conversion `movzx`. For `_sound_set_definition_end` +that load plus the resulting two-store shift is the *only* difference (72 vs 73 instructions); +`_update_channel_for_looping_sound` additionally shows a frame-cell permutation and one extra `xor eax,eax`, and +waveC measured it at exactly 1360/61 under an emulated `short` prototype. The owner ruled that prototype an ABI +lie (the `sound_definitions.c` definition is `long` and casts every use), so both rows stay blocked on that owner +decision; nothing in `sound_manager.c` can close them, and a mask or cast trick would be R7. When the ruling +unblocks the looping row, also fix the behavioural defect the w1 ledger recorded in the parked body (the +next-permutation test is nested, so the permutation advance is skipped). + +### `_sound_find_like_channel` (park, 272 / 16, 91/91 instructions) + +One callee-saved colouring swap (January `si`/`ebx` where we use `bx`/`esi`, including the `mov ax,si` return +copy). No law fires; declaration order and a named channel pointer are measured inert (waveC F1-F3). No shape +spent. + +## 6. Checks + +- Final real-file gate identical to baseline row for row: `== exact 58 residual 7 unwritten 0`; + `_point_from_line3d` guard passed. +- `scratch/parkcheck.py` on the real object: `parks 5 drift 0`. +- `tools/fake_match_scan.py source/sound/sound_manager.c`: 0 review leads (also 0 on `rl1.c`). +- `git diff --stat -- source/sound/sound_manager.c`: empty. + +## 7. Disposition + +`sound_manager.obj` stays at 58/65 strict exact; this wave claims no bytes. It leaves one actionable zero-credit +proposal (the `_sound_refresh_looping` park re-baseline to `rl1.c`), one owner ruling that would unlock 1,568 +padded bytes across `_sound_set_definition_end` and `_update_channel_for_looping_sound`, and four rows classified +as measured ties with their levers exhausted. diff --git a/docs/object_matching_logs/structure_lens_flares_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/structure_lens_flares_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..7e828801 --- /dev/null +++ b/docs/object_matching_logs/structure_lens_flares_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,110 @@ +# StructureLensFlares (opus5 250K house-clean lane, wave w3c, 2026-09-15): residual mechanism decoded, still owner-blocked + +Worker lane `opus/250k-house-clean-20260915`. **No production source changed.** +`git diff --stat -- source/structures/structure_lens_flares.c` is empty; the final real-file gate equals the baseline. + +## 1. Baseline and final (real file, whole-TU gate, `_point_from_line3d` guard passed) + +`== exact 3 residual 1 unwritten 0 (of 4 listed)` — unchanged at the end of the wave. + +| Row | January (padded / relocs / sha16) | Real file | +| --- | --- | --- | +| `_build_structure_lens_flares` (PARKED, `instruction-scheduling`) | 4336 / 156 / `40aa8e486e52fe25` | 4336 / 156 / `57d6f033f3793735`; 1327/1327 instructions, 13 differing aligned blocks | +| `_cluster_index_from_point` | 96 / 3 | EXACT | +| `_compare_temp_markers` | 32 / 0 | EXACT | +| `_structure_lens_flares_place` | 16 / 2 | EXACT | + +## 2. Duplicate prevention + +Read: the four canonical Jonas ledgers (grid topology 0826, policy 0829, Fable uniform boundary 0830, authenticated +fuzzy closeout 0901), the w2 ledger `structure_lens_flares_obj_opus5_150k_w2_20260914.md` and its worker notes +`scratch/workers/structure_lens_flares_breakable_surfaces.md`, `scratch/w2/laws_w2.md`, `scratch/w3/laws_w3.md`, +and the memory records (lens-flares 4-instruction witness, mechanism-proven parks, fast_ftol, no-fake-matching). +`git log --all -- source/structures/structure_lens_flares.c`: 21 commits. `branch_sweep.py`: 19 unique blobs, +14 compile-fail, the others 0-2 exact with 2-4 unwritten — **HEAD is the best blob in the corpus**. The Fable census +(ledger 0830) covered 1,562 worktrees and 903 distinct objects with zero strict-exact bodies. + +## 3. Residual decomposition (alndiff vs `build/split`, re-measured at this HEAD) + +Frame layout is identical to January's: `[ebp-0x34]/-0x30/-0x2c` = `s_temp.n.i/j/k`, `[ebp-0x24]/-0x20/-0x1c` = +`origin.x/y/z`, `[ebp-0x88]/-0x84/-0x80` = `t_plane.n.i/j/k`. + +- **N1 @0xa65** — January `fstp [ebp-0x2c]`, then j*j, then a reload `fld [ebp-0x2c]; fmul [ebp-0x2c]`; ours forwards k + in st(0) (`fst` + `fmul`). +- **N2/N3/N4 @0xb05, @0xb3d, @0xbb0** — every s_temp/s_plane sum (magnitude_squared3d inside `normalize3d`, + `s_temp.d = dot_product3d(origin, n)`, and both `plane3d_distance_to_point` sites) is **j,k,i** in January and + **k,j,i** in ours. The t_plane sums are **k,j,i in both** and match; k is the youngest t leaf, so ours is the plain + laws_w3 A35(2) "youngest term first" order and January's s-sums are the tie-broken ones. +- **derivative @0xdee** — 2-byte `mov edi,edi` vs 3-byte `lea ecx,[ecx]` loop padding, a pure consequence of the 3-byte + shift from N1; it disappears whenever N is fixed. +- **tie A @0xe28** (`fld [ebp-0x84]` vs `mov [ebp-0x44],eax`, s-loop preheader) and **tie B @0xe6a** (`push ebx` vs + `fadd [ebp-0x24]`, inside project_point3d's argument pushes) — int-vs-FP interleave transpositions, Fable + dbg32b-certified DAG-creation-order tie-breaks after ~15k probes. + +## 4. New evidence from this wave + +1. **The marker-search statement is byte-insensitive to its spelling.** `alndiff base.obj vs f1.obj` (f1 = w2's lf_F1, + i.e. HEAD with `point_from_line3d(&test_point, &direction, offset, &test_point)` restored) shows the two objects are + **byte-identical from 0xdf0 to the end of the function**, with identical frame size and identical `[ebp-N]` offsets. + The genuine inline call and HEAD's hand expansion emit exactly the same local code; the call's whole effect is a + global IL/DAG tie-break perturbation ~1,000 bytes earlier (N1-N4 and the padding). January's bytes therefore cannot + discriminate the two spellings locally. This is the "per-basin lottery" the Fable ledger recorded, promoted here from + a hypothesis to a measurement, and it is the reason no COMDAT-free respelling can be argued from byte evidence. +2. **laws_w3 A35(4) is confirmed for this function** (the law file already names this group N as its example): the hand + expansion ages `direction`/`test_point` field names and flips the later s-sums. +3. **A31(b) / A35(1) pointer-write routing is refuted here.** `set_real_vector3d(&direction, ...)` — the lever that + closed breakable_surfaces' cross-product roles and `_lightning_offset_marker_position` — makes this function worse + (13 -> 16 differing blocks) and adds a candidate-only `_set_real_vector3d` COMDAT that the January object does not + own (census: `('code','_set_real_vector3d')` absent from target). +4. **Premise correction to the w2 ledger.** w2 recorded the d-only plane form as blocked by "a representation cast + (house rule)". That is void: laws_w2 section B and laws_w3 section B both ADMIT point-to-vector `(real_vector3d *)` + casts, and `source/math/real_math.h:1325` (`plane3d_from_point_and_normal`) itself writes + `dot_product3d((real_vector3d *)point, &plane->n)`. The d-only form is house-rule admissible; it is simply inert on + its own. The **sole** remaining blocker for group N is the `_point_from_line3d` COMDAT. + +## 5. Law detector pass and shapes tried (3 of 5; scratch only, siblings 3/3 exact in every probe) + +| Probe | Shape (law) | Result | +| --- | --- | --- | +| `scratch/workers/w3c_structure_lens_flares/p1.c` | marker update as `test_point.x += direction.i * offset;` x3 (natural-spelling control for A35(4)) | 4336/156, 13 blocks; **function bytes identical to base** | +| `.../p2.c` | `set_real_vector3d(&direction, ...)` initialisation (A31(b) + A35(1)) | 4336/156, **16 blocks — worse**; new candidate-only `_set_real_vector3d` owner | +| `.../p3.c` | `s_temp.d = dot_product3d((real_vector3d const *)&origin, &s_temp.n);` alone, no `point_from_line3d` (corrected cast premise, single-plane granularity; w2's F2 tested both planes together) | 4336/156, 13 blocks; **function bytes identical to base** — VC7 fully eliminates the `plane3d_from_point_and_normal` self-copy | + +Detectors that did not fire: A12 touches tie A but w2's P* probes already spent the six `s_grid`/`s_count`/`t_distance` +decl-init orders in this basin (no flip); A31(a) does not apply (neither tie site is a post-call stack adjust — tie A is +a preheader integer store against a hoisted x87 invariant, tie B is one push inside a push sequence); A24 is inert (the +frame already equals January's); A23, A25-A30, A32-A34, A36-A45 have no hook (no unwritten row, no size/reloc delta, no +frame gap, no switch/loop-spelling question, no C4013 site in `scratch/gate4/c4013.txt`, no static-linkage candidate, no +`_code_` label, no alias class). + +Not repeated (earlier waves): L1, LB1/LB2, L2/L2b, LS2-LS4, the 16-combination `POINT_DOT_VECTOR3D` lattice, F4-F6, +G1-G3, M1/M2, K1-K4, P*, and the Fable ~15k-probe tie search. + +## 6. Outcome + +**NOT LANDED — OWNER-BLOCKED (group N) + SKIPPED-EXHAUSTED (ties A/B). No proposal.** + +Strict EXACT is unreachable without emitting `_point_from_line3d`, which WORKER_BRIEF sections 0 and 9b make a lane +rule and which laws_w3 R14 forbids re-proposing; and even with it the body stays two instruction pairs short (w2's +lf_F3/lf_F7). Brief section 6 allows an already-written residual to land only when it becomes strict EXACT, so no +body — however much closer — is landable here. Deliberately no orchestrator proposal: the w2 ledger already records +this function on the `point_from_line3d` owner-ruling list. + +## 7. Reopen criterion (revised; replaces the w2 three-clause criterion) + +Reopen only if **both**: (a) the owner lifts the `_point_from_line3d` no-emission rule; and (b) a source lever is found +for the two int-vs-FP transpositions at +0xe28 and +0xe6a. The w2 criterion's third clause ("a cast-free d-only plane +spelling") is **struck**: the cast is admitted policy and the d-only form is byte-inert on its own. + +Do **not** spend further waves searching for COMDAT-free respellings of the marker-search update. This wave proved the +statement's local bytes are spelling-insensitive, so no byte evidence can select one, and any COMDAT-free spelling that +happened to win the global tie-break would be an unfalsifiable lottery pick barred by the no-fake-matching gate. + +## 8. Verification + +- `python -B tools/campaign/gate.py source/structures/structure_lens_flares --all --forbid-emitted-symbol _point_from_line3d`: + guard passed, `== exact 3 residual 1 unwritten 0` (equals baseline). +- `python -B scratch/parkcheck.py source/structures/structure_lens_flares `: parks 1, drift 0. +- owner census vs `build/base` and `build/split`: candidate-only `[]`, base-only `[]`. +- `python -B tools/fake_match_scan.py source/structures/structure_lens_flares.c`: 0 review leads. +- `git diff --stat -- source/structures/structure_lens_flares.c`: empty. diff --git a/docs/object_matching_logs/ui_widget_game_data_input_functions_obj_opus5_250k_w3c_20260915.md b/docs/object_matching_logs/ui_widget_game_data_input_functions_obj_opus5_250k_w3c_20260915.md new file mode 100644 index 00000000..60c91e10 --- /dev/null +++ b/docs/object_matching_logs/ui_widget_game_data_input_functions_obj_opus5_250k_w3c_20260915.md @@ -0,0 +1,142 @@ +# `ui_widget_game_data_input_functions.obj` — opus5 250K house-clean lane, wave w3c (2026-09-15) + +Worktree `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/250k-house-clean-20260915`. +Worker scope: `source/interface/ui_widget_game_data_input_functions.c` only. No header, config, build or git +operation was performed. Worker totals are not credit; only the orchestrator's full build counts. + +**Outcome: nothing landed — honest early exit. The real file is byte-for-byte unchanged +(`git diff --stat` empty).** The unit's only strict-reachable non-exact row is a measured block-layout tie; the +other five rows are either accepted-semantic, csplit-alias-bound (strict exact unreachable), or a locked park whose +recorded blocker no w2/w3 law explains. + +## Baseline and final + +`gate.py source/interface/ui_widget_game_data_input_functions --all --forbid-emitted-symbol _point_from_line3d` + +| | exact | residual | unwritten | guard | +|---|---:|---:|---:|---| +| baseline (HEAD) | 40 | 6 | 0 | passed | +| final (unchanged) | 40 | 6 | 0 | passed | + +Parks: `_solo_level_select_list_update_displayed_items` — `scratch/parkcheck.py`: **parks 1, drift 0**. +`tools/fake_match_scan.py`: 0 review leads. Owner census: unchanged (no edit). + +| function | padded | tag | w3c verdict | +|---|---:|---|---| +| `_multiplayer_game_directions` | 320 | sha | NOT-LANDED — block-layout tie (A43 family), 1 new shape + 8 lab spellings | +| `_player_profile_1wide_list_update` | 1,152 | sha | OWNER-BLOCKED — duplicate-store ruling, and reloc-identity even then (zero strict credit) | +| `_player_profile_3wide_list_update` | 1,232 | reloc-identity | SKIPPED — accepted_ledger (semantic tier) | +| `_player_profile_update_cache_for_nwide_list` | 272 | reloc-identity | SKIPPED-EXHAUSTED — bytes already equal; csplit alias (A45) | +| `_solo_level_select_list_update_displayed_items` | 704 | sha | SKIPPED-EXHAUSTED — parked two-reload transposition; no law fires | +| `_splitscreen_pregame_status_screen_update` | 1,216 | reloc-identity | SKIPPED-EXHAUSTED — bytes already equal; csplit alias (A45) | + +There are no UNWRITTEN rows in this object, so the w3 work order started at the structural/residual rows. + +Prior ledgers read in full: `*_obj_opus5_150k_w1_20260914.md`, `*_obj_fable_salvage_20260902.md`, +`ui_widget_game_data_profile_fuzzy_reconciliation_20260909.md`, the park text in `config/parked.json`, plus +`scratch/w2/laws_w2.md` and `scratch/w3/laws_w3.md` (applied as detectors to every row; see below). + +## `_multiplayer_game_directions` (320) — the only strict-reachable row + +w1 had already driven this to 122/122 instructions with equal size and relocations using +`scratch/workers/ui_widget_game_data_input_functions/mgd_a.c` (the real body plus the teams `if/else` join, i.e. one +`widget->visible = TRUE;` after the teams arms — itself the laws_w3 **A25** join-store form, confirmed by January's +tail-duplicated per-arm stores). The single remaining hunk is block placement: + +``` +January ours (mgd_a) + 55 cmp word [esi+0x112],di 55 cmp word [esi+0x112],di + 5c jl 0x89 <- machine arm 5c jge 0x6f <- skip over inline arm + 5e call is_splitscreen 5e .. machine arm (0x22) + ret + .. 6f call is_splitscreen + 78 player arm (0x23) + ret 89 player arm (0x23) + ret + 89 machine arm (0x22) + ret +``` + +Flow graph, register allocation (widget in EBX, the shared `mov edi,2` constant), relocations and every other byte +are identical. January keeps the two test chains contiguous and emits both cold arms afterwards, player arm first. + +### New work this wave + +1. **f1 (A25 join-store applied to the server block)** — `scratch/workers/w3c_ui_widget_game_data_input_functions/f1.c`: + `short string_list_index = NONE;` assigned in the two arms, then one guarded store plus `visible = TRUE`. + Result **336 != 320 [size]**: VC7 does tail-duplicate the stores into both arms (A25 holds) but materialises the + value in a register (`mov eax,0x22 … mov word [ebx+0x40],ax`), and the arm order does not move. Reverted. +2. **Lab census** (`scratch/workers/w3c_ui_widget_game_data_input_functions/lab1.c`, `lab2.c`, runner `lab.py`, + compiled with this unit's ninja cflags). `h1` reproduces our real layout exactly, so the lab is faithful. + All of these leave the machine arm inline: else-if ladder (f2, h2 control), `while` instead of the for-increment + teams loop (f3 — laws_w3 **A23** frequency probe), the whole teams tail removed (f4), a `visible = FALSE` exit + inside the `if (server)` block (f5), and an extra nesting level on the splitscreen test (h3). + `h2` (the two ifs swapped in source order) swaps the **test** order in the emitted bytes, which positively proves + January's source order is machine-test-first — i.e. our source order is right and the difference is layout only. + +Together with w1's five shapes (local join store, else-if, flat single-exit chain, global visible join, condition +boolean local) that is **14 measured spellings**. Classification: block-layout tie, laws_w3 **A43** family (no source +lever has ever moved late block/epilogue placement). laws_w3 A39's switch device does not apply (no switch; both arms +already end in an explicit `return`), and A25/A23/A16/A26 were each tested above. + +**Reopen criterion:** a measured law that predicts which of two cold early-return arms C2 emits first (a real +block-frequency/placement decode), or January-side evidence that the machine arm is not a plain `if`-then in source. +Do not re-spend shapes on if/else-if, condition booleans, join stores, nesting or loop respellings. + +## `_player_profile_1wide_list_update` (1,152) — owner-blocked and strict-unreachable + +Re-measured w1's shape C (`scratch/workers/ui_widget_game_data_input_functions/p1_final.c`) at this HEAD: +**1,152/70, normalized bytes equal, tag [reloc-identity]** (the extra residual row in that gate is only because the +file predates the landed `_network_pregame_status_screen_update` unpark). + +Two independent reasons it cannot land in this wave: + +1. The body depends on January's byte-proven duplicated `text[0xFF] = 0` store (default-profile valid path stores at + +0x370 and again at +0x408). Brief section 5 prohibits duplicated stores; the w1 owner-ruling request is still open + (laws_w3 C3 "byte-proven redundant stores/branches", the A42 family) and the 2026-09-15 rulings do not cover it. +2. **Even with that ruling the row stays [reloc-identity]**: the remaining relocation differences are the csplit + one-past-the-end alias at +0x1a9 and the `L""` / `"\r\n"` literal COMDATs January references undefined + (laws_w3 **A45**). So it is worth **zero strict credit**, and brief section 6 forbids landing an already-written + residual that does not become strict EXACT. + +For the orchestrator: `_player_profile_3wide_list_update`, `_player_profile_update_cache_for_nwide_list` and +`_splitscreen_pregame_status_screen_update` are all already in `build/semantic_report.json` accepted_ledger, so w1's +semantic-credit request for the latter two is satisfied; `_player_profile_1wide_list_update` is the only alias-class +row in this object that is uncredited, and it needs the duplicate-store ruling first. + +## `_solo_level_select_list_update_displayed_items` (704, PARKED) — no law fires + +Park class `instruction-scheduling`; target and parked base are both 704/45 with objdiff 99.89 %. Measured today: +214/214 instructions with **one** transposition — the loop back-edge block at +0x9a reloads +`mov eax,[ebp-0xc]` (item_index) then `mov edi,[ebp+8]` (list_widget) in January and the reverse in ours. The loop +bottom is `mov eax,[ebp-0xc]; inc eax; cmp eax,3; mov [ebp-0xc],eax; jl 0x9a`, so both reloads are allocator-redundant; +the frame (profile −0x54, index array −0x24, item_index −0xc, marker homes −0x10/−0x14/−0x18) and all 45 relocations +already match. + +Detectors run with no hit: A6/A33 (frame layout, block scoping — the frame is already exact), A12 (preheader invariant +order — this is a back-edge reload block, not a preheader), A24 (parameter-home offer — no home is packed differently), +A5 (alias removal — there is no alias), A23/A25/A39 (layout/frequency — no block moves), A35 (x87 only). +The park's recorded cause is the shared `player_profile.h` ownership: the older exact body used a TU-local shadow +struct, which brief section 5 prohibits, and the park text forbids source-pressure tuning. No scratch candidate was +produced, because any edit here would be a pure two-load steering change. + +**Reopen criterion:** a measured law predicting the order of two independent spill reloads in a loop back-edge block, +or January-side evidence that `struct player_profile` / the `player_ui_get_active_player_profile` signature differs +from the shared header in a way that changes allocation. + +## Other rows + +- `_player_profile_update_cache_for_nwide_list` (272) and `_splitscreen_pregame_status_screen_update` (1,216): landed + byte-identical in w1; the residual tag is purely the csplit one-past-the-end alias (+0x40/+0x79 and +0x4a1 + respectively). laws_w3 A45 makes strict exact unreachable; no source work is possible or useful. +- `_player_profile_3wide_list_update` (1,232): accepted_ledger, semantic tier, untouched. + +## C4013 note (no action) + +`scratch/gate4/c4013.txt` reports one implicit declaration for this unit, +`ui_widget_game_data_input_functions.c(648) : warning C4013: 'error' undefined`. The site is inside +`_ui_widget_game_data_function_invoke`, which is **already strict EXACT**; laws_w3 A32 records that A7 includes whose +C4013 result is discarded or already exact are byte-inert, and E18 forbids landing byte-inert hygiene includes in a +unit where nothing else lands. No include was added. + +## Proposals + +None. No header, config or owner packet is needed for any row of this object beyond the two rulings already on the +orchestrator's list (the `_player_profile_1wide_list_update` duplicate-store ruling, which yields zero strict credit, +and the standing park criterion for `_solo_level_select_list_update_displayed_items`). diff --git a/source/game/game_engine.c b/source/game/game_engine.c index f8227032..85711ff4 100644 --- a/source/game/game_engine.c +++ b/source/game/game_engine.c @@ -1137,11 +1137,6 @@ static void game_engine_generate_title_string( wchar_t *secondary_string; wchar_t life_string[128]; wchar_t score_string[256]; - union - { - wchar_t team_names[22]; - struct postgame_statistic_entry entry; - } branch_storage; long string_list_index; wchar_t *format_string; @@ -1212,41 +1207,41 @@ static void game_engine_generate_title_string( { long did_player_win = game_engine_did_player_win(player_index); boolean has_teams = FALSE; + wchar_t *outcome_string; if (game_engine) has_teams = global_variant.has_teams; - if (did_player_win == NONE) + switch (did_player_win) { + case NONE: string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { - ustrncpy( - title_string, - unicode_string_list_get_string(string_list_index, 0x37), - 80); - title_string[79] = 0; - return; + outcome_string = unicode_string_list_get_string( + string_list_index, + 0x37); } - } - else if (did_player_win == FALSE) - { + else + outcome_string = L""; + ustrncpy(title_string, outcome_string, 80); + break; + + case FALSE: if (has_teams) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { - ustrncpy( - title_string, - unicode_string_list_get_string( - string_list_index, - 0x38), - 80); - title_string[79] = 0; - return; + outcome_string = unicode_string_list_get_string( + string_list_index, + 0x38); } + else + outcome_string = L""; + ustrncpy(title_string, outcome_string, 80); } else { @@ -1254,37 +1249,30 @@ static void game_engine_generate_title_string( tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { - ustrncpy( - title_string, - unicode_string_list_get_string( - string_list_index, - 0x39), - 80); - title_string[79] = 0; - return; + outcome_string = unicode_string_list_get_string( + string_list_index, + 0x39); } + else + outcome_string = L""; + ustrncpy(title_string, outcome_string, 80); } - } - else if (did_player_win == TRUE) - { + break; + + case TRUE: if (has_teams) { string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { - wchar_t *outcome_string = - unicode_string_list_get_string( - string_list_index, - 0x3A); - - ustrncpy( - title_string, - outcome_string, - 80); - title_string[79] = 0; - return; + outcome_string = unicode_string_list_get_string( + string_list_index, + 0x3A); } + else + outcome_string = L""; + ustrncpy(title_string, outcome_string, 80); } else { @@ -1292,36 +1280,30 @@ static void game_engine_generate_title_string( tag_loaded('ustr', "ui\\multiplayer_game_text"); if (string_list_index != NONE) { - ustrncpy( - title_string, - unicode_string_list_get_string( - string_list_index, - 0x3B), - 80); - title_string[79] = 0; - return; + outcome_string = unicode_string_list_get_string( + string_list_index, + 0x3B); } + else + outcome_string = L""; + ustrncpy(title_string, outcome_string, 80); } + break; } - else - return; - - ustrncpy(title_string, L"", 80); - title_string[79] = 0; - return; } - - if (game_engine && global_variant.has_teams) + else if (game_engine && global_variant.has_teams) { + wchar_t team0_name[8]; + wchar_t team1_name[8]; long team0_score; long team1_score; game_engine->format_team_name( 0, - &branch_storage.team_names[14]); + team0_name); game_engine->format_team_name( 1, - &branch_storage.team_names[0]); + team1_name); team0_score = game_engine_get_team_score(0); team1_score = game_engine_get_team_score(1); @@ -1341,8 +1323,8 @@ static void game_engine_generate_title_string( title_string, 80, format_string, - &branch_storage.team_names[14], - &branch_storage.team_names[0], + team0_name, + team1_name, secondary_string); } else if (team0_score < team1_score) @@ -1361,8 +1343,8 @@ static void game_engine_generate_title_string( title_string, 80, format_string, - &branch_storage.team_names[0], - &branch_storage.team_names[14], + team1_name, + team0_name, secondary_string); } else @@ -1381,50 +1363,54 @@ static void game_engine_generate_title_string( title_string, 80, format_string, - &branch_storage.team_names[0], + team1_name, secondary_string); } - - title_string[79] = 0; - return; - } - - branch_storage.entry = - *game_engine_get_player_place(&branch_storage.entry, player_index); - game_engine->format_player_score(player_index, score_string); - - if (is_place_tied(&branch_storage.entry)) - { - string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); - if (string_list_index != NONE) - { - format_string = unicode_string_list_get_string( - string_list_index, - 0x3F); - } - else - format_string = L""; } else { - string_list_index = tag_loaded('ustr', "ui\\multiplayer_game_text"); - if (string_list_index != NONE) + struct postgame_statistic_entry entry; + + entry = + *game_engine_get_player_place(&entry, player_index); + game_engine->format_player_score(player_index, score_string); + + if (is_place_tied(&entry)) { - format_string = unicode_string_list_get_string( - string_list_index, - 0x40); + string_list_index = + tag_loaded('ustr', "ui\\multiplayer_game_text"); + if (string_list_index != NONE) + { + format_string = unicode_string_list_get_string( + string_list_index, + 0x3F); + } + else + format_string = L""; } else - format_string = L""; + { + string_list_index = + tag_loaded('ustr', "ui\\multiplayer_game_text"); + if (string_list_index != NONE) + { + format_string = unicode_string_list_get_string( + string_list_index, + 0x40); + } + else + format_string = L""; + } + + usnprintf( + title_string, + 80, + format_string, + get_place_string(&entry), + score_string, + secondary_string); } - usnprintf( - title_string, - 80, - format_string, - get_place_string(&branch_storage.entry), - score_string, - secondary_string); title_string[79] = 0; return;