mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-17 18:00:25 +02:00
Match action_vehicle seat evaluation and reopen the input_xbox analog park
Wave w3d batch 1 (the structural retarget: census the frame, the [ebp+/-N] slots and the relocations before proposing any shape). - action_vehicle _action_vehicle_evaluate_seat (+976): the unit called vehicle_specific_definition_get through a C4013 implicit declaration; including its genuine owner units/vehicle_definitions.h fixes the call, and January's single-exit shape puts the three seat points in the taken branch's own block instead of behind an early return. - input_xbox _input_update_analog_button_state (+48): park reopened and removed. - effects _effect_allowed_by_environment and _effect_generate_particles: coupled A8 callee/caller re-baseline, ZERO credit. Both are now January-exact in padded size (and relocation count for the caller) and stay parked and fuzzy; the remeasures carry dated evidence in config/parked.json. Full ninja, parks 277 active / 0 stale / 0 invalid, stable diff vs scratch/claude250k-start.json: gained 3 / 1,824 bytes, regressions 0; fake scan 0 leads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df21bd9389
commit
ae10935da6
13 files changed
+1861
-188
No files matched your search
+9
-28
@@ -738,25 +738,6 @@
|
||||
"objdiff_percent": 82.10656
|
||||
}
|
||||
},
|
||||
{
|
||||
"unit": "source/input/input_xbox",
|
||||
"function": "_input_update_analog_button_state",
|
||||
"class": "unclassified",
|
||||
"evidence": "Genuine January standalone private analog-button hysteresis helper at file offset 780288, with an ordinary static source definition and authentic auto-inlining into the device poller. Target and candidate are 48 padded bytes with no relocations; only sub al,32 versus compiler-canonical add al,-32 differs. Retain the natural threshold subtraction, not assembly, optimizer steering or an invented helper. All 16 inherited exact owners are preserved and no extra code owner or point_from_line3d is emitted. Reopen only for new authentic compiler/source evidence. See docs/object_matching_logs/input_xbox_obj_device_keyboard_pollers_wave_20260904.md. This park grants no exact credit.",
|
||||
"measurements": {
|
||||
"target": {
|
||||
"size": 48,
|
||||
"relocation_count": 0,
|
||||
"normalized_sha256": "5850a17e99380c5499a719ba1667ef91f992a37284afe5049ba6e7f04e79c826"
|
||||
},
|
||||
"base": {
|
||||
"size": 48,
|
||||
"relocation_count": 0,
|
||||
"normalized_sha256": "78f85bb297cc663f10c1fcea301827288980a1f2dc6cf99b0012b4fe641a1bb9"
|
||||
},
|
||||
"objdiff_percent": 97.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"unit": "source/input/input_xbox",
|
||||
"function": "_input_get_device_states",
|
||||
@@ -2988,7 +2969,7 @@
|
||||
"unit": "source/effects/effects",
|
||||
"function": "_effect_allowed_by_environment",
|
||||
"class": "unclassified",
|
||||
"evidence": "The retained ordinary-C environment switch implements the complete January air, water, vacuum, and anywhere behavior with named enum constants and typed location/point arguments, and the later HCEA reconstruction independently corroborates those four arms. January's invalid-enum arm calls the fatal assertion path and then reloads an uninitialized result byte if system_exit unexpectedly returns. The Opus donor reproduced that indeterminate fallthrough and was exact, but it is rejected under the no-undefined-behavior rule. The closest safe natural source initializes the result from the meaningful anywhere predicate before overriding the air/water arms; invalid and vacuum values therefore remain FALSE. It compiles to 112 padded bytes and ten relocations versus January's 128, with 55.909092 percent objdiff, while preserving the donor's better 1,616-byte and 592-byte shapes in both real callers. Simple FALSE initialization and assignment-after-assert forms also produced 112 bytes but caused both callers to inline the assertion body and regress broadly. The fail-closed classifier returns UNKNOWN. No uninitialized read, false noreturn declaration, volatile/register forcing, barrier, pragma, assembly, fake dependency, or nonsensical branch is retained. Reopen only for authoritative January source proving a safe result lifetime or a truthful shared noreturn contract that naturally reproduces the target.",
|
||||
"evidence": "The retained ordinary-C environment switch implements the complete January air, water, vacuum, and anywhere behavior with named enum constants and typed location/point arguments, and the later HCEA reconstruction independently corroborates those four arms. January's invalid-enum arm calls the fatal assertion path and then reloads an uninitialized result byte if system_exit unexpectedly returns. The Opus donor reproduced that indeterminate fallthrough and was exact, but it is rejected under the no-undefined-behavior rule. The closest safe natural source initializes the result from the meaningful anywhere predicate before overriding the air/water arms; invalid and vacuum values therefore remain FALSE. It compiles to 112 padded bytes and ten relocations versus January's 128, with 55.909092 percent objdiff, while preserving the donor's better 1,616-byte and 592-byte shapes in both real callers. Simple FALSE initialization and assignment-after-assert forms also produced 112 bytes but caused both callers to inline the assertion body and regress broadly. The fail-closed classifier returns UNKNOWN. No uninitialized read, false noreturn declaration, volatile/register forcing, barrier, pragma, assembly, fake dependency, or nonsensical branch is retained. Reopen only for authoritative January source proving a safe result lifetime or a truthful shared noreturn contract that naturally reproduces the target. 2026-09-15 (250K lane wave w3d): re-measured after the coupled A8 callee re-baseline. The body is strictly closer - padded size is now January-exact (was 112), 10 relocations with every external target identical, and four distinct jump-table case entries instead of three. Zero strict credit; the row stays parked and fuzzy [sha], and its strict-exact reopen criterion is unchanged and NOT met.",
|
||||
"measurements": {
|
||||
"target": {
|
||||
"size": 128,
|
||||
@@ -2996,11 +2977,11 @@
|
||||
"normalized_sha256": "a005be4ac405246678b6c4e9a4bf65deb16b96b5208fad9454b45df70e8fa7cd"
|
||||
},
|
||||
"base": {
|
||||
"size": 112,
|
||||
"size": 128,
|
||||
"relocation_count": 10,
|
||||
"normalized_sha256": "19760ee664c88645139c9ce8c61958c388596d76ccd0eae8bba88f5c865632df"
|
||||
"normalized_sha256": "8f9a2185534b3801351e0f354e1fa4e5e3e39f7250559d1020cf2388df9248cf"
|
||||
},
|
||||
"objdiff_percent": 55.909092
|
||||
"objdiff_percent": 63.909092
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -3026,7 +3007,7 @@
|
||||
"unit": "source/effects/effects",
|
||||
"function": "_effect_generate_particles",
|
||||
"class": "unclassified",
|
||||
"evidence": "The retained ordinary-C implementation completes the event-particle traversal, distribution, marker transforms, environment/disposition filtering, velocity/orientation, tint, split-screen limits, and particle creation using named enums, typed structures, cseries flags, and engine real types. January disassembly and relocations establish the same behavior, and the later HCEA reconstruction corroborates the algorithm. The candidate is 1,616 padded bytes with the same 46 relocation count versus January's 1,888 bytes, at 68.30481 percent objdiff. January inlines effect_real_random_range at three sites, whereas VC7 keeps natural calls in this context; forcing inline would violate the house rule and disturb the exact out-of-line/helper schedule. The fail-closed classifier returns UNKNOWN. No forced inline, duplicated helper body, volatile/register forcing, barrier, pragma, assembly, raw offset, or fake control flow is retained. Reopen only for authoritative January source or natural compatible-compiler evidence for the three helper-inline decisions. On September 14 (lane opus/100k-consolidated-20260914, wave D, adversarially verified) the parked body's particle loop was corrected to January's per-instance count (hoisted count reloaded per instance, dec/jne proven at 0x187-0x196) as the coupled prerequisite of the exact effect_update reopen: 2048 bytes, 55 relocations, still fuzzy; the residual is the environment-helper inline decision plus the loop guard and register form. See docs/object_matching_logs/effects_obj_opus5_100k_waveD_20260914.md.",
|
||||
"evidence": "The retained ordinary-C implementation completes the event-particle traversal, distribution, marker transforms, environment/disposition filtering, velocity/orientation, tint, split-screen limits, and particle creation using named enums, typed structures, cseries flags, and engine real types. January disassembly and relocations establish the same behavior, and the later HCEA reconstruction corroborates the algorithm. The candidate is 1,616 padded bytes with the same 46 relocation count versus January's 1,888 bytes, at 68.30481 percent objdiff. January inlines effect_real_random_range at three sites, whereas VC7 keeps natural calls in this context; forcing inline would violate the house rule and disturb the exact out-of-line/helper schedule. The fail-closed classifier returns UNKNOWN. No forced inline, duplicated helper body, volatile/register forcing, barrier, pragma, assembly, raw offset, or fake control flow is retained. Reopen only for authoritative January source or natural compatible-compiler evidence for the three helper-inline decisions. On September 14 (lane opus/100k-consolidated-20260914, wave D, adversarially verified) the parked body's particle loop was corrected to January's per-instance count (hoisted count reloaded per instance, dec/jne proven at 0x187-0x196) as the coupled prerequisite of the exact effect_update reopen: 2048 bytes, 55 relocations, still fuzzy; the residual is the environment-helper inline decision plus the loop guard and register form. See docs/object_matching_logs/effects_obj_opus5_100k_waveD_20260914.md. 2026-09-15 (250K lane wave w3d): re-measured as the caller of the re-baselined callee (A8 cascade). Padded size AND relocation count are now January-exact (was 2048/55), all 46 relocations agree by target, the frame sub esp,0xe4 matches, and the body is 566 vs 562 instructions with 75 aligned diff blocks (was 622 instructions / 120 blocks). Zero strict credit; stays parked and fuzzy [sha].",
|
||||
"measurements": {
|
||||
"target": {
|
||||
"size": 1888,
|
||||
@@ -3034,11 +3015,11 @@
|
||||
"normalized_sha256": "d196a029c22bfe043323cd0def8c1f0365590b559f3fbb65f23c9ef5b4065ef5"
|
||||
},
|
||||
"base": {
|
||||
"size": 2048,
|
||||
"relocation_count": 55,
|
||||
"normalized_sha256": "2dccf111fad130493ba4305ac4012e6d5d2b0f724894fa2f468b2e8d01130c27"
|
||||
"size": 1888,
|
||||
"relocation_count": 46,
|
||||
"normalized_sha256": "c251b0c184dbcdeb303b45ddd8ad38706f8e446888df359a7177b6bc55a964f9"
|
||||
},
|
||||
"objdiff_percent": 80.361855
|
||||
"objdiff_percent": 92.56863
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# action_vehicle.obj - opus5 250K house-clean lane, wave w3d (2026-09-15)
|
||||
|
||||
Unit: `source/ai/action_vehicle.c`. Worker notes: `scratch/workers/w3d_action_vehicle.md`.
|
||||
Scratch: `scratch/workers/w3d_action_vehicle/`. Predecessors: the 11 earlier
|
||||
`action_vehicle_obj_*` ledgers, in particular `_obj_opus5_100k_20260914.md` and `_obj_opus5_150k_w1_20260914.md`.
|
||||
|
||||
## Baseline and result
|
||||
|
||||
| | exact | residual | unwritten |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| HEAD (baseline, guard on) | 12 | 5 | 0 |
|
||||
| after w3d | 13 | 4 | 0 |
|
||||
|
||||
Newly strict EXACT: `_action_vehicle_evaluate_seat` - 976 padded bytes, 30 relocations, sha 9f67690555e799a6.
|
||||
No parks for this unit; no baseline-exact row changed; `_point_from_line3d` emitted-symbol guard passed.
|
||||
|
||||
Remaining non-exact rows: `_action_vehicle_desirable` 272 [size 288, sha], `_action_vehicle_find_destination`
|
||||
1232 [size 1248, sha], `_action_vehicle_setup_impromptu` 464 [sha], `_distance3d` 64 [sha].
|
||||
|
||||
## 1. What unblocked the object
|
||||
|
||||
`scratch/w3/laws_w3.md` section B lists, under "Newly ADMITTED by verifiers (do not avoid)":
|
||||
"(real_point2d const *)&point3d casts into 2D helpers (32 source sites, HCEA); A15 role order for distance2d",
|
||||
landed in wave w3a as `_actor_move_avoidance_setup` (see `actor_moving_obj_opus5_150k_w3_20260914.md` line 77).
|
||||
Wave w1 had already produced a strict-EXACT `_action_vehicle_evaluate_seat` body and withheld it for exactly that
|
||||
policy. The 2026-09-15 owner rulings (brief sections 9b/9c) do not touch the cast class, so no new ruling was needed.
|
||||
|
||||
## 2. `_action_vehicle_evaluate_seat` - EXACT, landed
|
||||
|
||||
Target 976 / 30 / 9f67690555e799a6; HEAD was 960 / 31 / sha.
|
||||
|
||||
Structural census before the work:
|
||||
- One relocation too many (31 vs 30). January calls `_normalize2d` twice (@0x00c1 for the entry facing, @0x0283
|
||||
for the to-seat direction) and `_tag_get` twice; ours inlined the second normalize2d and emitted an extra
|
||||
`_tag_get` produced by the C4013 implicit declaration of `vehicle_specific_definition_get` at line 471
|
||||
(`scratch/gate4/c4013.txt`).
|
||||
- 16 bytes short (960 vs 976) from the same two effects plus the frame.
|
||||
|
||||
The landed body combines five measured levers:
|
||||
1. **A7 missing prototype.** `#include "units/vehicle_definitions.h"` - the genuine owner, which defines
|
||||
`vehicle_specific_definition_get` as a macro over `tag_get` at vehicle_definitions.h:30. No consumer-local
|
||||
prototype (R8). This also removes the paired C4047 "differs in levels of indirection" warning.
|
||||
2. **Admitted 2D-helper casts.** `normalize2d((real_vector2d *)&facing)`,
|
||||
`distance2d((real_point2d const *)&actor->input.position.body_position, (real_point2d const *)&entrance_point)`,
|
||||
the MIN of that and the seat distance, `vector_from_points2d(... , &to_seat_direction)` and
|
||||
`dot_product2d(&to_seat_direction, (real_vector2d const *)&actor->input.facing_vector)`. `const` is used wherever
|
||||
the real_math.h parameter is `const`, matching the admitted w3a idiom.
|
||||
3. **A23-class loop spelling.** `while (prop = prop_iterator_next(&iterator))`. A rotated
|
||||
`for (prop = next; prop; prop = next)` inflates the estimated frequency of the code after the loop and inlines
|
||||
the second `normalize2d`; the while form leaves it out of line exactly like January. The idiom is already in the
|
||||
tree at actions.c:4454, action_obey.c:1826 and actor_looking.c:734.
|
||||
4. **Sibling scope.** The prop iterator and `prop` live in their own block around the loop, so VC7 shares their
|
||||
8-byte slot with `to_seat_direction` ([ebp-0x18]) and the frame drops 0x5c -> 0x54.
|
||||
5. **A3/A29 single exit.** `result = TRUE` after the entrance-point success, `result = FALSE; break;` for the
|
||||
conflicting actor, the whole publish tail under `if (result)`, one `return result;`. This reproduces January's
|
||||
three epilogues: the break threads to `xor al,al`, the success path tail-duplicates `mov al,1`, the failures
|
||||
share `mov al,bl`.
|
||||
6. **A17 discarded validation call.** The `if (!allow_any_seat)` block is the bare statement
|
||||
`vehicle_specific_definition_get(vehicle->definition_index);` followed by
|
||||
`unit_seat_is_driver(vehicle_index, seat_index);`. The earlier candidate used an unused local plus a `(void)`
|
||||
cast, which A17 rejects.
|
||||
|
||||
Shapes: E1 (the w1 body, re-gated at this HEAD) EXACT; v1 = E1 plus the const casts and the A17 bare statement,
|
||||
EXACT - landed.
|
||||
|
||||
Checks: all 12 baseline-EXACT rows preserved (row-by-row `--all` diff); guard passed; `scratch/parkcheck.py` reports
|
||||
no parks for the unit; owner census versus `build/base` adds only `_dot_product2d`, a real_math.h select-any
|
||||
header-inline COMDAT of the class laws_w2 A4 accepts as systemic (`_vector_from_points3d`, `_magnitude_squared3d`,
|
||||
`_normalize2d`, `_square_root`, `_scale_vector2d`, `_vector_from_points2d`, `_magnitude2d`, `_magnitude_squared2d`,
|
||||
`_distance_squared3d` are already candidate-only owners at HEAD, so R14 is not newly triggered); no data, BSS or
|
||||
COMMON change; `tools/fake_match_scan.py` 0 leads (HEAD 0); `CL /Zs /W3` removes C4013 and C4047 and adds nothing.
|
||||
|
||||
Authenticity: the routine computes the seat entry facing from entrance to seat in the XY plane, falls back to the
|
||||
actor own facing vector when that degenerates, takes the nearer of the entrance and seat 2D distances, rejects the
|
||||
seat when a friendly actor is already closer to the same seat, and publishes weight / range / facing / fake flags
|
||||
only for an accepted seat. Every helper used is the real_math.h routine the arithmetic is named after.
|
||||
|
||||
## 3. `_action_vehicle_find_destination` - structural gap closed in scratch, not landed
|
||||
|
||||
Target 1232 / 34 / 698d66d07e83af5b; HEAD 1248 / 34, frame 0x468 against the January 0x464.
|
||||
|
||||
Census (recorded before any shape):
|
||||
- **Relocation census by target**: 34 versus 34, every symbol count equal, with ONE real difference -
|
||||
January references `__real@3dfae147` where we emit `__real@3dfae148`. `0x3dfae147` is `(float)(0.35f*0.35f)`,
|
||||
the square of the float 0.35f; `0x3dfae148` is the decimal literal `0.1225f`. The HEAD source writes the folded
|
||||
literal; January wrote the product. This is an emitted-constant fidelity defect, not a tie.
|
||||
- **Relocation ORDER**: January calls `_global_collision_bsp_get` at @0x40f, BEFORE the `_global_up3d` read at
|
||||
@0x418; ours calls it at @0x47e just before `_collision_bsp_test_vector`. By the store/call-order law the BSP
|
||||
pointer is obtained before the test_point and down_vector statements, i.e. it is a block-top declaration
|
||||
`struct collision_bsp *collision_bsp = global_collision_bsp_get();`, not an inline argument.
|
||||
- **Frame census** (`slots.py` over `alndiff --include-equal`): January `sub esp,0x464`, ours `sub esp,0x468`.
|
||||
Ours owns one extra dword `[ebp-0x50]` (3 references, all in the trailing collision block) which pushes the
|
||||
collision result buffer from `[ebp-0x464]` to `[ebp-0x468]`. January packs BOTH trailing aggregates into slots
|
||||
the earlier block already owns (test_point -> [ebp-0x10..-8], down_vector -> [ebp-0x24..-0x1c]); ours packs only
|
||||
down_vector. laws_w2 A6 side law: nested-block locals may pack.
|
||||
- **Statement order from the aligned stream**: January computes `marker.x - actor_position->x` and
|
||||
`marker.y - actor_position->y` at @0x116/@0x130, i.e. in the OUTER block before `anchor_point` is assigned, and
|
||||
reuses those two cells in the `t` numerator and the perpendicular sign test - a named outer-block 2D vector is
|
||||
missing from HEAD. The tail is single-exit (`test al,al; je <fail>` with the success block inline, `mov al,1`
|
||||
and a separate `xor al,al` epilogue). `*surface_index_reference` is stored FIRST, its load interleaved into the
|
||||
x destination product.
|
||||
|
||||
All five defects are implemented by the w1 diagnostic body `x5.c`, which the cast admission now makes usable. On
|
||||
top of it this wave spent five evidence-led shapes:
|
||||
|
||||
| shape | change | result |
|
||||
| --- | --- | --- |
|
||||
| s1 | the two hand-written 2D sign dots become `dot_product2d(...)` calls (A35(1)+(2)) | 411 vs 412 insns; also closed the point_from_line2d x87 CSE pop and the magnitude_squared3d k,j,i sum order. KEPT |
|
||||
| s2 | `collision.t*down_vector.i + test_point.x` (t-first) | byte identical, inert. REVERTED |
|
||||
| s3 | `scale_vector3d(global_down3d, 4.0f, &down_vector);` for the three component multiplies (A35(1) leaf age) | 412 vs 412 instructions at IDENTICAL addresses; closed the stray `fxch st(1)` and the Y/Z destination products. KEPT |
|
||||
| s4 | surface_index store moved after the destination stores | worse. REVERTED |
|
||||
| s5 | dot-2 argument swap | byte identical - confirms laws_w3 A44. REVERTED |
|
||||
|
||||
Fixed point `scratch/workers/w3d_action_vehicle/fd_cand.c` (the current real file with only find_destination
|
||||
replaced): 1232 / 34, `[sha]` only - the `[size]` tag is gone - 412/412 instructions at identical addresses,
|
||||
whole-TU `== exact 13 residual 4`, guard passed, fakescan 0 leads, no new warnings, owner census +`_dot_product2d`
|
||||
and +`_scale_vector3d` (both A30/A4 accepted class) and the `.rdata` literal corrected to `__real@3dfae147`.
|
||||
|
||||
Eleven instructions remain, in four x87 leaf-age / scheduling families: the i-term of the first inlined
|
||||
`dot_product2d` (@0x284), both terms of the second (@0x36e, @0x377), the interleave of the
|
||||
`debug->field_F8 = chosen_point` loads against the `debug->field_F4 = TRUE` byte store (@0x3dd), and the X
|
||||
destination product (@0x483/@0x48c; Y and Z now match).
|
||||
|
||||
NOT LANDED under brief section 6: an already-written residual may be landed only when it becomes strict EXACT, and
|
||||
this candidate is not below the January size or relocation count, so the fuzzy allowance does not apply.
|
||||
|
||||
Reopen criterion: a source lever that flips x87 leaf age inside an inlined `dot_product2d` or a named-local product
|
||||
without an R3 operand swap (A35 is diagnostic only today); or an orchestrator decision to take the strictly-closer
|
||||
zero-credit body for its proven fidelity content - notably the emitted constant, where HEAD writes `0.1225f`
|
||||
(`__real@3dfae148`) and January writes `0.35f*0.35f` (`__real@3dfae147`).
|
||||
|
||||
## 4. `_action_vehicle_desirable` - initializer proven, blocked by the pending A42 ruling
|
||||
|
||||
Target 272 / 7 / e3b1ecf64be4fb82; HEAD 288 / 7 (100 versus 112 instructions).
|
||||
|
||||
Census: relocations 7/7 with identical targets; frame slots identical apart from one extra `[ebp-8]` read. The
|
||||
16-byte gap is therefore not a frame gap: it is our two extra reloads of the datum homed at `[ebp-8]` plus the
|
||||
position of `xor bl,bl`.
|
||||
|
||||
New evidence this wave: laws_w3 A38/A42 fire. January zeroes BL at 0x2b, before the `test al,4` dead-bit compare at
|
||||
0x2d, which A42 reads as proof of a declaration initializer. Shape `d4` - `boolean desirable = FALSE;` at the
|
||||
declaration plus `if (!TEST_FLAG(vehicle->object.damage_flags, _object_dead_bit)) { if (scripted_request) ... else
|
||||
... }`, with no redundant store anywhere - reproduces that hoisted `xor bl,bl` exactly and brings the row to
|
||||
**272 bytes, the January size**, at 101 versus 100 instructions. So the initializer, not the w1 `vd` redundant
|
||||
dead-arm store, is what the January entry proves.
|
||||
|
||||
Two families still differ in `d4`, and they are coupled:
|
||||
- January keeps `vehicle_index` in its incoming ESI and the vehicle datum in EDI (pushing only ebx and edi);
|
||||
`d4` moves `vehicle_index` into EDI and the vehicle datum into ESI, which flips the private ABI of the static.
|
||||
`_action_vehicle_perform` (768) and `_action_vehicle_setup_specific` (336), both exact at HEAD, then fall back to
|
||||
`[sha]` (`== exact 11 residual 6`). Losing an exact row is a regression, so `d4` is not landable.
|
||||
- January lays the not-dead block out of line (`je 0x58`, `jmp 0x31` back); the `d4` `if (!dead) { ... }` body stays
|
||||
inline. HEAD's `if (dead) X; else if ... else ...` chain reproduces the January layout, but its first arm cannot
|
||||
be empty, so the only spelling carrying BOTH the initializer and the chain is the w1 `vd` redundant store.
|
||||
|
||||
Reopen criterion: the pending A42 owner ruling on byte-proven redundant stores (laws_w3 C3 groups
|
||||
`_encounter_test_rule`, `_extract_sequence`, `_action_alert_next_position` and `_action_obey_command_begin` here).
|
||||
If granted, `vd` plus this wave's initializer evidence closes desirable at 272, and the w1 ledger records that
|
||||
`_action_vehicle_setup_impromptu` then has only the inlined x,z,y square order left.
|
||||
|
||||
## 5. Not targets this wave
|
||||
|
||||
`_action_vehicle_setup_impromptu` (464, [sha]) and `_distance3d` (64, [sha]) were not in the w3d TARGET list and no
|
||||
shapes were spent. w1 classified setup_impromptu as desirable-dependent (it inlines desirable) plus one x87
|
||||
square-order tie, and `_distance3d` as exhausted - a caller-independent header COMDAT body whose two frozen waves
|
||||
and the w1 `g1` rename-removal probe all produce z,x,y or z,y,x against the January x,y,z.
|
||||
@@ -0,0 +1,218 @@
|
||||
# `actor_moving.obj` — opus5 250K house-clean lane, wave w3d (structural pass, 2026-09-15)
|
||||
|
||||
## 1. Scope and provenance
|
||||
|
||||
- Translation unit `source/ai/actor_moving.c`; target `build/split/source/ai/actor_moving.obj`
|
||||
(January 2002 Xbox debug build, VC7 13.00.9254, XDK 3911).
|
||||
- Narrow structural pass over three rows only: `_actor_move_vector_avoidance`, `_actor_move_update`,
|
||||
`_actor_get_stopping_distances`. No other row was touched.
|
||||
- Measurements: `tools/campaign/gate.py --forbid-emitted-symbol _point_from_line3d`,
|
||||
`tools.campaign.tinfo`, `tools.campaign.alndiff` (`--include-equal` for the frame census),
|
||||
`tools.campaign.relocdiff --count-by-target --allow-structural`, `tools/campaign/branch_sweep.py`,
|
||||
`tools/fake_match_scan.py`.
|
||||
- Worker report with every census and shape: `scratch/workers/w3d_t3_actor_moving.md`.
|
||||
Candidates and dumps: `scratch/workers/w3d_t3_actor_moving/`
|
||||
(`base.c/base.obj`, `va_alndiff.txt`, `va_full.txt`, `mu_alndiff.txt`, `mu_full.txt`, `sd_alndiff.txt`,
|
||||
`slots.py`, `va1.c/va1.obj`, `sd1.c`).
|
||||
- Prior ledgers read: all fifteen `actor_moving_obj_*.md`, plus `scratch/WORKER_BRIEF.md`,
|
||||
`scratch/w2/laws_w2.md` and `scratch/w3/laws_w3.md` (sections A–E).
|
||||
|
||||
## 2. Baseline and result
|
||||
|
||||
| | exact | residual | unwritten |
|
||||
|---|---:|---:|---:|
|
||||
| baseline (real file at HEAD) | 29 | 7 | 0 |
|
||||
| final real-file gate | **29** | 7 | 0 |
|
||||
|
||||
`_point_from_line3d` guard passes at baseline and at the end. The unit has **no parks**, so there is no
|
||||
park drift. `git diff --stat -- source/ai/actor_moving.c` is **empty**: nothing landed, by design.
|
||||
`tools/fake_match_scan.py`: 0 review leads. Two shapes were spent in total (one per closable target),
|
||||
both reverted in scratch.
|
||||
|
||||
Duplicate prevention: `git log --all --oneline -- source/ai/actor_moving.c` (21 commits; the newest is the
|
||||
w3 landing `901a6ae73`, already in the baseline). `branch_sweep.py source/ai/actor_moving`: 18 unique blobs
|
||||
across all branches; **HEAD is the best of them** (exact 29; next best 26). No donor body exists for any of
|
||||
the three targets.
|
||||
|
||||
## 3. `_actor_move_vector_avoidance` — NOT LANDED; structural gap fully quantified, policy-blocked
|
||||
|
||||
Target 4144 / 135 relocs / `a8a8010c11375e82`; ours 4192 / 140. 1212 vs 1229 instructions; 236 hunks.
|
||||
|
||||
### Frame census
|
||||
January `sub esp, 0x60e0`; ours `sub esp, 0x60ec` — **+12 = exactly one `real_vector3d`.**
|
||||
The thirteen referenced cells of the `avoidance_data` aggregate have an identical shape in both builds
|
||||
(4 cells, an 8-byte hole, then 9 cells) and are simply shifted by 0xc; the small-local region starts at
|
||||
January `-0xa0` and ours `-0xac`. So ours allocates one surplus 12-byte cell and everything above it moves
|
||||
down. The last six aggregate cells (`avoidance_data.left`, `.up`) are read **4× by January and 3× by us**.
|
||||
|
||||
### Relocation census
|
||||
Only five rows differ, and they sum exactly to 140 − 135 = +5 (the `symbol:` vs
|
||||
`defined-noncode:.rdata:` spelling of `__real@…` is a csplit artifact and is combined):
|
||||
|
||||
| target | JAN | OURS | delta |
|
||||
|---|---:|---:|---:|
|
||||
| `__real@00000000` | 21 | 24 | +3 |
|
||||
| `__real@3f800000` | 24 | 28 | +4 |
|
||||
| `_avoid_ray_avoidance_weights+4` | 4 | 2 | −2 |
|
||||
| `_actor_move_transform_avoidance_vector` (REL32) | 1 | 2 | +1 |
|
||||
| `_global_zero_vector3d` | 3 | 2 | −1 |
|
||||
|
||||
Everything else matches, including both `_actor_move_test_avoidance_vector` calls, both
|
||||
`_actor_move_vector_avoidance_find_direction` calls, `_csmemset`, `_csmemcpy`, `_angle_between_vectors3d`,
|
||||
`_avoidance_directions+0/+4/+8`, `_sense_rays`, `_avoidance_rays` and the five
|
||||
`__real@3f1a36e2e0000000` normalize3d epsilons (**normalize3d inlines at the same 5 sites in both builds**).
|
||||
|
||||
### Defect (a): January inlines the transform at the `perpendicular` site — one mechanism, four deltas
|
||||
|
||||
January's only `_actor_move_transform_avoidance_vector` REL32 is at `+0xbff`, inside the `sharp_turn`
|
||||
block (`_angle_between_vectors3d` follows at `+0xc92`). Its **third** `_global_zero_vector3d` reference is at
|
||||
`+0xe74`, inside the `else if (maximum_sense_emergency > 0.f)` arm, and the bytes there are the transform
|
||||
body expanded with the `forward` group folded away:
|
||||
|
||||
```
|
||||
e73 mov eax,[_global_zero_vector3d] ; *direction_vector = *global_zero_vector3d
|
||||
e7b mov ecx,[eax]; edx,[eax+4]; eax,[eax+8] -> [ebp-0x24]/[ebp-0x20]/[ebp-0x1c] (= rotation)
|
||||
e86 movsx eax, word [ebp-0x28]; lea eax,[eax+eax*2]; shl eax,2 ; best_avoidance_direction*12
|
||||
e96 fld [eax+_avoidance_directions+8]; fchs ; component = -avoidance_directions[best].k
|
||||
ea5 fld [ebp-0x60bc] / [ebp-0x60b8] / [ebp-0x60b4] ; avoidance_data.left.i/.j/.k
|
||||
ecc fld [eax+_avoidance_directions+4]; fst [ebp-0x44] ; component = avoidance_directions[best].j
|
||||
ed5 fmul [ebp-0x60b0] / [ebp-0x60ac] / [ebp-0x60a8] ; avoidance_data.up.i/.j/.k
|
||||
```
|
||||
|
||||
No `perpendicular` storage exists (the two components are consumed straight off the x87 stack) and no
|
||||
`forward` term is emitted, because `perpendicular.i` is the compile-time constant `0.f`. That single
|
||||
mechanism explains every remaining census delta: the extra call in ours, the missing
|
||||
`_global_zero_vector3d`, the six missing `left`/`up` reads, and the +12 frame (VC7 scalar-replaces
|
||||
`perpendicular` once the call is inlined).
|
||||
|
||||
**Why it cannot be landed.** `__forceinline` is forbidden (forced inlining, brief §5). The transform body
|
||||
cannot be made cheaper: January's own bytes prove the `real component;` local (`fld [edx]` once at
|
||||
`+0x25/+0x45/+0x65`, then `fld st(0)` to duplicate) and the `*global_zero_vector3d` copy, and the row is
|
||||
already strict EXACT at 144. Writing the site out by hand is manual inlining (house rule 13 / laws_w3 R15);
|
||||
the w3 worker self-withheld exactly that form, and it is the only reason w1's `va6.c` reached 4128/135.
|
||||
|
||||
### Defect (b): byte-proven arm polarity in the avoid-ray weight loop (new, admissible)
|
||||
|
||||
January at `+0x4a0`: `cmp word [esi],0 ; fld 1.0f ; jne 0x51b` — the **clear** case is the fall-through
|
||||
THEN arm and the obstructed case is the block at the end. Our source spells it
|
||||
`if (avoidance_types[ray_index] != _actor_vector_avoidance_clear) {obstructed} else {clear}`, so we lay the
|
||||
obstructed arm out first (`je 0x4e5`). January's source is therefore
|
||||
`if (avoidance_types[ray_index] == _actor_vector_avoidance_clear) { … } else { … }`
|
||||
(laws_w3 A26 / laws_w2 A16: block layout follows source arm order).
|
||||
|
||||
### Defect (c): the weight multiply is tail-duplicated in January
|
||||
|
||||
January emits `fmul dword ptr [ecx]` (the strength-reduced `avoid_ray_avoidance_weights+4` walker) at FOUR
|
||||
sites (`+0x4be`, `+0x4f2`, `+0x511`, `+0x536`) — one per PIN arm of `clear_fraction` plus the obstructed
|
||||
arm; we emit it twice because all clear sub-arms jump to a shared multiply. This is the
|
||||
`−2 _avoid_ray_avoidance_weights+4` row.
|
||||
|
||||
### Shape measured
|
||||
|
||||
**va1** (`scratch/workers/w3d_t3_actor_moving/va1.c`) — defect (b) only. Result **4208 / 142**
|
||||
(baseline 4192 / 140; target 4144 / 135), 239 hunks.
|
||||
It **confirms (b) and (c) together**: the loop body becomes instruction-for-instruction January's shape and
|
||||
the `_avoid_ray_avoidance_weights+4` relocation row goes 2 → **4 = January's 4**. The only residual left
|
||||
inside the loop is a register fact: January keeps `obstructed` in **BL** (`xor bl,bl` in the preheader,
|
||||
`mov bl,1` at `+0x53c`) while we spill it to `[ebp-0x39]`, which costs the +16 bytes.
|
||||
NOT landed: an already-written residual may land only when strict EXACT (brief §6), and va1 is not strictly
|
||||
closer on size, relocations or hunk count.
|
||||
|
||||
### Reopen criterion
|
||||
|
||||
(i) A measured VC7 mechanism that makes a same-TU, non-`__inline`, 13-statement function inline at ONE call
|
||||
site — the one whose vector argument has a compile-time-zero component — while staying a call at the other;
|
||||
or (ii) an owner ruling admitting the written-out transform at the `perpendicular` site. Start from `va1.c`
|
||||
(loop shape already correct) plus a cause for `obstructed` living in BL.
|
||||
|
||||
## 4. `_actor_move_update` — SKIPPED-EXHAUSTED; the `[size]` tag is NOT structural
|
||||
|
||||
On the brief's DO-NOT-SPEND list; touched only for the mandated census, zero shapes spent.
|
||||
|
||||
- **Frame census: identical.** January `sub esp,0x44`, ours `sub esp,0x44`; every `[ebp-N]` cell exists in
|
||||
both builds, none is JAN-only or OURS-only.
|
||||
- **Relocation census: 72 / 72, every row equal.** No missing or extra call, global, literal or assert
|
||||
string (including both `_normalize3d`, both `_normalize2d`, both `_valid_real_normal3d`,
|
||||
`_vector_from_points3d`, `_rotate_vector_about_axis`, `_actor_move_vector_avoidance`,
|
||||
`_actor_move_calculate_movement`).
|
||||
- Only four slot use-counts differ: `[ebp-0x8]` (`crouch`) **20 vs 9**, `[ebp+8]` (the `actor_index`
|
||||
parameter home) 6 vs 4, `[ebp-0x3c]` 6 vs 5, `[ebp-0xc]` 6 vs 7.
|
||||
|
||||
The entire 48-byte / 19-instruction gap is **register-allocation write-back traffic**: January writes
|
||||
`crouch` back to its home at every assignment (`xor bl,bl; mov byte [ebp-8],bl`, sixteen sites) even though
|
||||
`[ebp-8]` is never read back as a byte, while our build elides eleven of those write-through stores; plus
|
||||
two extra `[ebp+8]` re-reads and one zero-register pin (`cmp word [esi+0x15e], 0` vs our
|
||||
`cmp word [esi+0x15e], di`). A25's join-store lever already fires correctly for the one if/else that assigns
|
||||
`crouch` — both builds emit the merged `mov byte [ebp-8],bl` at the join (JAN `+0x400`, ours `+0x3f3`) — and
|
||||
the remaining sixteen sites are switch-case constant assignments with no arm to merge.
|
||||
|
||||
**Recommendation:** reclassify this row from `[size]`-structural to an allocation tie so that no future wave
|
||||
spends a structural pass on it. Reopen criterion: a measured VC7 lever that forces write-through of a byte
|
||||
local sharing a recycled, address-taken 4-byte cell.
|
||||
|
||||
## 5. `_actor_get_stopping_distances` — NOT LANDED; the laws_w3 C1 reopen criterion is REFUTED
|
||||
|
||||
Target 464 / 11 relocs; ours **464 / 11**. 156 vs 156 instructions. Frame identical. Relocation census:
|
||||
**0 differing rows**. Exactly one real hunk:
|
||||
|
||||
```
|
||||
T f6 fld dword ptr [esi + 0x24] ; biped->object.forward.i
|
||||
T f9 fmul dword ptr [esi + 0x18] ; biped->object.translational_velocity.i
|
||||
O f6 fld dword ptr [esi + 0x18]
|
||||
O f9 fmul dword ptr [esi + 0x24]
|
||||
```
|
||||
|
||||
`laws_w3` C1 (A35(4)) asks for "an earlier by-name read of the same fields … an earlier hand expansion that
|
||||
ages velocity/forward before the dot". A full scan of the **target** stream for `esi`-based field reads
|
||||
gives `[esi] 2, [esi+0x18] 2, [esi+0x1c] 2, [esi+0x20] 2, [esi+0x24] 2, [esi+0x28] 2, [esi+0x2c] 2` —
|
||||
each velocity/forward component is read exactly twice in January, once in the vehicle dot at `+0x89` and
|
||||
once in the biped dot at `+0xf6`, and `[esi]` twice for the two `definition_index` loads. **There is no
|
||||
earlier read of `translational_velocity` or `forward`, by name or otherwise, anywhere in January's
|
||||
`_actor_get_stopping_distances`**, so the A35 leaf-aging mechanism cannot explain this row.
|
||||
|
||||
What January does show: the two dots are the same inlined `dot_product3d` spelling, and January flips the
|
||||
i-term in the biped one only (vehicle `+0x89` `fld [esi+0x18]; fmul [esi+0x24]`, biped `+0xf6`
|
||||
`fld [esi+0x24]; fmul [esi+0x18]`), with the j and k terms identical at both sites. The instructions
|
||||
interleaved into the biped dot (`mov ecx,eax` / `mov al,[ecx+0x2f4]` / `add esp,8` / `test al,4`) are
|
||||
byte-identical to ours, so the flip is decided inside VC7's commutative x87 lowering (laws_w3 A44, "the
|
||||
inliner canonicalises inline-call evaluation order by liveness"), not by anything visible at the call site.
|
||||
laws_w2 R3 forbids an operand swap justified only by x87 load order, and the helper body lives in
|
||||
`source/math/real_math.h`, which workers may not edit.
|
||||
|
||||
Shape measured — **sd1** (`scratch/workers/w3d_t3_actor_moving/sd1.c`): move `biped_definition_get` after
|
||||
the dot (`struct biped_definition *definition;` + assignment) so the leaf creation order changes.
|
||||
Result **448 != 464 [size]** — structurally worse, refuted; it also positively proves that January's
|
||||
definition fetch precedes the dot (`call tag_get` at `+0xd6` before the first `fld` at `+0xdb`).
|
||||
|
||||
**Reopen criterion (replaces the refuted A35(4) one):** a measured VC7 rule that decides the `fld` operand of
|
||||
a commutative `mem*mem` product differently for two identically-spelled inlined `dot_product3d` calls inside
|
||||
one function, or an owner ruling on editing `real_math.h`'s `dot_product3d` association.
|
||||
|
||||
## 6. Proposals
|
||||
|
||||
No header, `symbols.json`, `parked.json` or other config change is needed or proposed by this wave.
|
||||
|
||||
Two board-hygiene items for the orchestrator (no bytes, no source change):
|
||||
|
||||
1. Reclassify `_actor_move_update` (3136) from a `[size]`-structural residual to an allocation tie
|
||||
(census in §4: frame identical, relocations identical).
|
||||
2. Retire the `laws_w3` C1 row for `_actor_get_stopping_distances` (464): its stated reopen criterion is
|
||||
refuted by January's own bytes (§5). It is a one-instruction-pair commutative x87 tie.
|
||||
|
||||
## 7. Do-not-repeat (added)
|
||||
|
||||
- `_actor_move_vector_avoidance`: do not re-derive the frame gap — it is `perpendicular`, and it disappears
|
||||
only when the transform is inlined at that site. Do not re-measure a call/`__inline`/`__forceinline` form
|
||||
of the transform (w1 already measured `__forceinline` at 4304). The arm polarity of the avoid-ray weight
|
||||
loop is settled by January's `jne` at `+0x4aa` (clear arm first); va1 measures it at 4208/142.
|
||||
- `_actor_move_update`: do not spend a structural pass — frame and relocations are identical; the gap is
|
||||
`crouch` write-back traffic.
|
||||
- `_actor_get_stopping_distances`: do not look for an earlier by-name read of the biped velocity/forward
|
||||
components; January has none. Do not re-order the `biped_definition_get` fetch (sd1, 448 [size]).
|
||||
|
||||
## 8. Disposition
|
||||
|
||||
`actor_moving.obj` remains active, not Matching: 29 / 36 strict exact, unchanged by this wave. No new exact
|
||||
credit, no fuzzy landing, no park change, no owner change, and the real file is byte-identical to HEAD.
|
||||
The wave's product is the three censuses, the two new measured facts in `_actor_move_vector_avoidance`, and
|
||||
the refutation of one third-pass candidate.
|
||||
@@ -0,0 +1,219 @@
|
||||
# `actors.obj` Opus5 250K lane, wave w3d: structural pass on the three size-gap parks (2026-09-15)
|
||||
|
||||
## Scope and provenance
|
||||
|
||||
- Translation unit `source/ai/actors.c`, target `build/split/source/ai/actors.obj`.
|
||||
- Lane `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/250k-house-clean-20260915`.
|
||||
`source/ai/actors.c` is unchanged since `5a84a96dd`; **the real file was not modified in this wave**
|
||||
(`git diff --stat -- source/ai/actors.c` is empty).
|
||||
- Compiler: XDK VC7 13.00.9254 `/O2 /Oy- /DDEBUG /Dxbox` (build.ninja, unchanged).
|
||||
- Assignment: STRUCTURAL PASS over `_actor_input_update` (2384 vs 2400), `_actor_place` (800 vs 816, w3 C1
|
||||
third-pass row with law A24) and `_actor_customize_unit` (640 vs 672). All three are parked, so every
|
||||
measurement is scratch-only.
|
||||
- Evidence used: the January split COFF and its listings; `relocdiff --count-by-target --allow-structural`;
|
||||
per-function `[ebp±N]` frame censuses; `scratch/w2/laws_w2.md` and `scratch/w3/laws_w3.md` applied as
|
||||
detectors; HCEA `blam/ai/actor_place.c`, `actor_customize_unit.c`, `actor_input_update.c` (behaviour and
|
||||
statement order only); the w2/w3 scratch bodies re-gated at HEAD.
|
||||
- Worker notes: `scratch/workers/w3d_t3_actors.md`. Scratch: `scratch/workers/w3d_t3_actors/`.
|
||||
|
||||
## Baseline and final (real file)
|
||||
|
||||
`gate.py source/ai/actors --all --forbid-emitted-symbol _point_from_line3d`
|
||||
|
||||
- **Baseline:** 70 exact, 6 residual, 0 unwritten; point guard passes
|
||||
(`scratch/workers/w3d_t3_actors/baseline_gate.txt`).
|
||||
- **Final:** byte-identical listing (`final_gate.txt`; `diff` against the baseline is empty). Guard passes.
|
||||
- **Parks:** `scratch/parkcheck.py` reports 5 parks, 0 drift.
|
||||
- **Fake scan:** `tools/fake_match_scan.py source/ai/actors.c`, 0 review leads.
|
||||
- **Owner census:** not applicable to the real file (unchanged); measured for the scratch candidates below.
|
||||
- **branch_sweep:** 34 unique blobs, current HEAD blob is the best (70 exact).
|
||||
|
||||
| Function | Target | Real file | Park class | w3d outcome |
|
||||
| --- | ---: | ---: | --- | --- |
|
||||
| `_actor_create_for_unit` | 624 / 29 | 624 | register-allocation | not a target this wave |
|
||||
| `_actor_customize_unit` | 640 / 26 | 672 | unclassified | **not landed** — full census, 2 shapes, tie |
|
||||
| `_actors_spawn_from_unit` | 672 / 29 | 672 | instruction-scheduling | not a target this wave |
|
||||
| `_actor_place` | 800 / 41 | 816 | unclassified | **not landed; STRUCTURAL GAP CLOSED in scratch (800/41)** |
|
||||
| `_actor_input_update` | 2384 / 89 | 2400 | unclassified | **not landed** — census shows no new structural gap |
|
||||
| `_actor_unit_control` | 784 / 48 | 784 | — | owner-blocked (units.c protected, E1) |
|
||||
|
||||
## 1. `_actor_place` — the structural gap is a STATEMENT ORDER defect (A20)
|
||||
|
||||
### Census
|
||||
|
||||
- **Relocations:** 41/41, identical target multiset. No missing or extra call.
|
||||
- **Frame:** January `sub esp,0x190`, ours `sub esp,0x194`. Ours carries **one extra own 4-byte cell**.
|
||||
- January: `encounter_name[256]` at `[ebp-0x190]`, `placement_data` (0x88) at `[ebp-0x90]`,
|
||||
`swarm` at `[ebp-8]`, `initially_braindead` at `[ebp-4]`; `unit_index` lives in `initial_variant`'s
|
||||
parameter home `[ebp+0x1c]` and `initial_state` in `upgrade_major`'s home `[ebp+0x18]`;
|
||||
`default_state` and `actor_definition` never leave EDI.
|
||||
- Ours: the same aggregates shifted by 4, **plus `unit_index` in its own cell `[ebp-8]`**, `swarm` pushed to
|
||||
`[ebp-0xc]`; `actor_definition` spilled to `[ebp+0x18]` at 0x9e and `default_state` homed as well.
|
||||
- So ours spills six values into five slots where January spills four into four. The two extra spilled values
|
||||
are `actor_definition` and `default_state`, both of which January keeps in EDI, because January takes EBX
|
||||
for `starting_location` at entry (`push ebx` precedes the `starting_location` assert) while ours puts
|
||||
`starting_location` in EDI and shrink-wraps EBX onto `squad_index` at 0x17b.
|
||||
|
||||
### Derived defect and fix
|
||||
|
||||
January's independent stores inside `if (encounter_index != NONE)` are, in order, `mov byte [ebp-4],dl`
|
||||
(`initially_braindead`, 0x224) and then `mov word [ebp+0x18],cx` (`initial_state`, 0x227). By law A20
|
||||
(independent stores keep source order) the braindead flag is assigned FIRST. HCEA `blam/ai/actor_place.c` has
|
||||
exactly that order. Our body assigned `initial_state`, `default_state`, then the flag.
|
||||
|
||||
**`pl_o1`** — move
|
||||
`initially_braindead = TEST_FLAG(encounter_definition->flags, _encounter_braindead_bit);`
|
||||
ahead of the two squad-state assignments, nothing else:
|
||||
|
||||
- `_actor_place` **800 / 41**, normalized sha
|
||||
`ce8124335417a81a5e1448d24f1fa9176741033aa2c9ffc59b15faa6c8b23c03` (target 800/41 `a4258acc…`).
|
||||
- frame becomes 0x190 and the whole slot map becomes January's, including `unit_index` in `[ebp+0x1c]`
|
||||
and `initial_state` in `[ebp+0x18]`;
|
||||
- whole-TU gate: 70 exact, 6 residual, guard passes, no baseline-exact row changes;
|
||||
- owner census vs `build/base/source/ai/actors.obj`: 190/190, no candidate-only and no base-only owner;
|
||||
- **no header or config change is needed for this.**
|
||||
|
||||
### Remaining residual after `pl_o1` (+ the E11 type fact)
|
||||
|
||||
With E11 applied in a shadow tree (`pl_n1`, 800/41,
|
||||
`3a33538607b4cb44c23e441317ca7e4236b613b3e226db7a7f93269a71ee2295`) January's
|
||||
`movsx <16-bit>, byte ptr [eax+0x12]` argument form is reproduced and the body is byte-identical up to 0x249.
|
||||
The entire residual is then the emission order of three argument pre-loads for `actor_create_for_unit`:
|
||||
|
||||
```
|
||||
January: mov edx,[ebp+0x18] (arg 9 initial_state)
|
||||
xor ecx,ecx / mov cx,[eax+0x1a] (arg 11 command_list_index)
|
||||
movsx ax, byte [eax+0x12] (arg 12 noncombat_sequence_id)
|
||||
ours: movsx dx, byte [eax+0x12] (arg 12)
|
||||
movsx eax, word [eax+0x1a] (arg 11, dest==base -> the w2 lab-t11 movsx form)
|
||||
mov ecx,[ebp+0x18] (arg 9)
|
||||
```
|
||||
|
||||
January evaluates 9, 11, 12 (left to right), ours 12, 11, 9. Everything after that (ecx/eax/edx rotations in the
|
||||
two warning blocks and the epilogues) follows from it. 270 vs 269 instructions; sizes and relocations equal.
|
||||
|
||||
### Shapes (7)
|
||||
|
||||
| shape | change | result |
|
||||
| --- | --- | --- |
|
||||
| pl_d1 | `long unit_index;` declared before `placement_data` | 816/41, identical to base |
|
||||
| pl_d2 | pointer/scalar declarations moved after the aggregates | 816/41, identical to base |
|
||||
| **pl_o1** | braindead assignment first (A20 + HCEA) | **800/41 `ce812433…` — structural gap closed** |
|
||||
| pl_o2 | pl_o1 + squad fetch and state assignments in a nested block | 800/41 `9466a0dc` |
|
||||
| pl_n1 | pl_o1 + E11 `short noncombat_sequence_id` (shadow header) | 800/41 `3a335386` |
|
||||
| pl_n2 | pl_o2 + E11 | 800/41 `c932b184` |
|
||||
| pl_n3 | pl_n1 + `initial_state`/`default_state` declaration swap | identical to pl_n1 (inert) |
|
||||
|
||||
## 2. `_actor_customize_unit` — the `[size]` tag is a coalescing tie, not a structural gap
|
||||
|
||||
### Census
|
||||
|
||||
- **Relocations:** 26/26, identical target multiset.
|
||||
- **Frame:** `sub esp,0x98` in both, four dword slots plus `placement_data` in both, identical parameter homes.
|
||||
The only difference is the occupant of `[ebp-0xc]`: January spills the `change_colors` element pointer there,
|
||||
ours spills `actor_variant_definition`. A24 does not fire.
|
||||
|
||||
### Byte accounting (+21 real bytes)
|
||||
|
||||
3 (`actor_variant_definition` spill at 0x25) + 2 (memory-only loop-counter init) + 4 (`jle` rel8->rel32)
|
||||
− 3 (`change_colors` kept in EBX) + 3 (memory-only loop increment) + 3 + 3 + 3 (three `unit` reloads at joins)
|
||||
+ 8 (two `je error` rel8->rel32 because the error block is sunk past the first epilogue) − 5 (camo block).
|
||||
|
||||
Root cause: callee-saved coalescing. January pairs EDI = `unit` then the loop counter and keeps EBX =
|
||||
`actor_variant_definition` for the whole function; ours pairs EBX = `unit_index` then `change_colors` and keeps
|
||||
`unit` re-cached in ESI at three joins, which leaves the loop counter in memory. The error-block sink and all
|
||||
three jump growths are downstream of the extra join blocks.
|
||||
|
||||
### Shapes (2)
|
||||
|
||||
- **cu1** — equipment powerup test written positive-first
|
||||
(`!= _equipment_powerup_none && != _equipment_powerup_grenade`, placement body as the then-arm, `error()` in
|
||||
the else arm), which is the form HCEA `blam/ai/actor_customize_unit.c` keeps. Result 672/26, sha `0a9c42b8`,
|
||||
**byte-identical to base**: VC7 canonicalises the polarity and still sinks the error block.
|
||||
- **cu2** — `short change_color_index;` declared ahead of `struct object_placement_data placement_data;`.
|
||||
672/26, byte-identical to base.
|
||||
|
||||
Classification: SKIPPED-EXHAUSTED / register-coalescing tie.
|
||||
|
||||
## 3. `_actor_input_update` — no structural gap remains beyond the existing scratch body
|
||||
|
||||
### Census
|
||||
|
||||
- **Relocations:** 89/89, identical target multiset.
|
||||
- **Frame:** `sub esp,0x24` in both, the same seven slots plus `[ebp+8]`. Only the reference counts differ
|
||||
(`[ebp-4]` 4 vs 9, `[ebp-8]` 7 vs 3, `[ebp-0x10]` 2 vs 4): ours makes one extra `unit` reload, which raises
|
||||
`unit`'s reference density and swaps the `-4`/`-8` occupants (the w2 finding, re-confirmed).
|
||||
|
||||
### The structural gap is already closed by `iu3`
|
||||
|
||||
`scratch/workers/w3_actors/iu3.c`, re-gated at this HEAD as `scratch/workers/w3d_t3_actors/iu3.obj`:
|
||||
**2384 / 89**, sha `f6ff0151006bb86f` — target size and target relocation count, 70 exact rows unchanged,
|
||||
guard passes. Its residual has one root:
|
||||
|
||||
1. `0x6a6` — January `lea edx,[ebx+0x1ec]` keeps EBX = `unit` live into the join, where `add ebx,0x210` is its
|
||||
last use; ours does `add ebx,0x1ec` and then reloads `mov eax,[ebp-4]` at the join.
|
||||
2. That reload produces the `-4`/`-8` slot swap and the register rotations at 0x4d3 / 0x517 / 0x590 / 0x5b0
|
||||
(all pure renames — the apparent `game_team_is_enemy` argument difference in the aligned diff is an
|
||||
alignment artifact; the two byte streams are structurally identical there).
|
||||
3. Five of the twelve products in the two inlined `cross_product3d` bodies `fld` the other leaf (A22/A35). The
|
||||
helper argument roles were re-verified against HCEA and are correct, as is the `normalize2d` polarity
|
||||
(January `test ah,0x41; jne` makes the `> 0` case the fall-through, which is our then-arm) and the
|
||||
`burning_to_death` / `melee_attacker_attached` / `delayed_attached_projectile_index` store order.
|
||||
|
||||
No new shapes were spent: the census produces no structural defect beyond what `iu3` already fixes, and the w3
|
||||
do-not-repeat table (iu3, iu4) plus R13 bind the tie budget.
|
||||
|
||||
## Orchestrator proposals
|
||||
|
||||
### P-A — `_actor_place` park re-baseline (actors.c only, no header edit)
|
||||
|
||||
Apply the `pl_o1` statement reorder and re-baseline the `_actor_place` entry in `config/parked.json` from
|
||||
`816 / 41 / ff120b98736b6eb6…` to `800 / 41 / ce8124335417a81a5e1448d24f1fa9176741033aa2c9ffc59b15faa6c8b23c03`.
|
||||
Evidence: A20 store order in January (0x224 braindead before 0x227 initial_state) plus HCEA
|
||||
`blam/ai/actor_place.c`. Effect: frame 0x194 -> 0x190 with January's exact slot map; 70 exact rows unchanged;
|
||||
owner set identical to `build/base`; guard passes; other four parks unchanged. Strictly closer, zero strict
|
||||
credit (R11), so it is worth landing as the prerequisite for the reopen criterion below or together with P-B.
|
||||
|
||||
### P-B — E11 upgraded: `short noncombat_sequence_id` (header packet)
|
||||
|
||||
- Text: `source/ai/actors.h:1038` `\tchar noncombat_sequence_id);` -> `\tshort noncombat_sequence_id);`, and the
|
||||
matching definition line in `source/ai/actors.c`. Do **not** change the struct field at `actors.h:457`
|
||||
(`char noncombat_sequence_id;`) — January's `movsx <16-bit>, byte ptr [eax+0x12]` proves a signed *byte
|
||||
field* read into a *short parameter*.
|
||||
- Evidence: January `_actor_place` +0x252; the w2 lab t11 table (`signed char` field + `short` param ->
|
||||
`movsx r16, byte ptr`; + `char` param -> `xor r,r; mov r8`, which is today's form).
|
||||
- Consumers: 46 `actors.h` includers. Only three TUs call `actor_create_for_unit`: `actors.c` (2 sites),
|
||||
`ai.c` (`ai_disconnect_from_structure_bsp`), `ai_script.c` (`ai_scripting_attach_unit`,
|
||||
`ai_scripting_attach_free`); the latter two push the constant 0.
|
||||
- Measured through a shadow header (pickup proven with an injected `#error`): `source/ai/ai` stays 42/46 and
|
||||
`source/ai/ai_script` stays 112/116 with identical residual rows; `actors.obj` stays 70/76 with
|
||||
`_actor_place` at 800/41 `3a33538607b4cb44…`. A full-board rebuild is still required.
|
||||
- Note: VC7 13.00.9254 does not reject a `char` prototype against a `short` definition, so both halves must be
|
||||
changed together for source correctness.
|
||||
|
||||
### P-C — unchanged
|
||||
|
||||
units.h/units.c `short animation_impulse` (E1) remains the only blocker for `_actor_unit_control` 784;
|
||||
`units.c` is protected in this lane.
|
||||
|
||||
## Do-not-repeat (additions)
|
||||
|
||||
| Function | Spellings that are inert or rejected |
|
||||
| --- | --- |
|
||||
| customize_unit | positive-first equipment powerup test (byte-identical); `change_color_index` declaration moved ahead of `placement_data` (byte-identical) |
|
||||
| place | `unit_index` / `actor_definition` declaration-position moves (byte-identical); `initial_state`/`default_state` declaration swap on top of pl_o1 (inert); squad fetch in a nested block (pl_o2/pl_n2, 800/41 but a different sha and no closer) |
|
||||
|
||||
## Reopen criteria
|
||||
|
||||
- `_actor_place`: any source form that makes VC7 evaluate the 9th argument (`initial_state`) before the two
|
||||
`starting_location->` field arguments of `actor_create_for_unit`. With pl_o1 + P-B in place that is the only
|
||||
remaining difference and the function becomes strict exact (800 B).
|
||||
- `_actor_customize_unit`: a same-compiler allocation donor (or January local records) explaining why EBX goes
|
||||
to the definition pointer and the loop counter is enregistered while `change_colors` spills.
|
||||
- `_actor_input_update`: unchanged — a lever for the `unit` live-range split at the aiming join; start from
|
||||
`scratch/workers/w3d_t3_actors/iu3.c` (2384/89).
|
||||
|
||||
## Disposition
|
||||
|
||||
The object is still 70/76 strict. Nothing was landed, no fuzzy credit was taken, and the real file is
|
||||
byte-identical to HEAD.
|
||||
@@ -0,0 +1,215 @@
|
||||
# `effects.obj` Opus5 250K house-clean lane, wave w3d: structural pass (2026-09-15)
|
||||
|
||||
## Scope and provenance
|
||||
|
||||
- Translation unit `source/effects/effects.c`, target `build/split/source/effects/effects.obj`.
|
||||
- Compiler VC7 13.00.9254 (XDK 3911). No header, config, flag or tooling change.
|
||||
- Lane `opus/250k-house-clean-20260915` (worktree `opus5-150k-house-clean-20260914`), worker `w3d:effects`.
|
||||
- Assigned TARGETs (both parked and locked): `_effect_generate_particles` (1888, `[size 2048!=1888,
|
||||
relocs 55!=46, sha]`) and `_effect_allowed_by_environment` (128, `[size 112!=128, sha]`).
|
||||
`_effect_generate_part` was explicitly out of scope this wave.
|
||||
- Every experiment ran on scratch copies under `scratch/workers/w3d_effects/`.
|
||||
**The production file is unchanged** (`git diff --stat -- source/effects/effects.c` is empty).
|
||||
- Worker notes with the mandatory censuses and per-function blocks: `scratch/workers/w3d_effects.md`.
|
||||
- Evidence: the January target COFF (disassembly, relocations, jump tables, frame slots); the ledgers
|
||||
`effects_obj_opus5_150k_w2_20260914.md`, `effects_obj_opus5_100k_waveD_20260914.md` and
|
||||
`effects_obj_opus_reconciliation_20260904.md`; `config/parked.json`; the later HCEA per-function sources
|
||||
(semantics only); effects.c's own exact functions for idiom precedent.
|
||||
|
||||
## Baseline (real file, HEAD)
|
||||
|
||||
`== exact 38 residual 3 unwritten 0 (of 41 listed)`. Point guard passes; park drift 0/3.
|
||||
|
||||
| residual (all parked) | target | base | tag |
|
||||
|---|---|---|---|
|
||||
| `_effect_allowed_by_environment` | 128/10 | 112/10 | size, sha |
|
||||
| `_effect_generate_part` | 992/27 | 976/28 | size, relocs, sha (not a target) |
|
||||
| `_effect_generate_particles` | 1888/46 | 2048/55 | size, relocs, sha |
|
||||
|
||||
## Census 1 - `_effect_allowed_by_environment`
|
||||
|
||||
**Frame census.** January: `push ebp; mov ebp,esp; push ecx` - exactly one 4-byte cell, read once at
|
||||
`0x5f` (`mov al, byte ptr [ebp-1]`) and **never written on any path** (the whole 59-instruction body was
|
||||
disassembled). Ours: frameless, no `[ebp-N]` at all.
|
||||
|
||||
**Control flow (jump-table order = case value order).**
|
||||
|
||||
```
|
||||
ja 0x45 -> default
|
||||
case 0 anywhere @0x13 : mov al,1
|
||||
case 1 air @0x19 : call scenario_location_underwater; neg al; sbb al,al; inc al
|
||||
case 2 water @0x2f : call scenario_location_underwater
|
||||
case 3 vacuum @0x3f : xor al,al
|
||||
default @0x45 : display_assert(NULL, file, 888, TRUE); system_exit(-1);
|
||||
mov al,[ebp-1] <- returns an UNINITIALISED stack byte
|
||||
```
|
||||
|
||||
**Relocation census.** 10 vs 10, every target identical; only the four internal jump-table entries differ -
|
||||
January `[0x13,0x19,0x2f,0x3f]` (four distinct blocks), HEAD `[0x5b,0x16,0x2e,0x5b]` (case 0 and case 3
|
||||
merged by the `boolean allowed = environment == _effect_environment_anywhere;` initializer). **No call or
|
||||
global is missing on either side.**
|
||||
|
||||
**Structural defect derived.** The 16-byte gap is one extra exit block plus the 4-byte frame. January has
|
||||
five distinct exits; HEAD has four, because its default arm returns a value that is neither `TRUE` nor
|
||||
`FALSE` but a load of a never-written local.
|
||||
|
||||
## Census 2 - `_effect_generate_particles`
|
||||
|
||||
**Relocation census by target** (`relocdiff --count-by-target --allow-structural`): the whole +9 is one
|
||||
inlined copy of the environment helper.
|
||||
|
||||
| target | January | HEAD | delta |
|
||||
|---|---:|---:|---:|
|
||||
| `_effect_allowed_by_environment` (call) | 1 | 0 | -1 |
|
||||
| `_scenario_location_underwater` | 0 | 2 | +2 |
|
||||
| effects.c `__FILE__` literal | 1 | 2 | +1 |
|
||||
| `_display_assert` | 1 | 2 | +1 |
|
||||
| `_system_exit` | 1 | 2 | +1 |
|
||||
| `internal:` (table base + 4 entries) | 0 | 5 | +5 |
|
||||
| all 26 other targets | = | = | 0 |
|
||||
|
||||
**Frame census** (`scratch/workers/w3d_effects/slots.py`): January `sub esp,0xe4`, HEAD `sub esp,0xe8`.
|
||||
The aggregate set is identical, shifted one cell; the extra cell is the home of `count_delta`
|
||||
(`mov [ebp-0xc8],ebx` @0x12d, reloaded @0x190) which January keeps in EBX while homing `instance` at
|
||||
`[ebp-0xc]`. That is a register-role swap downstream of the inline (laws_w3 A44).
|
||||
|
||||
**Conclusion.** No missing statement, call, local or loop shape: the entire structural gap was one inline
|
||||
decision.
|
||||
|
||||
## Results
|
||||
|
||||
Candidate: `scratch/workers/w3d_effects/c1_crlf.c` (CRLF, three hunks against the real file; LF twin
|
||||
`c1.c`, object `c1_crlf.obj`).
|
||||
|
||||
| row | HEAD | candidate c1 | change |
|
||||
|---|---|---|---|
|
||||
| `_effect_allowed_by_environment` | 112/10 `[size, sha]` | **128/10 `[sha]`** | size + relocation identity now exact |
|
||||
| `_effect_generate_particles` | 2048/55 `[size, relocs, sha]` | **1888/46 `[sha]`** | size + relocs now exact, frame exact |
|
||||
| all 38 exact rows | EXACT | EXACT | unchanged (row-by-row diff) |
|
||||
| `_effect_generate_part` | 976/28 | 976/28 | untouched |
|
||||
|
||||
Neither row becomes strict EXACT, so this wave claims **zero strict bytes**. Both rows are parked, so the
|
||||
candidate is a **coupled park re-baseline** (laws_w3 "newly ADMITTED": a coupled re-baseline of a still-parked
|
||||
callee to a strictly closer body when a control proves the caller needs it). The control is u1/v0:
|
||||
with the same caller body, the helper is out of line only when the callee body changes.
|
||||
|
||||
### The three source hunks
|
||||
|
||||
1. `effect_allowed_by_environment`: `boolean allowed = FALSE;` and `case _effect_environment_anywhere:
|
||||
allowed = TRUE; break;` replace the computed `allowed = environment == _effect_environment_anywhere;`
|
||||
initializer. Semantics are unchanged (anywhere TRUE, air `!underwater`, water `underwater`, vacuum and
|
||||
invalid FALSE) and the default-deny reading matches HCEA's reconstruction of the out-of-range case.
|
||||
2. `effect_generate_particles`, attached arm: the file's own idiom
|
||||
`short node_index = instance->node_designator; if (node_index != NONE) node_index &= ...;` replaces the
|
||||
`?:`. Proven by January 0x3de-0x402, which stores the designator register itself in the NONE arm.
|
||||
In-file precedent: effects.c:1672 and effects.c:1804 (both inside the exact-adjacent
|
||||
`_effect_generate_part`).
|
||||
3. `effect_generate_particles`, count computation: a separate `long last_particle_count` statement
|
||||
evaluated first. Proven by January 0xd6-0x11d, which calls the integral with `last_event_fraction`
|
||||
([ebp-0xc4]) first and homes that result in a 4-byte slot before calling with `event_fraction`
|
||||
([ebp-0x14]). With this hunk the candidate is byte-identical to January from 0xd6 through 0x11d,
|
||||
including the `movzx edx, byte ptr [ebx+edi+0xdc]` addressing HEAD does not produce.
|
||||
|
||||
### NEW LAW (w3d-1): the callee body, not the call site, sets a TU-static's inline decision
|
||||
|
||||
Call-site levers are inert at this site: respelling the inner `for`-with-increment as a `while` (A23
|
||||
detector, g1) and adding an `if (count_delta > 0)` nesting level instead of `continue` (inline-frequency
|
||||
detector, g4) both leave the row at 2048/55 with the helper still inlined. The callee body is decisive and
|
||||
orders by cost:
|
||||
|
||||
| env body | env | generate_particles | generate_parts |
|
||||
|---|---|---|---|
|
||||
| per-case `return`s (cheapest) | 112/10 | 2016/55 (inlined) | **688/24 - exact row lost** |
|
||||
| computed initializer (HEAD) | 112/10 | 2048/55 (inlined) | EXACT (called) |
|
||||
| constant initializer + one assigning case | 128/10 | 1952/46 (**called**) | EXACT (called) |
|
||||
| uninitialised local (January, prohibited) | EXACT | 1952/46 (**called**) | EXACT (called) |
|
||||
|
||||
Detector for other units: when January *calls* a TU-static at a hot site that we inline, do not respell the
|
||||
caller - count January's distinct exit blocks in the CALLEE and reproduce them.
|
||||
|
||||
### The park's recorded negative is stale
|
||||
|
||||
`config/parked.json` for `_effect_allowed_by_environment` says "Simple FALSE initialization and
|
||||
assignment-after-assert forms also produced 112 bytes but caused both callers to inline the assertion body
|
||||
and regress broadly". The same entry dates itself by naming "the donor's better 1,616-byte and 592-byte
|
||||
shapes in both real callers"; `_effect_generate_particles` was re-based to the corrected per-instance loop
|
||||
nest (2048/55) on 2026-09-14. Against that caller the simple-FALSE-initialization form measures 128/10 with
|
||||
both callers intact. The assignment-after-assert half of the premise still holds (112, probe e5).
|
||||
|
||||
### Why strict EXACT is still owner-blocked
|
||||
|
||||
`_effect_allowed_by_environment` reaches EXACT only with `boolean allowed;` left uninitialised so the
|
||||
default arm returns the never-written home (probe u1, EXACT). That is prohibited by WORKER_BRIEF section 5
|
||||
(uninitialised read) and fails R19: effects.c's own post-fatal-assert precedent,
|
||||
`_effect_evaluate_function_integral` (exact at HEAD), returns a DEFINED fallback. HCEA
|
||||
(`effect_allowed_by_environment.c:4`) corroborates the indeterminate original but is not a licence.
|
||||
|
||||
With the safe initializer the residual is a result-storage tie: a definite initializer makes `allowed` live
|
||||
from entry across two `scenario_location_underwater` calls, so VC7 gives it a callee-saved register
|
||||
(`push ebx; xor bl,bl; ... mov al,bl; pop ebx`); only the uninitialised local, whose live range starts
|
||||
inside the arms, produces January's `push ecx` frame and AL-per-arm returns.
|
||||
|
||||
`_effect_generate_particles` residual under the candidate: 566 vs 562 instructions (was 622), 75 vs 120
|
||||
aligned diff blocks. The four extra instructions are `mov [ebp-0xc0],ebx` (count_delta spilled) and the
|
||||
per-instance `test bx,bx; jle` guard January elides because count_delta stays in EBX - exactly the
|
||||
"loop guard and register form" the park text already records. Its environment-helper half is resolved.
|
||||
|
||||
## Experiment matrix (all scratch)
|
||||
|
||||
| probe | change | env | generate_particles | generate_parts |
|
||||
|---|---|---|---|---|
|
||||
| v0 | control (= HEAD) | 112/10 | 2048/55 | EXACT |
|
||||
| e1 | env per-case `return`s | 112/10 | 2016/55 | 688/24 LOST |
|
||||
| e2 | env `= FALSE` init + anywhere TRUE | **128/10** | **1952/46** | EXACT |
|
||||
| e3 | env `= TRUE` init + vacuum FALSE | 128/10 | 1952/46 | EXACT |
|
||||
| e4 | env `= FALSE` init, every case assigns | 112/10 | 2016/55 | EXACT |
|
||||
| e5 | env default assigns FALSE before the assert | 112/10 | 2016/55 | EXACT |
|
||||
| e6 | e5 with vacuum falling through | 144/10 | 1952/46 | EXACT |
|
||||
| u1 | env uninitialised local (PROHIBITED, evidence only) | EXACT | 1952/46 | EXACT |
|
||||
| g1 | inner `for` -> `while` + increment (A23) | 112/10 | 2048/55 inert | EXACT |
|
||||
| g2 | node_index idiom | 112/10 | 2032/55 | EXACT |
|
||||
| g3 | g2 + `last_particle_count` statement | 112/10 | 2048/55 | EXACT |
|
||||
| g4 | `if (count_delta > 0)` instead of `continue` | 112/10 | 2048/55 inert | EXACT |
|
||||
| u2 | u1 + g2 | EXACT | 1904/46 | EXACT |
|
||||
| u3 | u1 + g2 + g3 | EXACT | 1888/46 | EXACT |
|
||||
| **c1** | **e2 + g2 + g3** | **128/10** | **1888/46** | EXACT |
|
||||
| c2 | e3 + g2 + g3 | 128/10 | 1888/46 | EXACT |
|
||||
|
||||
## Do-not-repeat (added by this wave)
|
||||
|
||||
- Call-site levers for the environment-helper inline: loop respelling (A23) and extra `if` nesting are both
|
||||
measured INERT in `_effect_generate_particles`.
|
||||
- `effect_allowed_by_environment` written with per-case `return` statements: it is cheaper to inline and
|
||||
costs the exact `_effect_generate_parts` row.
|
||||
- `effect_allowed_by_environment` with every case assigning explicitly (e4), or with the default assigning
|
||||
before the assert (e5): both 112.
|
||||
|
||||
## Orchestrator proposals
|
||||
|
||||
1. **Coupled park re-baseline (zero strict credit)** of `_effect_allowed_by_environment` and
|
||||
`_effect_generate_particles` from `scratch/workers/w3d_effects/c1_crlf.c`, as one object commit.
|
||||
New park bases: env 128 / 10 / `8f9a2185534b3801`; generate_particles 1888 / 46 / `c251b0c184dbcdeb`.
|
||||
Both are strictly closer (size and relocation count now exact on both rows); `_effect_generate_part`
|
||||
does not drift; all 38 exact rows are unchanged.
|
||||
2. **Park evidence text**, `_effect_allowed_by_environment`: replace the stale sentence about simple-FALSE
|
||||
initialization with the e2/e5 measurements above; the reopen criterion (owner ruling (a)) is unchanged.
|
||||
3. **Park evidence text**, `_effect_generate_particles`: the environment-helper inline decision is resolved
|
||||
by a natural safe callee body; the remaining residual is the count_delta-EBX vs instance-EBX role swap
|
||||
and the per-instance loop guard.
|
||||
4. **Owner ruling (a) (unchanged question, now fully quantified):** if a read reachable only when
|
||||
`system_exit(-1)` returns is admitted, `scratch/workers/w3d_effects/u3.c` makes
|
||||
`_effect_allowed_by_environment` strict EXACT (128 B) and leaves `_effect_generate_particles` at
|
||||
1888/46 `[sha]`. Without it, c1 is the ceiling.
|
||||
|
||||
## Checks (candidate c1)
|
||||
|
||||
Gate `== exact 38 residual 3`, only the two proposed rows change; point guard passes; `scratch/parkcheck.py`
|
||||
drifts exactly those two parks and leaves `_effect_generate_part` at base; owner census vs `build/base` and
|
||||
`build/split` is identical (no new candidate-only code/data/BSS/COMMON owner); `tools/fake_match_scan.py`
|
||||
reports 0 leads, the same as HEAD.
|
||||
|
||||
## Final real-file gate
|
||||
|
||||
`== exact 38 residual 3 unwritten 0`, identical to the baseline row by row. Point guard passes, park drift
|
||||
0/3, owner census identical, fake scan 0 leads. `git diff --stat -- source/effects/effects.c` is empty and
|
||||
`newly_exact` is empty: nothing in this wave is strict EXACT without an owner ruling.
|
||||
@@ -0,0 +1,123 @@
|
||||
# `glow.obj`: Opus5 250K house-clean lane, wave w3d (structural pass, 2026-09-15)
|
||||
|
||||
## Result
|
||||
|
||||
Nothing landed. `source/objects/widgets/glow.c` is **unchanged** (`git diff --stat` empty).
|
||||
Baseline and final real-file gate, with the `_point_from_line3d` guard: **exact 24 / residual 1 /
|
||||
unwritten 0**, row-for-row identical. Park drift 0, fake scan 0 leads.
|
||||
|
||||
The single residual `_get_particle_world_position` (1,744 padded) is parked and blocked on an owner
|
||||
ruling. This wave contributes:
|
||||
|
||||
1. a **quantitative January-side proof** of the original defect, from the relocation census, that the
|
||||
w1 ledger could only infer;
|
||||
2. **cross-build corroboration** from HCEA that the same defect shipped;
|
||||
3. a **re-verification** that the w1 scratch candidate is still strict EXACT at this HEAD, and that it
|
||||
now closes the whole object 25/25 with no header packet (the packet w1 needed for
|
||||
`_glow_trailing_particle_new` has landed since).
|
||||
|
||||
## Duplicate checks
|
||||
|
||||
Ledgers read: `glow_obj_opus5_150k_w1_20260914`, `glow_obj_house_rule_reconciliation_20260908`,
|
||||
`glow_obj_opus_source_admission_hold_20260906`, `glow_obj_jonas_complete_hcea_packet_20260830`,
|
||||
`glow_obj_jonas_new_20260829`, `glow_obj_jonas_delete_20260829`,
|
||||
`glow_obj_jonas_nonuniform_spline_pair_20260827`, `glow_obj_jonas_parametric_line_leaf_20260821`.
|
||||
The w1 do-not-repeat list (four-statement knot copies, a bound marker pointer, cross-product
|
||||
reordering, the unnamed `cos`/`sin` form, any further defined-C repair aimed at exactness) was
|
||||
honoured; R13 binds it.
|
||||
|
||||
## Census (mandatory)
|
||||
|
||||
| | padded / relocs / sha16 |
|
||||
| --- | --- |
|
||||
| January target | 1744 / 43 / `84720a5067d22512` |
|
||||
| real file (parked body) | 2528 / 55 / `fb7bb3c6b7236d62` |
|
||||
|
||||
**Relocation census by target** (`relocdiff --count-by-target --allow-structural`):
|
||||
|
||||
| relocation target | January | ours | delta |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| `_display_assert` | 3 | 3 | 0 |
|
||||
| `_system_exit` | 3 | 3 | 0 |
|
||||
| `_nonuniform_cubic_spline_vector3d` | 3 | 3 | 0 |
|
||||
| assert / file string literals | 6 | 6 | 0 |
|
||||
| `__real@3f000000` (0.5f) | 14 | 20 | **+6** |
|
||||
| `__real@3e800000` (0.25f) | 7 | 10 | **+3** |
|
||||
| `__real@3f400000` (0.75f) | 7 | 10 | **+3** |
|
||||
| TOTAL | 43 | 55 | +12 |
|
||||
|
||||
**Nothing is missing from our body.** There is no missing call, no missing global, no missing string.
|
||||
Our body carries twelve *extra* float-constant references and 784 extra code bytes.
|
||||
`point_from_parametric_line` inlines three components per call, so
|
||||
|
||||
- +3 at 0.25f and +3 at 0.75f is exactly one extra pair of parametric-line interpolations - the
|
||||
two-marker path's side vectors;
|
||||
- +6 at 0.5f is exactly two extra midpoint interpolations - the two three-marker sub-cases' side
|
||||
vectors.
|
||||
|
||||
That is precisely the code that fills `sides[0..3]` on the degenerate paths. **The relocation census
|
||||
therefore proves, from January's own bytes, that January never builds the side control vectors on the
|
||||
two- and three-marker paths while still passing `&sides[0..3]` to the third
|
||||
`nonuniform_cubic_spline_vector3d` call.** The w1 ledger asserted this from the frame layout; it is now
|
||||
measured, and it also proves no defined-C body can reach 1744/43, because any correct initialization of
|
||||
`sides[]` emits exactly those twelve references.
|
||||
|
||||
**Frame.** Not a frame gap. The local set is identical (`sides[4]`, `ups[4]`, `positions[4]`,
|
||||
`knots[4]`, `delta`, `side`, `up`, `angle`, `sine`, `cosine`, four shorts); the difference is 784 bytes
|
||||
of extra straight-line interpolation code inside two switch arms.
|
||||
|
||||
## New evidence beyond w1
|
||||
|
||||
**Cross-build corroboration.** HCEA's disasm-verified reconstruction of the shipped Xbox build
|
||||
(`...\halocea-review\src\blam\objects\widgets\get_particle_world_position.c`, function at 0x8380BB00)
|
||||
states in its own header comment, independently of this campaign, that the degenerate paths
|
||||
"carry faithful shipped quirks and are reproduced verbatim: (a) several z-component interior control
|
||||
points are lerped onto the *y* base of the endpoint rather than the z base; (b) the side-vector control
|
||||
points (side_ctrl) are never filled on the 2/3 marker paths; and (c) the 3-marker interval-1 path reads
|
||||
knot1 from the never-written knot scratch." Corroboration only - the January-side proof is the census
|
||||
above - but it removes the "reconstruction artifact" reading of the w1 candidate.
|
||||
|
||||
**Candidate re-verified at this HEAD.**
|
||||
`gate.py source/objects/widgets/glow --source scratch/workers/glow/owner_candidate.c --all
|
||||
--forbid-emitted-symbol _point_from_line3d` -> **exact 25 / residual 0 / unwritten 0**, with
|
||||
`_get_particle_world_position` EXACT at 1744. w1 measured 24/25 because `_glow_trailing_particle_new`
|
||||
then required header packet C; that packet has since landed, so the candidate needs no header or config
|
||||
change at all. Checks re-run at HEAD: guard passes; `scratch/parkcheck.py` reports drift only on the
|
||||
reopened park itself (2528/55 -> 1744/43/`84720a5067d22512`, i.e. to the target - the expected reopen
|
||||
signature); the owner census versus `build/base/source/objects/widgets/glow.obj` is identical, with no
|
||||
new candidate-only code, data, BSS or COMMON owner; `tools/fake_match_scan.py` reports 0 leads.
|
||||
|
||||
## Shapes tried
|
||||
|
||||
None. Triage (brief step 0) classifies this row as an owner-ruling block rather than a live shape
|
||||
search, and the relocation census makes that conclusive: our body is a strict superset of January's,
|
||||
and the surplus is exactly the defect repair.
|
||||
|
||||
## Owner-ruling proposal (re-raised, with the new quantitative evidence)
|
||||
|
||||
Admit, for `_get_particle_world_position` only, the reproduction of two original Bungie defects under
|
||||
the docs' `/* BUG (preserved for exact matching): ... */` form, then replace the parked body with the
|
||||
body in `scratch/workers/glow/owner_candidate.c` and unpark it in a dedicated commit:
|
||||
|
||||
1. the two- and three-marker paths never fill `sides[0..3]`, which the unconditional third
|
||||
`nonuniform_cubic_spline_vector3d` call then reads;
|
||||
2. on the three-marker `parent_marker_index == 1` path the midpoint knot is written over `knots[2]`
|
||||
instead of `knots[1]`, so `knots[1]` is never set
|
||||
(`marker_time_index[1]` loaded at +0x2EA, midpoint stored at +0x5C6 into `[ebp-8]`, `[ebp-0xc]`
|
||||
read unwritten at +0x5D2).
|
||||
|
||||
Both are reads of an indeterminate value from an address-taken automatic array, which brief section 5
|
||||
currently lists among the undefined-behaviour exclusions. Supporting evidence: the January-side
|
||||
relocation census; the January-side offsets above; HCEA's independent shipped-build reconstruction
|
||||
naming both quirks; and a candidate that is strict EXACT, closes the object 25/25, loses no sibling,
|
||||
adds no owner and scans clean.
|
||||
|
||||
Value: **1,744 padded bytes**, and it is the last non-exact row in `glow.obj`.
|
||||
|
||||
## Reopen criterion (unchanged, restated)
|
||||
|
||||
An owner ruling that admits these two preserved original defects for this function, or January evidence
|
||||
that two- and three-marker glows can never reach the side spline (which would make the `sides[]` read
|
||||
unreachable rather than indeterminate). No further defined-C repair should be attempted: the relocation
|
||||
census proves that any correct initialization of `sides[]` adds the twelve constant references that
|
||||
constitute the entire remaining gap.
|
||||
@@ -0,0 +1,260 @@
|
||||
# `input_xbox.obj` structural pass — opus5 250K house-clean lane, wave w3d (2026-09-15)
|
||||
|
||||
## Scope and baseline
|
||||
|
||||
- Worktree `C:\halo-worktrees\opus5-150k-house-clean-20260914`, branch `opus/250k-house-clean-20260915`.
|
||||
- Owned source: `source/input/input_xbox.c` only. No header, config, build, tool or other source was touched and
|
||||
nothing was committed. All three non-exact rows are parked in `config/parked.json`, so every body was built and
|
||||
measured in scratch only; the real file is byte-unchanged (`git diff --stat -- source/input/input_xbox.c` empty)
|
||||
and its final gate is identical to the baseline.
|
||||
- Baseline and final real-file gate (with the `_point_from_line3d` guard): **`== exact 24 residual 3 unwritten 0`**.
|
||||
- Park drift on the real file: none (`scratch/parkcheck.py`, 3 parks, 0 drift).
|
||||
|
||||
| function | January padded / relocs / sha16 | real file | wave outcome |
|
||||
| --- | --- | --- | --- |
|
||||
| `_input_get_device_states` | 1232 / 28 / `56316233dc2ab683` | 1280 / 32 / `b7cded6529564446` | NOT LANDED — 1 allocator hunk (best scratch 1216 / 28) |
|
||||
| `_input_update_analog_button_state` | 48 / 0 / `5850a17e99380c54` | 48 / 0 / `78f85bb297cc663f` | **PARK-REOPEN PROPOSED — strict EXACT** |
|
||||
| `_input_update_keyboard_devices` | 640 / 49 / `80bcd2f76c52c728` | 640 / 51 | NOT LANDED — pin + the same allocator hunk |
|
||||
|
||||
Ledgers read: all twelve `input_xbox_obj_*.md` (in particular the w2 park re-investigation
|
||||
`input_xbox_obj_opus5_150k_w2_20260914.md` and `input_xbox_obj_device_keyboard_pollers_wave_20260904.md`, which
|
||||
owns the three park criteria), `scratch/w2/laws_w2.md` (R11 rejects the w2 `d2`/`k3` zero-credit park remeasures;
|
||||
section D lists `_input_update_analog_button_state` as a spent-tie row), `scratch/w3/laws_w3.md`, the 100K handoff.
|
||||
`git log --all -- source/input/input_xbox.c` (22 commits; latest `e123a1d82`). No branch holds a better blob.
|
||||
|
||||
Worker notes with the full probe tables: `scratch/workers/w3d_input_xbox.md`.
|
||||
Scratch: `scratch/workers/w3d_input_xbox/` (`lab.py` micro-TU compiler using input_xbox.c's own cflags, `run.sh`
|
||||
whole-TU gate, `sub.py` body splicer, `lab1.c`-`lab5.c` probes, `fn/*.c` bodies, `*.gate.txt`/`*.obj`).
|
||||
|
||||
---
|
||||
|
||||
## 1. `_input_update_analog_button_state` — EXACT (48 B), park reopen proposed
|
||||
|
||||
### The one-instruction gap
|
||||
|
||||
January's standalone copy and ours differ in exactly one instruction out of 30:
|
||||
|
||||
```
|
||||
0 test dl,dl 2 je 0x15 4 cmp al,0x20 6 jae 0xc
|
||||
8 xor al,al a jmp 0xe c sub al,0x20 <-- January; ours: add al,0xe0
|
||||
e cmp al,[ecx] 10 jbe 0x25 12 mov [ecx],al 14 ret
|
||||
15 cmp al,0xbf 17 jbe 0x1d 19 or al,0xff 1b jmp 0x1f
|
||||
1d add al,0x40 1f cmp al,[ecx] 21 jae 0x25 23 mov [ecx],al 25 ret
|
||||
```
|
||||
|
||||
The layout proves the source arm order already in the file (`if (value < OFFSET) … else …` in both paths), and the
|
||||
release path's `add al,0x40` / `or al,0xff` prove `+= 64` and `= UNSIGNED_CHAR_MAX`. The park's recorded blocker was
|
||||
"only `sub al,32` versus compiler-canonical `add al,-32`", with the criterion *"Reopen only for new authentic
|
||||
compiler/source evidence"*.
|
||||
|
||||
### New evidence: a measured VC7 byte-width law (new, not a re-run of the w2 search)
|
||||
|
||||
Every earlier wave respelled the **subtrahend**. Twelve such spellings were re-measured in a micro-TU built with
|
||||
`input_xbox.c`'s own cflags (`scratch/workers/w3d_input_xbox/lab2.c`, probes `pa`-`pl`): `value -= 32`,
|
||||
`value = value - 32`, a `const byte` local, a file-scope `static const byte`, an `enum` constant, `32u`,
|
||||
`(byte)(value-32)`, `+= -32`, `(int)32`, `0x20`, `sizeof(int)*8`, `(byte)(value-32u)` — **all twelve emit
|
||||
`add al,0xe0`**. The subtrahend is inert; that direction is closed.
|
||||
|
||||
The destination is not. `lab3.c` / `lab4.c` controls:
|
||||
|
||||
| probe | source | emitted |
|
||||
| --- | --- | --- |
|
||||
| `q_ret8` | `byte f(byte v){ v -= 32; return v; }` | **`sub al,0x20`** |
|
||||
| `q_branch8` | the same `v -= 32` inside an `if/else` | `add al,0xe0` |
|
||||
| `q_branch32` | the same shape at 32-bit width | **`sub eax,0x20`** |
|
||||
| `q_twice` | `v -= 32; v -= 32;` | `sub al,0x40` |
|
||||
| `q_bigconst` | `v -= 200` | `add al,0x38` |
|
||||
| `q_mem8/16/32` | `*p -= 32` | `add …,0xe0 / 0xffe0 / -0x20` |
|
||||
| `b1`-`b5`, `b8`-`b10` | eight further branch shapes that write back into `value` | `add al,0xe0` |
|
||||
| **`b7`** | `byte result; if (c) result = value - 32; else result = 0; return result;` | **`sub al,0x20`** |
|
||||
|
||||
**Law (L1, byte self-update canonicalisation).** At byte width VC7 13.00.9254 rewrites a *self-update*
|
||||
`x = x - C` into `add x, (256-C)`; it keeps `sub x, C` when the difference is assigned to a **different** variable
|
||||
(after copy propagation, the function result counts as one). 32-bit arithmetic keeps `sub` in both cases, and a byte
|
||||
constant above 127 always becomes `add` with the complement. Detector: *January `sub r8,imm` where ours has
|
||||
`add r8,imm` with the high bit set means the source assigned to a second variable.*
|
||||
|
||||
In-tree corroboration: the **current real** `_input_get_device_states` body, which binds the reading to its own
|
||||
local `byte value` before calling the helper, already emits `sub al,0x20` at its inlined copy of this helper
|
||||
(`scratch/workers/w3d_input_xbox/realeq.txt`, +0x2B9).
|
||||
|
||||
`lab5.c` then showed that the four natural placements of the result variable (function scope, per-arm scope,
|
||||
declare+initialise via a ternary, press arm only) are **byte-identical** and reproduce every other instruction of
|
||||
January's body — the bytes do not discriminate them, so the house rules pick.
|
||||
|
||||
### Landed body (`fn/h1.c`)
|
||||
|
||||
```c
|
||||
static void input_update_analog_button_state(
|
||||
byte *threshold,
|
||||
byte value,
|
||||
boolean pressed)
|
||||
{
|
||||
byte new_threshold;
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
if (value < ANALOG_BUTTON_PRESS_THRESHOLD_OFFSET)
|
||||
{
|
||||
new_threshold = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_threshold = value - ANALOG_BUTTON_PRESS_THRESHOLD_OFFSET;
|
||||
}
|
||||
|
||||
if (new_threshold > *threshold)
|
||||
{
|
||||
*threshold = new_threshold;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
…the release path with the same named result…
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
The helper maintains a hysteresis envelope for one analog button: it derives the threshold implied by this frame's
|
||||
reading and moves `*threshold` towards it (up while held, down while released). Naming that derived quantity and
|
||||
leaving the incoming `value` parameter unmodified is ordinary defensive C, and it is what January's bytes require.
|
||||
No cast, no `volatile`, no redundant store, no unused local (three uses per arm), no change to the arm order,
|
||||
constants or control flow.
|
||||
|
||||
### Checks — candidate `scratch/workers/w3d_input_xbox/candidate_reopen.c`
|
||||
|
||||
sha256 `c13f310311128753a30fb961b9d874ba98a4614fdd3fa8e081f871801b81beb2` (the current real file plus only this body).
|
||||
|
||||
1. Whole-TU gate: `== exact 25 residual 2 unwritten 0`; the row-by-row diff against the baseline listing shows
|
||||
**exactly one** changed row, `residual 48 _input_update_analog_button_state -> EXACT 48`. No other row moved.
|
||||
2. `_point_from_line3d` emitted-symbol guard passed.
|
||||
3. `scratch/parkcheck.py`: `_input_get_device_states` OK (1280 / 32 / `b7cded65…` — unchanged; the helper edit does
|
||||
**not** drift it), `_input_update_keyboard_devices` OK (640 / 51), and
|
||||
`_input_update_analog_button_state` drifts to the January target values, as a reopen must.
|
||||
4. Owner census vs `build/base` and `build/split`: 52 candidate owners vs 52 base and 50 target; the only delta is
|
||||
the compiler-generated jump-table label `$L8245` -> `$L8246` (present in neither January object).
|
||||
`target owners missing in candidate: []`. No new or lost real code/data/BSS/COMMON owner.
|
||||
5. `tools/fake_match_scan.py`: 0 review leads, same as HEAD.
|
||||
6. CRLF line endings preserved (1229 CRLF, 0 lone LF).
|
||||
|
||||
**ORCHESTRATOR PROPOSAL (park-reopen).** Remove the `config/parked.json` entry for
|
||||
`source/input/input_xbox::_input_update_analog_button_state` and apply the `fn/h1.c` body in the same commit.
|
||||
Park criterion satisfied: "new authentic compiler/source evidence" — a measured byte-width codegen law with ~20
|
||||
controls, not an equivalent-expression trial.
|
||||
|
||||
---
|
||||
|
||||
## 2. `_input_get_device_states` — structural gap closed, one allocator hunk left (NOT landed)
|
||||
|
||||
### Census (done before any shape)
|
||||
|
||||
- **Frame.** January `sub esp,0x30`; the real body `sub esp,0x44` — five extra cells. January's slots:
|
||||
`[ebp-8]`/`[ebp-0x10]` gamepad insertions/removals, `[ebp-0x14]`/`[ebp-0x18]` the memory-unit pair, `[ebp-4]`
|
||||
`device_change_flags`, `[ebp-0xc]` the hot-plug count, `[ebp-0x30]` the `XINPUT_STATE`; then in the poll loop three
|
||||
homed pointers `[ebp-0xc]` = `raw_gamepad_states` (+282), `[ebp-4]` = `gamepad_states` (+332), `[ebp-8]` =
|
||||
`gamepad_handles` (+316) and `[ebp-0x10]` = count. The real body additionally builds and homes two table bases
|
||||
(`mov eax,_gamepad_analog_button_indices; sub eax,_input_globals+332; …; sub eax,0x18`).
|
||||
- **Relocations.** 32 vs 28. The four extras are those two hand-built table bases plus `_input_globals+284`/`+340`
|
||||
bases where January uses `+282`/`+332`.
|
||||
- **Diagnosis.** A missing/extra-local + wrong-cursor problem, i.e. structural, exactly as the tags said. w2's
|
||||
`fn/d2.c` — index `for` loops over `input_globals.<array>[gamepad_index]` with the insert/remove work in its own
|
||||
block — already fixes both: **1216 / 28** with `sub esp,0x30` exact.
|
||||
|
||||
### This wave
|
||||
|
||||
With the new helper body the second of d2's two recorded blockers disappears (`sub cl,0x20` at +0x29F now matches),
|
||||
leaving a single hunk in the binary-button loop's inlined `update_ticks`:
|
||||
|
||||
```
|
||||
T 2c3 lea eax,[esi+0x18] … 2d4 movzx ecx,[eax] 2d7 inc ecx 2d8 cmp ecx,0xff … 2e9 mov [eax],cl 2eb inc eax
|
||||
O 2c3 lea ecx,[esi+0x18] … 2d4 movzx eax,[ecx] 2d7 inc eax 2d8 cmp eax,0xff … 2e8 mov [ecx],al 2ea inc ecx
|
||||
```
|
||||
|
||||
The entire 16-byte size gap is the one-byte difference between `cmp ecx,imm32` (6 B) and `cmp eax,imm32` (5 B).
|
||||
|
||||
Shapes measured this wave (all with the helper fix in place):
|
||||
|
||||
| shape | spelling | result |
|
||||
| --- | --- | --- |
|
||||
| `hd1` | w2 `fn/d2.c` + `fn/h1.c` | 1216 / 28, one hunk |
|
||||
| `e1` | binary loop in its own block with a separate `short binary_button_index` | identical |
|
||||
| `e2` | mask test operands swapped | identical |
|
||||
| `e3` | `buttons[button_index + FIRST_GAMEPAD_BINARY_BUTTON]` | identical |
|
||||
| `e4` | `byte *binary_button_ticks` base indexed by `button_index` | 1248, worse |
|
||||
| `e5` | tick pointer bound to a local before the call | identical |
|
||||
| `e6` | binary loop as a pointer/count `while` walk | 1248, worse |
|
||||
| `hd_d6` | w2 `fn/d6.c` + helper | 1232 (right size) but three extra hunks: `wButtons` reloaded inside the loop instead of hoisted into BL, plus a second EAX/ECX flip at +0x330 |
|
||||
|
||||
Classification: **C2 register-allocation tie** (law L2 below), not structural. `hd_d6` also confirms that January
|
||||
hoists the `wButtons` low byte into BL before the loop, which `d2` reproduces and `d6` does not.
|
||||
|
||||
**Not proposed.** Re-baselining this park from 1280/32 to 1216/28 would be a zero-credit park remeasure, and
|
||||
laws_w2 **R11** already rejected exactly this (`input_xbox d2/k3`). The helper reopen does **not** drift this park
|
||||
(`parkcheck` reports it OK), so no coupled A8 re-baseline is required either.
|
||||
|
||||
**Reopen criterion.** A measured source lever that moves which of EAX/ECX the C2 allocator gives to a pointer IV
|
||||
versus the inlined `update_ticks` 32-bit temp, in a loop whose body is only that call; then re-gate
|
||||
`scratch/workers/w3d_input_xbox/hd1.c` (1216 / 28 / `0138629fbb74299e`).
|
||||
|
||||
---
|
||||
|
||||
## 3. `_input_update_keyboard_devices` — structural gap already closed by w2, two ties left (NOT landed)
|
||||
|
||||
### Census
|
||||
|
||||
- **Frame.** No `sub esp` gap; slots agree (`[ebp-8]`/`[ebp-4]` insertions/removals, the `key_stroke` at
|
||||
`[ebp-8..-5]`, the `XINPUT_DEBUG_KEYSTROKE` at `[ebp-4]`).
|
||||
- **Relocations** (`relocdiff --count-by-target`, real body): 51 vs 49 — `_input_globals+568` **6 vs 5** and
|
||||
`_input_globals+672` **3 vs 2**; every other target is equal. January walks **one** induction variable over
|
||||
`key_ticks` (+568) and reaches `key_latches` (+672) through the constant displacement `+0x68`
|
||||
(`mov cl,[eax+0x68]`, `movzx ecx,[eax]`). w2's `fn/k3.c` (a `short key_code` `for` loop indexing both arrays)
|
||||
already reproduces that: **640 / 49**. The structural row is therefore closed by an existing body; what remains is
|
||||
`[sha]`.
|
||||
|
||||
### This wave
|
||||
|
||||
| shape | spelling | result |
|
||||
| --- | --- | --- |
|
||||
| `kA` | **diagnostic only** — ascii else-value `0` instead of `NONE` | the `mov ebx,0xff` pin disappears, `push ebx` moves to January's +0x27 and `pop ebx` to +0xD7, 197 -> 195 instructions, only the EAX/ECX hunk left |
|
||||
| `kB` | ascii store as `key.ascii_code = keystroke.Ascii; if (table[...] == NONE) key.ascii_code = NONE;` | identical to k3 |
|
||||
| `kE` | `long key_code` instead of `short` | 656 / 51, much worse — proves `short key_code` |
|
||||
| `kF` | named `boolean down = input_globals.key_latches[key_code];` | identical to kA |
|
||||
|
||||
`kA` identifies the mechanism of the recorded "constant-pinning" blocker: the two in-loop uses of
|
||||
`UNSIGNED_CHAR_MAX` inside the inlined `update_ticks` and the later **byte** store `key.ascii_code = NONE` are
|
||||
unified by VC7 into one enregistered 0xFF (`cmp eax,ebx`, `mov eax,ebx`, `mov [ebp-7],bl`), which additionally
|
||||
forces `push ebx` into the prologue instead of shrink-wrapping it to the hot-plug block. Remove the third use and
|
||||
VC7 rematerialises the constant exactly as January does. January performs all three 0xFF uses and does not unify
|
||||
them; the else-value is genuinely `NONE`, so there is no admissible spelling that avoids it.
|
||||
|
||||
`kA` also shows the **same** EAX/ECX role swap as `_input_get_device_states`, so this row cannot close on the pin
|
||||
alone.
|
||||
|
||||
**Not proposed** (R11: the w2 `k3` park remeasure was already rejected as zero-credit).
|
||||
|
||||
**Reopen criterion.** (a) a source lever that stops VC7 unifying a byte constant across a loop and a later byte
|
||||
store, **and** (b) the allocator lever the device_states row needs; then re-gate `hk3.c` (640 / 49).
|
||||
|
||||
---
|
||||
|
||||
## 4. Laws proposed for the wave file
|
||||
|
||||
**L1 — BYTE SELF-UPDATE CANONICALISATION (closing; closed 48 B first probe).** At byte width VC7 rewrites
|
||||
`x = x - C` into `add x,(256-C)`; `sub x,C` appears only when the difference is assigned to a different variable
|
||||
(or is the function result). 32-bit arithmetic keeps `sub` either way; a byte constant `> 127` always becomes
|
||||
`add`. Twelve subtrahend spellings and ten branch shapes were controls. Detector: January `sub r8,imm` against our
|
||||
`add r8,imm` with the high bit set ⇒ the source names a second variable.
|
||||
|
||||
**L2 — SOLE-STATEMENT LOOP HELPER ALLOCATION TIE (diagnostic).** In a loop whose body is only an inlined helper
|
||||
taking (pointer, condition), January gives EAX to the pointer IV and ECX to the helper's 32-bit temp; our build
|
||||
reverses them in all nine spellings measured across two functions (index loop, separate index variable, operand
|
||||
swap, index-base swap, one-use pointer local, pointer/count `while`, `long` index, named boolean local, and the
|
||||
pointer-walk body in the real file today). Where the loop body has other work that also reads through the IV
|
||||
(the analog-button loop) the assignment matches. Costs one byte per `cmp r32,imm32`. No source lever found.
|
||||
|
||||
**L3 — BYTE-CONSTANT UNIFICATION ACROSS A LOOP (diagnostic).** Two in-loop uses of an int constant whose low byte
|
||||
equals a later byte-store constant are unified into one enregistered value and push the callee-saved register into
|
||||
the prologue instead of shrink-wrapping it; removing the third use restores rematerialisation. Our-only behaviour
|
||||
in `_input_update_keyboard_devices`; no admissible source lever (the third use is a genuine `NONE`).
|
||||
@@ -0,0 +1,163 @@
|
||||
# `light_volumes.obj`: Opus5 250K house-clean lane, wave w3d (structural pass, 2026-09-15)
|
||||
|
||||
## Result
|
||||
|
||||
Nothing landed. `source/objects/widgets/light_volumes.c` is **unchanged** (`git diff --stat` empty).
|
||||
Baseline and final real-file gate, with the `_point_from_line3d` guard: **exact 9 / residual 1 /
|
||||
unwritten 0**, row-for-row identical. Park drift 0, fake scan 0 leads.
|
||||
|
||||
The parked `_light_volume_render` was re-investigated in scratch only. This pass produced a complete
|
||||
frame/relocation census, **three byte-proven source defects** (two of which together make January's
|
||||
loop header reproduce instruction-for-instruction), and **three corrections to the recorded w2 evidence
|
||||
and to the `laws_w3` C1 premise for this row**. No candidate reached strict exact; the retained parked
|
||||
body is still the smallest body measured.
|
||||
|
||||
| | padded / relocs / frame / sha16 | instrs | alndiff hunks |
|
||||
| --- | --- | ---: | ---: |
|
||||
| January target | 912 / 47 / 0xa4 / `2e43dbc111ad1255` | 291 | - |
|
||||
| retained (park base) | 928 / 47 / 0xa8 / `a4e273c7939882fc` | 296 | 64 |
|
||||
| best this wave (v12) | 944 / 47 / 0xac | 306 | 58 |
|
||||
| loop-only fix (v7) | 944 / 47 / 0xac | 309 | 59 |
|
||||
|
||||
## Duplicate checks
|
||||
|
||||
Ledgers read: `light_volumes_obj_opus5_150k_w2_20260914`, `..._credible_fuzzy_closeout_20260831`,
|
||||
`..._jonas_exhaustive_census_20260830`, `..._jonas_distance_fade_schedule_fixed_point_20260826`,
|
||||
`..._jonas_interpolator_recovery_20260826`, `..._jonas_partial`. Laws applied as detectors:
|
||||
laws_w2 A4, A5, A6, A10, A12, A18, A20, A22; laws_w3 A24, A31, A35, A38, A44, plus the C1 third-pass
|
||||
row for this function and R14/R15/R16. The w2 starting point
|
||||
`scratch/workers/render_particles_rasterizer_xbox_decals_etc/lvJ.c` was re-measured in this tree
|
||||
(944/48) rather than assumed.
|
||||
|
||||
## Census (mandatory, recorded before any shape)
|
||||
|
||||
**Relocations: 47 == 47.** No missing and no extra call or global. The gate tag is `[size, sha]` only.
|
||||
Calls: `_datum_get`, `_tag_get`, `_light_volume_interpolate_frames`, `_object_get_marker_by_name`,
|
||||
`_object_get_function_value`, `_rasterizer_widget_begin`, `_rasterizer_widget_set_texture`,
|
||||
`__CIpow` x4, `_rgb_colors_interpolate`, `_real_argb_color_to_pixel32`,
|
||||
`_rasterizer_widget_draw_sprite3d`, `_rasterizer_widget_end`. January's object contains no
|
||||
`_dot_product3d` and no `_vector_from_points3d` COMDAT.
|
||||
|
||||
**Frame: `sub esp,0xa4` vs `sub esp,0xa8` - exactly one extra dword local.**
|
||||
|
||||
| object | target | ours (retained) |
|
||||
| --- | --- | --- |
|
||||
| `marker` (108 B) | `[ebp-0xa4 .. -0x39]` | `[ebp-0xa8 .. -0x3d]` |
|
||||
| `color` (16 B) | `[ebp-0x38 .. -0x29]` | `[ebp-0x3c .. -0x2d]` |
|
||||
| loop `position` (12 B) | `[ebp-0x18 .. -0x10]`, **shared with `delta`** | `[ebp-0x2c .. -0x24]` |
|
||||
| `delta` (12 B) | the same cells | **no slot at all - x87-stack resident** |
|
||||
| scalars | 7 dwords: radius `-0x28`, color_fraction `-0x24`, divisor `-0x20`, pow1#1 exponent `-0x1c`, sprite_index `-0xc`, distance_fade `-8`, pow1#2-4 exponent / brightness_fraction `-4` | 8 dwords: radius `-0x20`, pow1#1 exp `-0x1c`, pow1#2 exp `-0x18`, brightness_fraction `-0x14`, sprite_index `-0x10`, pow1#3-4 exp `-0xc`, `-8`, distance_fade / color_fraction `-4` |
|
||||
| `[ebp+8]` (`object_index` home) | `function_value`, then the `count-1` temp, then `offset_fraction` (13 refs) | `parallel_factor`, the `count-1` temp, `offset_fraction` (20 refs) |
|
||||
|
||||
The extra dword is a pow1 exponent home: January reuses one home (`[ebp-4]`) for inlined pow1
|
||||
instances #2/#3/#4; ours uses two.
|
||||
|
||||
## Byte-proven source defects found
|
||||
|
||||
**(a) `definition->count` is re-read inside the loop instead of using the `count` local (A5 class).**
|
||||
January loads `[ebx+0x6e]` once (`mov ax,[ebx+0x6e]; test ax,ax; jle; movsx ecx,ax; dec ecx`), converts
|
||||
`count - 1` to `real` once in the preheader (`mov [ebp+8],ecx; fild [ebp+8]; fstp [ebp-0x20]`) and
|
||||
divides with `fdiv [ebp-0x20]`. The retained `(real)(definition->count - 1)` re-loads the field and
|
||||
emits `fidiv [ebp+8]` in the body (the conversion is not hoisted). HCEA independently reconstructs a
|
||||
named `float divisor = (float)(count - 1);` local.
|
||||
|
||||
**(b) the sprite loop is a `for` with a `short` index, not `do { } while (--count)`.**
|
||||
January emits a 32-bit shadow index in `[ebp-0xc]` and a 16-bit trip counter in `DI`
|
||||
(`movzx edi,ax` ... `inc ecx; dec edi; mov [ebp-0xc],ecx; jne`); the retained form emits `test di,di`
|
||||
and no shadow.
|
||||
|
||||
**(a)+(b) together make January's loop header instruction-identical** (`scratch/workers/w3d_glow_light_volumes/v7.c`;
|
||||
target[162..179] equal, only slot numbers and one `lea esp,[esp]` loop-head alignment nop differ, and
|
||||
that nop is a downstream consequence of the earlier byte offset). Hunks 64 -> 59.
|
||||
|
||||
**(c) `real function_value = 1.f;`** - January `mov dword ptr [ebp+8],0x3f800000` at +0xd3, between the
|
||||
far-fade `fld [ebx+0x38]` (+0xd0) and its `fcomp` (+0xda): the A38 signature of a declaration in the
|
||||
block enclosing the far-fade `if`. The retained body has no initializer.
|
||||
|
||||
## Corrections to the recorded evidence (important for future waves)
|
||||
|
||||
**(d) The retained hand-written dot expressions are already January's association and, for the i term,
|
||||
January's operand order.** January's rule here is "`fld` the SECOND source operand, `fmul` the first":
|
||||
`marker.i*cam.i` -> `fld [render+28]; fmul [ebp-0x68]`, `cam.k*marker.k` -> `fld [ebp-0x60]; fmul
|
||||
[render+36]`. The retained `marker.i*cam.i + (cam.j*marker.j + cam.k*marker.k)` predicts all three
|
||||
target terms, and the `A + (B + C)` association is confirmed by January's `faddp` order ((k+j)+i).
|
||||
Our build reproduces only the i term; the j/k terms come out global-first because the A22 younger-leaf
|
||||
rule overrides source order while the delta values are alive on the x87 stack.
|
||||
The w2 ledger's "wrong operand order: the HCEA hand-written dot-product order" is a mis-attribution.
|
||||
|
||||
**(e) `laws_w3` C1's "try genuine `dot_product3d` calls first" is MEASURED and NEGATIVE.**
|
||||
`dot_product3d(&marker.matrix.forward, &render.camera.forward)` makes all three terms local-first -
|
||||
it breaks the i term the hand expansion already gets right - and grows the body to 944. Swapping the
|
||||
arguments is byte-identical (w2's "canonicalised by VC7" note confirmed). A35(1)'s "i global-first,
|
||||
j/k local-first through dot_product3d parameters" does not reproduce here. **A35 does not fire on this
|
||||
function; the C1 row should be retired.**
|
||||
|
||||
**(f) `vector_from_points3d` does NOT reproduce the delta memory stores** (contradicts w2 evidence #2).
|
||||
It leaves `delta` entirely on the x87 stack, exactly like the hand-written subtraction, and so does a
|
||||
`(real_vector3d const *)&delta` cast. The `_vector_from_points3d` surplus-COMDAT owner ruling (R14) is
|
||||
therefore **not** a blocker for this function.
|
||||
|
||||
**(g) Mechanism for the delta home.** The only measured form that reproduces January's immediate
|
||||
`fstp [ebp-0x18]/[ebp-0x14]/[ebp-0x10]` stores is the aggregate's address escaping to a real
|
||||
(non-inlined) call - the camera delta and the loop's sprite position being one function-scope
|
||||
`real_point3d` passed to `rasterizer_widget_draw_sprite3d`. That homes the values but at
|
||||
`[ebp-0x20..-0x18]` (frame 0xac) and does not fix the j/k operand order, so it is not January's form.
|
||||
|
||||
## Shapes measured (all scratch, `scratch/workers/w3d_glow_light_volumes/`)
|
||||
|
||||
| shape | change | size / relocs / frame | instrs | hunks |
|
||||
| --- | --- | --- | ---: | ---: |
|
||||
| lv_base | retained parked body | 928 / 47 / 0xa8 | 296 | 64 |
|
||||
| lvJ | w2 starting point re-measured here | 944 / 48 | - | - |
|
||||
| v1 | `dot_product3d` x2 + `real_vector3d delta` | 944 / 47 | 310 | - |
|
||||
| v2 | v1 with swapped arguments | 944 / 47 | - | - |
|
||||
| v3 | v1 + `vector_from_points3d` | 928 / 47 | 301 | - |
|
||||
| v4 | retained + `dot_product3d` x2 + point->vector cast | 944 / 47 | - | - |
|
||||
| v5 | v4 with swapped arguments | 944 / 47 | - | - |
|
||||
| v6 | v4 + one shared `real_point3d` (diagnostic) | 944 / 47 | 307 | - |
|
||||
| v7 | retained + `count - 1` + `for`/`short sprite_index` | 944 / 47 / 0xac | 309 | 59 |
|
||||
| v8 | v7 + `dot_product3d` x2 | 944 / 47 | - | 61 |
|
||||
| v10 | v7 + `real function_value = 1.f;` | 944 / 47 / 0xb0 | 306 | 63 |
|
||||
| v11 | v10 + `dot_product3d` x2 | 944 / 47 | 304 | 65 |
|
||||
| v12 | v7 + one shared `real_point3d` | 944 / 47 / 0xac | 306 | 58 |
|
||||
| v13 | lv_base + `#include "interface/hud_draw.h"` | 928 / 47, sha `a4e273c7939882fc` | 296 | byte-identical |
|
||||
| v14 | v7 + the same include | 944 / 47 | 309 | - |
|
||||
|
||||
Every probe kept all nine sibling rows EXACT and the `_point_from_line3d` guard passing. The owner
|
||||
census of the `dot_product3d` probes adds only `_dot_product3d`, which laws_w2 A4 accepts as systemic.
|
||||
|
||||
## Residual classification
|
||||
|
||||
Allocation / x87-scheduling tie sitting on top of the three identified source defects. The three
|
||||
remaining differences move together and none of the twelve shapes separated them:
|
||||
1. whether `delta` is memory- or x87-resident;
|
||||
2. whether `parallel_factor` takes `[ebp+8]` (`fst [ebp+8]; fcomp`) or stays on the x87 stack
|
||||
(January's non-popping `fcom`);
|
||||
3. which inlined pow1 instances share an exponent home, and whether that home is written by `fst`
|
||||
(January) or an integer `mov` pair (ours).
|
||||
|
||||
## Proposal (orchestrator decides; zero credit)
|
||||
|
||||
Add to `source/objects/widgets/light_volumes.c`, immediately after `#include "cseries/cseries.h"`:
|
||||
|
||||
#include "interface/hud_draw.h"
|
||||
|
||||
This is the unit's only C4013 (`scratch/gate4/c4013.txt`: `objects\widgets\light_volumes.c(353):
|
||||
warning C4013: 'real_argb_color_to_pixel32' undefined; assuming extern returning int`). The genuine
|
||||
owner is `source/interface/hud_draw.h:44`, and the sibling widget file in the same directory,
|
||||
`source/objects/widgets/lightning.c:50`, already includes it for the identical call. Measured
|
||||
byte-identical (928/47, sha `a4e273c7939882fc`, all ten rows unchanged), so it is a pure fidelity fix.
|
||||
It is a consumer-side `#include`, not a header edit. Not landed here because brief section 8 requires
|
||||
an empty file diff in a unit where nothing lands (E18 class).
|
||||
|
||||
## Reopen criterion (replaces the w2 criterion)
|
||||
|
||||
Reopen on a source-level fact that gives `delta` a stack home at `[ebp-0x18..-0x10]` **without** adding
|
||||
a frame slot - something other than sharing one aggregate with the loop's `position`, which homes it at
|
||||
`[ebp-0x20..-0x18]` (frame 0xac). Apply (a), (b) and (c) together; each is independently byte-proven.
|
||||
|
||||
Do not retry: `dot_product3d` in either argument order; `vector_from_points3d`; the
|
||||
`(real_vector3d const *)` cast; declaring `function_value` in the outer block; or any w2 probe
|
||||
(declaration order, `function_value` scoping, two-statement versus nested pow1 arguments, `pow` versus
|
||||
`power` inside `pow1`, ternary or result-variable `pow1`, the if-negate ABS form, making
|
||||
`light_volume_globals` static).
|
||||
@@ -0,0 +1,150 @@
|
||||
# `main.obj` opus5 250K house-clean lane, wave w3d - structural pass (2026-09-15)
|
||||
|
||||
Worker `w3d:main`. Unit `source/main/main.c`. **Real file unchanged** (`git diff --stat -- source/main/main.c`
|
||||
empty). Notes with per-function report blocks: `scratch/workers/w3d_main.md`; candidates, micro-labs and the two
|
||||
census helpers: `scratch/workers/w3d_main/`.
|
||||
|
||||
## Baseline and final
|
||||
|
||||
`gate.py source/main/main --all --forbid-emitted-symbol _point_from_line3d`:
|
||||
**baseline == final == `exact 88 residual 6 unwritten 1`**; `_point_from_line3d` guard passed;
|
||||
`scratch/parkcheck.py` 5 parks drift 0; `tools/fake_match_scan.py` 0 review leads.
|
||||
Nothing landed: the three structural targets are PARKED and none reached strict EXACT, and `_main_crash` stays
|
||||
owner-blocked. Baseline listing: `scratch/workers/w3d_main/base_main.txt`.
|
||||
|
||||
Duplicate prevention: read `main_obj_opus5_150k_w1_20260914`, `main_obj_opus5_100k_waveD_20260914`,
|
||||
`main_obj_natural_state_and_render_packet_20260909`, `main_obj_lifecycle_and_fuzzy_reconstruction_20260909`,
|
||||
`main_obj_jonas_crash_policy_closeout_20260826`, `small_unwritten_threshold_wave_main_hs_ai_20260913` and
|
||||
`scratch/workers/tiny_stubs.md`. **waveD's scratch tree (`scratch/workersD/`) no longer exists**, so its
|
||||
`best_update_time.c` / `frd3a.c` / `gr1.c` / `wc2.c` could not be reused; every finding below was re-derived from
|
||||
January's bytes. w1's `branch_sweep` over 54 historical blobs (nothing better) was not repeated.
|
||||
|
||||
## Tooling added (reusable)
|
||||
|
||||
- `scratch/workers/w3d_main/realdiff.py <unit> <fn> <obj>` - alndiff with the relocation *offset* and the
|
||||
`defined-noncode:.rdata:` vs `symbol:` literal-owner spelling normalised away, so the printed count is genuine
|
||||
code difference. Raw alndiff row counts over-report by 3-4x on relocation-dense functions.
|
||||
- `scratch/workers/w3d_main/slots.py <unit> <fn> <obj>` - per-`[ebp+-N]` reference census of both sides
|
||||
(the laws_w3 A24 detector; modelled on `W:w3_projectiles/slots.py`).
|
||||
|
||||
## Per-function outcomes
|
||||
|
||||
| Function | Padded | Baseline | Best this wave | Verdict |
|
||||
| --- | ---: | --- | --- | --- |
|
||||
| `_main_update_time` (parked) | 1440 | 1440 / **118** relocs, 65 blocks / 353 rows | **1440 / 117 relocs** (== January), 27 blocks / 86 rows, sha c6c6733d193740aa (`ut_v10.c`) | park re-baseline proposed |
|
||||
| `_main_frame_rate_debug` (parked) | 544 | **528** / **51** relocs | **544** / 51 relocs, 3 blocks / 12 rows (`frd_v2.c`) | 8 B + 1 reloc short |
|
||||
| `_main_game_render` (parked) | 448 | 464 / 29 relocs | 464 / 29 (one of the two defects closed, `gr_v4.c`) | not closer overall |
|
||||
| `_main_crash` | 16 | UNWRITTEN | - | OWNER-BLOCKED (NULL-deref UB) |
|
||||
|
||||
### `_main_update_time` - size and relocation identity now both EXACT
|
||||
|
||||
Census (mandatory step): frame `sub esp,0x34` vs our `0x30` (one missing dword local); relocation count-by-target
|
||||
differs in exactly one row, `_main_globals+950` 4 vs 5 (a second read of `vblank_interval_current` inside the
|
||||
ignore-failure conjunction). Twelve byte-proven source defects were found and fixed (full list with the January
|
||||
byte evidence in the notes file), the most load-bearing being:
|
||||
|
||||
1. `60 / (requested_rate ? requested_rate : 30)` - January's `jne` reuses the `test ax,ax` flags of the `>= 0`
|
||||
test and puts the default 30 in a 32-bit register, so the rate selection is an **int-typed conditional
|
||||
expression**, not an `if (rate == 0) rate = 30;` statement; the field read must stay inside the `&&` so it CSEs.
|
||||
2. `short_target_index` and `best_interval = 5` are assigned **after** the first `_snprintf` (this alone restores
|
||||
the missing frame slot and took the frame from 0x30 to 0x34).
|
||||
3. the failure-count clamp is `x = counts[slot]; if (x >= 99) x = 99;` - cseries `MIN` is `((a)>(b)?(b):(a))` and
|
||||
emits the 99 store first, January stores the value first and overwrites (`jl`).
|
||||
4. `if (!ignore_failure) {record} else {label = "ignor";}`, `if (target >= last + 15) {"ok"} else {"wt"/"dn"}`,
|
||||
`counts[slot] >= 4 ? "dn" : "wt"` - three arm orders each proven by which block January makes the fall-through.
|
||||
5. `slot >= vblank_interval_requested`, `(short)(current_interval / 2) <= slot_bucket`,
|
||||
`(__int64)rasterizer_globals.frame_and_vertical_blank_index > target_index`, strict `>` for
|
||||
`throttle_start_index > initial_index + slot`, and `" des %d"` printing `best_interval`.
|
||||
|
||||
Whole-TU gate of `ut_v10.c`: 88/6/1 with every baseline-EXACT row unchanged, the other four parks drift 0, guard
|
||||
passed, fake scan clean, and the owner census versus `build/base` shows no new owner. The remaining 27 blocks are
|
||||
callee-saved assignment of the 64-bit `target_index` pair, one 16-bit-vs-32-bit load of a short struct field whose
|
||||
upper half is padding, the conditional-expression format-string phi (per-arm push vs a register), and the
|
||||
placement of the cold `"ignor"` block.
|
||||
|
||||
### `_main_frame_rate_debug` - size closed, one relocation short
|
||||
|
||||
Census: frame identical (`sub esp,0x214`); exactly two relocation rows differ - `_global_screenshot_count+902`
|
||||
(`framerate_sample_index`) 4 vs 3, and the threshold literal `__real@3fa26e978d4fdf3c` vs our
|
||||
`__real@3fa26e978d4fdf3b`. Three byte-proven defects were fixed:
|
||||
|
||||
- the threshold is the **folded value of an expression** (0.036000000000000004), not the literal `0.036`;
|
||||
`1.08/30.0`, `36*0.001`, `3.6/100.0` fold to it, `0.036`, `36.0/1000.0`, `0.03*1.2` do not (measured).
|
||||
The spelling itself is not byte-identifiable - `1.08 / 30.0` was used and is disclosed as a choice.
|
||||
- the ring-buffer wrap is a **signed** `% 8` (`movsx`, `and eax,0x80000007`, `jns/dec/or/inc`, 15 B);
|
||||
`% NUMBEROF(...)` makes the expression `size_t` and collapses to `and dl,7` (3 B) - 12 of the 16 missing bytes.
|
||||
(R7 forbids `% (long)NUMBEROF(x)`, so the divisor has to be a genuine signed constant.)
|
||||
- the index is incremented at **char width first** (`inc dl; movsx eax,dl`), i.e. `sample_index++;` as its own
|
||||
statement; `(sample_index + 1) % 8` reuses the already-extended index and also loses January's `mov eax,1`
|
||||
hoisted above the SET_FLAG branch.
|
||||
|
||||
After those, the only difference in the whole function is that January emits the `framerate_sample_index` load
|
||||
TWICE with different successors (0x56 on the `!framerate_reset` edge, before the second `debug_frame_rate` test,
|
||||
and 0x6a on the `framerate_reset && debug_frame_rate` edge, which is jump-threaded straight into the body) while
|
||||
our build cross-jumps the two identical one-instruction loads into one block - 8 bytes and one relocation.
|
||||
All four admissible CFG spellings of the predicate (nested both ways, `&&`, `||`) are byte-identical; every
|
||||
placement that moves the load out of the pre-test position sinks it to a single copy (frd_v3/v7/v8/v10, 528 B).
|
||||
|
||||
### `_main_game_render` - the width defect is solved, the tail duplication is not
|
||||
|
||||
Census: frame identical (`sub esp,0x14`, five slots, same roles); exactly one relocation row differs,
|
||||
`_observer_get_camera` 1 vs 2; instructions 0..66 are byte-identical to January.
|
||||
|
||||
- **`last_local_player_index` is a `short`, not a `long`.** January stores the raw return of the short-returning
|
||||
`local_player_get_next` (`mov dword ptr [ebp-0xc], eax`, no `movsx`) yet compares and stores 16-bit. Micro-labs
|
||||
(`lab/l1.c`, `lab/l2.c`) show VC7 always emits `movsx` for `long x = short_fn()`, and that a **`short` local
|
||||
whose upper half is never read gets a full dword slot with dword stores** - exactly January's pattern. Stian
|
||||
(`main.c:4224 __int16 next_player`) and HCEA (`int16_t last_assigned_player`) agree. `gr_v4.c` closes that hunk.
|
||||
- The remaining defect is that VC7 tail-duplicates the 5-instruction shared observer tail into the film-playback
|
||||
arm. Micro-labs `lab/l3.c`-`l5.c` reproduce both behaviours exactly: a 5-instruction / 17-byte join block is
|
||||
duplicated into a fall-through predecessor, a 6-instruction / 20-byte one is not, and January's sixth
|
||||
instruction is `mov dword ptr [ebp-0x14], eax` - i.e. January does not register-forward `observer` across the
|
||||
join. Refuted causes: observer live-out of the loop (January pushes literal 0 for the console window and for
|
||||
`render_frame`), loop-scope declaration, arm polarity, NULL assigned in the arms, `console_window` before
|
||||
`set_window_camera_values`, and the HCEA flat else-if chain (480/30, worse).
|
||||
- `gr_v4` is not strictly closer overall (the short type shifts the console-window register rotation), so nothing
|
||||
is proposed for this park; the finding and the reopen criterion replace waveD's.
|
||||
|
||||
### `_main_crash`
|
||||
|
||||
Unchanged: the 16 bytes are a store of a literal's address through absolute address 0. Brief section 5 and the
|
||||
crash-policy closeout forbid reproducing NULL-dereference UB and no new provenance appeared this wave.
|
||||
|
||||
## Not spent this wave
|
||||
|
||||
`_main_get_window_count` (tail-merged conditional-arm constant block placement) and `_compute_subframe_counts`
|
||||
(`setl/test/je` for a loop-body if) were left untouched - they are not [size]/[relocs] rows and neither waveD's
|
||||
reopen criterion has been met by a new law. `_main_load_last_solo_map` (the only non-parked residual) is the
|
||||
recorded ESI/EDI callee-saved permutation, exhausted in waveD and w1.
|
||||
|
||||
## Proposals
|
||||
|
||||
1. **Park re-baseline `_main_update_time`** to `scratch/workers/w3d_main/ut_v10.c` (or the combined file
|
||||
`scratch/workers/w3d_main/combined_ut_frd.c`): 1440 / **117** relocs / sha `c6c6733d193740aa` versus the
|
||||
recorded base 1440 / 118 / `5f8cfd1a...`. Strictly closer on every axis (relocation identity now equals
|
||||
January; 353 -> 86 differing instruction rows; the recorded objdiff base was 62.5%). Zero strict credit.
|
||||
2. **Park re-baseline `_main_frame_rate_debug`** to `scratch/workers/w3d_main/frd_v2.c`: **544** / 51 /
|
||||
sha `3c132f904203944a` versus the recorded base 528 / 51 / `5608160797d3d13d`. Size now equals January and the
|
||||
owner census picks up January's own `__real@3fa26e978d4fdf3c` literal COMDAT in place of our `...3b`.
|
||||
Zero strict credit. Discloses the un-identifiable threshold spelling (`1.08 / 30.0`).
|
||||
3. No header or config change is needed by anything in this wave.
|
||||
|
||||
## Reopen criteria (replacing waveD's)
|
||||
|
||||
- `_main_update_time`: start from `ut_v10.c`; what remains is the callee-saved assignment of the 64-bit
|
||||
`target_index` pair (January keeps the low half in EBX and reloads the high half from its home after the
|
||||
throttle-flag store) and the format-string conditional-expression phi (per-arm `push` versus a materialised
|
||||
register).
|
||||
- `_main_frame_rate_debug`: a natural source form in which the `framerate_reset && debug_frame_rate` path reaches
|
||||
the sample body without re-testing `debug_frame_rate`, so the two `framerate_sample_index` loads keep different
|
||||
successors and are not cross-jumped. Everything else already matches byte for byte in `frd_v2.c`.
|
||||
- `_main_game_render`: a source form that makes VC7 keep the `observer` store at its definition (memory home, not
|
||||
EAX forwarding) with the loop's register pressure unchanged; with `short last_local_player_index` the rest of
|
||||
the function is byte-identical to January.
|
||||
- `_main_crash`: an explicit owner ruling on intentional crash routines (unchanged).
|
||||
|
||||
## Candidate index (`scratch/workers/w3d_main/`)
|
||||
|
||||
`ut_v1..ut_v10.c` (update_time), `frd_v1..frd_v10.c` (frame_rate_debug), `gr_v1..gr_v8.c` (game_render),
|
||||
`combined_ut_frd.c` (both park re-baselines in one file), `lab/l1.c..l5.c` + `lab/run.py` (micro-labs),
|
||||
`realdiff.py`, `slots.py`, `base_main.txt`, `ut_v10_all.txt`, `frd_v2_all.txt`, `combined_all.txt`.
|
||||
@@ -0,0 +1,184 @@
|
||||
# `observer.obj` third-pass structural census (Opus5 250K house-clean lane, wave w3d, 2026-09-15)
|
||||
|
||||
## Result
|
||||
|
||||
No real-file change and no new exact credit. `source/camera/observer.c` stays at **24 exact / 2 residual /
|
||||
0 unwritten** (guard passes, parks drift 0, owner census unchanged, fake scan 0 leads).
|
||||
`git diff --stat -- source/camera/observer.c` is empty. Both targets are classified NOT-LANDED /
|
||||
SKIPPED-EXHAUSTED with a complete structural census recorded, which is the intended outcome of a structural
|
||||
pass over rows whose structure is already exact in scratch.
|
||||
|
||||
## Scope and provenance
|
||||
|
||||
- Targets (both PARKED, scratch-only): `_observer_update_command` (1040/33) and `_observer_update_positions`
|
||||
(1568/40).
|
||||
- Prior ledgers read in full: `observer_obj_opus5_150k_w3_20260914.md`, `observer_obj_opus5_150k_w2_20260914.md`,
|
||||
`observer_obj_opus5_100k_waveD_20260914.md` (which themselves consumed
|
||||
`observer_obj_replacement_exact_packet_20260912.md`, `observer_obj_real_update_caller_cluster_20260905.md`,
|
||||
`observer_obj_integrator_penetration_independent_review_20260905.md`).
|
||||
- Worker notes read: `scratch/workers/observer.md` (w2),
|
||||
`C:/halo-worktrees/opus5-100k-consolidated-20260914/scratch/workersD/observer.md` (waveD),
|
||||
`scratch/workers/w3_observer_editor_flying_camera/` (w3).
|
||||
- Laws: `scratch/w2/laws_w2.md` and `scratch/w3/laws_w3.md` read completely and applied as detectors.
|
||||
- Worker notes for this wave: `scratch/workers/w3d_t3_observer.md`; scratch dir
|
||||
`scratch/workers/w3d_t3_observer/`.
|
||||
|
||||
## Baseline and final gate
|
||||
|
||||
`gate.py source/camera/observer --all --forbid-emitted-symbol _point_from_line3d`
|
||||
|
||||
- Baseline: exact 24 / residual 2 / unwritten 0; guard pass.
|
||||
- Final real-file gate (`scratch/workers/w3d_t3_observer/final.gate.txt`, object `final.obj`): row-by-row
|
||||
identical to the baseline.
|
||||
- `scratch/parkcheck.py`: parks 2, drift 0 (1056/35 and 1280/28, equal to `measurements.base`).
|
||||
- `tools/fake_match_scan.py source/camera/observer.c`: 0 review leads.
|
||||
- Owner census: unchanged by construction (the real file is byte-identical to HEAD).
|
||||
|
||||
## The framing fact this pass establishes
|
||||
|
||||
The `[size]`/`[relocs]` tags on the two rows describe the FROZEN PARKED bodies, which predate wave D. The best
|
||||
scratch candidate `scratch/workers/observer/a3.c` (waveD `ecmd1` command body + w2 `pos_a3` positions body)
|
||||
was re-gated at this HEAD (`scratch/workers/w3d_t3_observer/a3.c` -> `a3.obj`) and gives
|
||||
|
||||
residual 1040 _observer_update_command [sha] <- size and relocs EQUAL
|
||||
residual 1568 _observer_update_positions [sha] <- size and relocs EQUAL
|
||||
== exact 24 residual 2 unwritten 0, guard pass
|
||||
|
||||
so the structural gap the wave was pointed at is already closed in scratch. The censuses below are therefore
|
||||
taken against the live frontier (`a3.obj`).
|
||||
|
||||
## Structural census
|
||||
|
||||
### Frame census
|
||||
|
||||
| function | January `sub esp,N` | candidate | slots missing | slots extra |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `_observer_update_positions` | 0x84 | 0x84 | none | none |
|
||||
| `_observer_update_command` | 0xc | 0xc | none | none |
|
||||
|
||||
`_observer_update_positions`: every `[ebp-N]` slot matches, including the 56-byte
|
||||
`struct observer_derivative displacement` at [ebp-0x84] with its untouched 12-byte tail, the ten pointer homes
|
||||
at -0x1c..-0x48, and the inlined `axis` copy at [ebp-0x10] (i), [ebp-0xc] (j), [ebp-8] (k) with the magnitude
|
||||
temp at [ebp-4].
|
||||
|
||||
`_observer_update_command`: same size and the same three dword slots, with the ROLES permuted -
|
||||
January [ebp-4] = `parameter_flags` pointer, [ebp-8] = `timer` pointer, [ebp-0xc] = the `valid_real()` int-pun
|
||||
temp of the 0x172 assert (9 stores); ours [ebp-4] = flags pointer and then the homed down-counter,
|
||||
[ebp-8] = the temp, [ebp-0xc] = `timer`. This permutation is a consequence of the single defect below, not an
|
||||
independent one: once VC7 merges the timer induction variable, the `timer` local keeps one reference and drops
|
||||
below the 18-reference assert temp in the descending-reference frame ranking.
|
||||
|
||||
### Relocation census (`relocdiff --count-by-target --allow-structural`)
|
||||
|
||||
- Parked positions body: 40 target / 28 candidate, 29 differing rows - surplus `_csmemset`, a surplus
|
||||
out-of-line `_valid_real_vector3d_axes2` call, 1 vs 7 and 1 vs 3 count-table references, and the missing
|
||||
`__real@3f800000` x5 / `__real@3f50624de0000000` x3 / `__real@3f1a36e2e0000000` x3 of the inlined
|
||||
normalize3d and inlined axes predicate. All fixed by `pos_a3`.
|
||||
- Parked command body: 35 vs 33 (two surplus, fixed by the waveD cross-jumped `MIN(*last_timer, 2.f)` block).
|
||||
- **a3 candidate vs January, both functions: 40 == 40 and 33 == 33 with equal targets.** The only rows
|
||||
`relocdiff` reports are the `--source` literal-COMDAT naming artifact (`['symbol', X, 0]` vs
|
||||
`['defined-noncode', '.rdata', X, 0]`). No call, global or literal is missing or extra.
|
||||
|
||||
### Instruction census
|
||||
|
||||
471 == 471 (positions) and 307 vs 302 (command). No control-flow, loop-shape, width or branch-polarity
|
||||
difference in either function.
|
||||
|
||||
## `_observer_update_command`: SKIPPED-EXHAUSTED (one backend defect)
|
||||
|
||||
The residual reduces to a single mechanism. January keeps FOUR independent loop variables:
|
||||
edx = `timer` pointer (`add edx,4`), edi = `last_timer` pointer (`add edi,4`), [ebp-4] = the spilled
|
||||
`parameter_flags` byte pointer (`inc ecx`, reloaded at the top of every iteration), esi = the down-counter
|
||||
(`mov esi,5` / `dec esi`). VC7 on our source applies a linear-function-test replacement to `timer`: it
|
||||
computes the loop-invariant delta `esi = timer0 - last0` in the preheader and addresses all four `*timer` uses
|
||||
as `[esi+edi]`, which frees EDX for the flags pointer and spills the counter into [ebp-4] instead. The four
|
||||
alndiff hunks (preheader, the three `[edx]` vs `[esi+edi]` accesses, the loop tail, the frame-slot
|
||||
permutation) are all consequences of that one merge.
|
||||
|
||||
Everything else is byte-identical: the 0x172 assert expansion, the `observer->pending_command` reloads after
|
||||
the assert, the cross-jumped `MIN(*last_timer, 2.f)` block, the `<` NaN polarity of the time-invalid test and
|
||||
the `rep movsd` struct assignment.
|
||||
|
||||
Law detector pass: no law in laws_w2 (A1-A22) or laws_w3 (A23-A45) addresses induction-variable merging.
|
||||
A5/A33 (indexed access vs pointer walk changes the strength-reduced bias) is the closest and wave D already
|
||||
spent five indexed/pointer permutations, all worse (raw diffs 52/61/72/119 against 49-54 for the pointer
|
||||
form); A24 does not apply (the single `short` parameter arrives in AX with no home); A23, A25, A31 and A38 do
|
||||
not apply to this body. No shapes were spent this wave.
|
||||
|
||||
**Reopen criterion.** A January-side reason for VC7 not to apply the linear-function-test replacement to the
|
||||
`timer` pointer against `last_timer`: a genuine live-out use of one of the two pointers, a loop form that
|
||||
defeats the merge, or a same-compiler donor function that keeps two same-step pointer induction variables of
|
||||
DIFFERENT objects unmerged inside a counted loop. Start from `scratch/workers/observer/v1.c`.
|
||||
|
||||
## `_observer_update_positions`: NOT LANDED (one backend mechanism + a credibility blocker)
|
||||
|
||||
The residual is 18 instructions in three hunks:
|
||||
|
||||
1. 0x27b-0x28e - the inlined `normalize3d` -> `magnitude_squared3d` of the `axis` copy inside the inlined
|
||||
`observer_apply_rotational_displacement`: January k*k, i*i, j*j; ours k*k, j*j, i*i.
|
||||
2. 0x447-0x461 - the inlined `dot_product3d(&observer->forward, &observer->up)`: January k, i, j; ours k, j, i.
|
||||
3. 0x4ab-0x4ce - three commutative operand ranks in the repair `cross_product3d` j and k components.
|
||||
|
||||
New diagnostic facts (not in any prior ledger):
|
||||
|
||||
- (a) Hunks 1 and 2 are ONE mechanism. The positional pattern is identical in both builds - term 1 is the k
|
||||
term, term 2 loads the FIRST helper parameter first, term 3 loads the SECOND parameter first - and only
|
||||
WHICH of i/j occupies position 2 differs, even though hunk 1 works on the components of a local struct copy
|
||||
held in ecx/edx/eax and hunk 2 on memory fields of two different structs. The open question is therefore
|
||||
narrow: is the offset-0 leaf ranked above or below the offset-4 leaf.
|
||||
- (b) In the two `valid_real_normal3d` magnitudes at 0x393-0x3b3 and 0x3ea-0x40a, the SAME header helper
|
||||
(`magnitude_squared3d`) is emitted i,j,k (source order) and our build matches January EXACTLY. There the
|
||||
three leaves are pre-loaded onto the x87 stack (the A4 "all loads then all products" form). The inversion
|
||||
therefore only ever appears in the direct-memory-operand form. This clears the real_math.h
|
||||
`magnitude_squared3d` / `dot_product3d` bodies and localises the A22/A35 family question.
|
||||
- (c) `_observer_apply_rotational_displacement` compiled out of line is strict EXACT (128 B) from the same
|
||||
source, with `normalize3d` out of line there, so the exact out-of-line row says nothing about the inlined
|
||||
rank.
|
||||
|
||||
Shapes spent this wave (the two allowed for a residual `[sha]` row), both **measurably inert** - the candidate
|
||||
`_observer_update_positions` sha stays `fe491b6d48e19230` and all 24 siblings stay exact:
|
||||
|
||||
| shape | source change | result |
|
||||
| --- | --- | --- |
|
||||
| s2 (`scratch/workers/w3d_t3_observer/s2.c`) | `!(A && B && C)` instead of `!A \|\| !B \|\| !C` for the axes check | 1568/40, byte-identical to a3 |
|
||||
| s3 (`scratch/workers/w3d_t3_observer/s3.c`) | declare-then-assign split of `axis` / `angle` in `_observer_apply_rotational_displacement` (A38 probe) | 1568/40, byte-identical to a3; out-of-line row stays EXACT |
|
||||
|
||||
So the enclosing boolean spelling and the initializer/assignment split do not move the x87 leaf rank; add both
|
||||
to the do-not-repeat list.
|
||||
|
||||
**Credibility blocker.** January inlines the axes predicate (no `_valid_real_vector3d_axes2` relocation, three
|
||||
0.001 doubles and three 1e-4 doubles). The `real_math.h` body at HEAD (nested `if` plus a named `product`)
|
||||
stays out of line in this caller, so the only in-lane spellings that reproduce the January inline layout are
|
||||
(i) writing the three helper calls in `observer.c`, which is manual inlining of an existing owner-header
|
||||
helper (house rule 13 / R15 class), or (ii) the E6 conjunction header, which laws_w3 records as HOLD (net
|
||||
-3,664 padded; it loses `_bitmap_copy` 2,784 and `_bitmap_2d_uncompress_from_mipmap` 880) and which wave w3
|
||||
measured as NOT closer here (the dot order becomes i,k,j). E6 therefore does not unlock observer and no header
|
||||
proposal is filed.
|
||||
|
||||
**Reopen criterion (refined).** A measured VC7 rule that predicts, for a three-term commutative sum whose
|
||||
leaves are direct memory operands, whether the offset-0 leaf ranks above or below the offset-4 leaf (January
|
||||
k,i,j against our k,j,i), together with the matching `cross_product3d` operand rank, and that also explains
|
||||
fact (b). Start from `scratch/workers/observer/pos_a3.c`. Any landing additionally needs a ruling on spelling
|
||||
(i) or a non-regressing replacement for E6.
|
||||
|
||||
## Do-not-repeat (this wave)
|
||||
|
||||
- positions: `!(A && B && C)` in place of the negated `||` chain (byte-identical, s2);
|
||||
- positions: declare-then-assign split of `axis`/`angle` in `_observer_apply_rotational_displacement`
|
||||
(byte-identical, s3);
|
||||
- positions: the E6 conjunction header as a route to this row (measured in w3, not closer, and net negative
|
||||
board-wide);
|
||||
- command: no shape is worth spending until the induction-variable-merge question has an answer.
|
||||
|
||||
## Proposals
|
||||
|
||||
None. No park-reopen (neither body is strict EXACT), no header packet, no config change.
|
||||
|
||||
## Value for other units
|
||||
|
||||
Fact (b) above is reusable by the laws_w3 C1 "local-first x87 products" family
|
||||
(`_rasterizer_lens_flares_draw`, `_light_volume_render`, `_collision_debug_render`,
|
||||
`_rasterizer_model_draw_environment_shader`, `_triangle_coplanar`, `_actor_get_stopping_distances`): in this
|
||||
object the A22/A35 rank inversion never appears when the three leaves are pre-loaded onto the x87 stack, only
|
||||
when the products consume memory operands directly, and the difference is confined to the relative rank of the
|
||||
offset-0 and offset-4 leaves.
|
||||
+130
-143
@@ -95,6 +95,7 @@ symbols in this file:
|
||||
#include "physics/collisions.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "units/unit_definitions.h"
|
||||
#include "units/vehicle_definitions.h"
|
||||
#include "units/vehicles.h"
|
||||
|
||||
/* ---------- constants */
|
||||
@@ -369,180 +370,166 @@ boolean action_vehicle_evaluate_seat(
|
||||
struct actor_variant_definition *variant_definition =
|
||||
actor_variant_definition_get(actor->meta.variant_definition_index);
|
||||
boolean result = FALSE;
|
||||
real_point3d entrance_point;
|
||||
real_point3d seat_point;
|
||||
real_point3d seat_hint_point;
|
||||
|
||||
if (unit_seat_filled(vehicle_index, seat_index) ||
|
||||
(TEST_FLAG(
|
||||
if (!unit_seat_filled(vehicle_index, seat_index) &&
|
||||
(!TEST_FLAG(
|
||||
definition->flags2,
|
||||
_actor_definition_disallow_vehicle_combat_bit) &&
|
||||
!unit_seat_allow_noncombatants(vehicle_index, seat_index)) ||
|
||||
!unit_get_seat_entrance_point(
|
||||
_actor_definition_disallow_vehicle_combat_bit) ||
|
||||
unit_seat_allow_noncombatants(vehicle_index, seat_index)))
|
||||
{
|
||||
real_point3d entrance_point;
|
||||
real_point3d seat_point;
|
||||
real_point3d seat_hint_point;
|
||||
|
||||
if (unit_get_seat_entrance_point(
|
||||
actor->meta.unit_index,
|
||||
vehicle_index,
|
||||
seat_index,
|
||||
&entrance_point,
|
||||
&seat_point,
|
||||
&seat_hint_point))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
{
|
||||
real_point3d vehicle_origin;
|
||||
real_vector2d entry_direction;
|
||||
real_vector2d computed_facing;
|
||||
real computed_facing_z = 0.0f;
|
||||
real entrance_dx;
|
||||
real entrance_dy;
|
||||
real seat_dx;
|
||||
real seat_dy;
|
||||
real distance_to_seat;
|
||||
struct prop_iterator iterator;
|
||||
struct prop_datum *prop;
|
||||
|
||||
object_get_origin(vehicle_index, &vehicle_origin);
|
||||
entry_direction.i = seat_point.x - entrance_point.x;
|
||||
entry_direction.j = seat_point.y - entrance_point.y;
|
||||
computed_facing = entry_direction;
|
||||
if (normalize2d(&computed_facing) == 0.0f)
|
||||
{
|
||||
computed_facing.i = actor->input.facing_vector.i;
|
||||
computed_facing.j = actor->input.facing_vector.j;
|
||||
computed_facing_z = actor->input.facing_vector.k;
|
||||
}
|
||||
real_point3d vehicle_origin;
|
||||
real_vector3d facing;
|
||||
real distance_to_seat;
|
||||
|
||||
entrance_dx = entrance_point.x - actor->input.position.body_position.x;
|
||||
seat_dx = seat_point.x - actor->input.position.body_position.x;
|
||||
entrance_dy = entrance_point.y - actor->input.position.body_position.y;
|
||||
seat_dy = seat_point.y - actor->input.position.body_position.y;
|
||||
if (square_root(entrance_dy*entrance_dy + entrance_dx*entrance_dx) <=
|
||||
square_root(seat_dy*seat_dy + seat_dx*seat_dx))
|
||||
{
|
||||
distance_to_seat = square_root(
|
||||
entrance_dy*entrance_dy + entrance_dx*entrance_dx);
|
||||
}
|
||||
else
|
||||
{
|
||||
distance_to_seat = square_root(seat_dy*seat_dy + seat_dx*seat_dx);
|
||||
}
|
||||
result = TRUE;
|
||||
|
||||
prop_iterator_new(&iterator, actor_index);
|
||||
for (prop = prop_iterator_next(&iterator);
|
||||
prop;
|
||||
prop = prop_iterator_next(&iterator))
|
||||
{
|
||||
if (!prop->enemy && prop->actor_index != NONE)
|
||||
object_get_origin(vehicle_index, &vehicle_origin);
|
||||
facing.i = seat_point.x - entrance_point.x;
|
||||
facing.j = seat_point.y - entrance_point.y;
|
||||
facing.k = 0.0f;
|
||||
if (normalize2d((real_vector2d *)&facing) == 0.0f)
|
||||
{
|
||||
struct actor_datum *other_actor = actor_try_and_get(prop->actor_index);
|
||||
facing = actor->input.facing_vector;
|
||||
}
|
||||
|
||||
if (other_actor->state.action == _actor_action_vehicle &&
|
||||
other_actor->state.action_data.vehicle.vehicle_index == vehicle_index)
|
||||
distance_to_seat = MIN(
|
||||
distance2d(
|
||||
(real_point2d const *)&actor->input.position.body_position,
|
||||
(real_point2d const *)&entrance_point),
|
||||
distance2d(
|
||||
(real_point2d const *)&actor->input.position.body_position,
|
||||
(real_point2d const *)&seat_point));
|
||||
|
||||
{
|
||||
struct prop_iterator iterator;
|
||||
struct prop_datum *prop;
|
||||
|
||||
prop_iterator_new(&iterator, actor_index);
|
||||
while (prop = prop_iterator_next(&iterator))
|
||||
{
|
||||
struct vehicle_state_data *other_state =
|
||||
&other_actor->state.action_data.vehicle;
|
||||
|
||||
if (other_state->seat_index == seat_index)
|
||||
if (!prop->enemy && prop->actor_index != NONE)
|
||||
{
|
||||
real other_dx = other_state->destination_point.x -
|
||||
other_actor->input.position.body_position.x;
|
||||
real other_dy = other_state->destination_point.y -
|
||||
other_actor->input.position.body_position.y;
|
||||
struct actor_datum *other_actor =
|
||||
actor_try_and_get(prop->actor_index);
|
||||
|
||||
if (other_dy*other_dy + other_dx*other_dx <
|
||||
distance_to_seat*distance_to_seat)
|
||||
if (other_actor->state.action == _actor_action_vehicle &&
|
||||
other_actor->state.action_data.vehicle.vehicle_index == vehicle_index)
|
||||
{
|
||||
return FALSE;
|
||||
struct vehicle_state_data *other_state =
|
||||
&other_actor->state.action_data.vehicle;
|
||||
|
||||
if (other_state->seat_index == seat_index)
|
||||
{
|
||||
real other_dx = other_state->destination_point.x -
|
||||
other_actor->input.position.body_position.x;
|
||||
real other_dy = other_state->destination_point.y -
|
||||
other_actor->input.position.body_position.y;
|
||||
|
||||
if (other_dy*other_dy + other_dx*other_dx <
|
||||
distance_to_seat*distance_to_seat)
|
||||
{
|
||||
result = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unit_seat_is_driver(vehicle_index, other_state->seat_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unit_seat_is_driver(vehicle_index, other_state->seat_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allow_any_seat)
|
||||
{
|
||||
struct unit_datum *vehicle = vehicle_get(vehicle_index);
|
||||
struct vehicle_definition *vehicle_definition =
|
||||
vehicle_specific_definition_get(vehicle->definition_index);
|
||||
|
||||
(void)vehicle_definition;
|
||||
unit_seat_is_driver(vehicle_index, seat_index);
|
||||
}
|
||||
|
||||
{
|
||||
real_vector2d to_seat_direction;
|
||||
real facing_dot;
|
||||
boolean within_range;
|
||||
boolean correct_facing;
|
||||
boolean could_potentially_fake;
|
||||
real seat_weight;
|
||||
|
||||
to_seat_direction.i =
|
||||
seat_point.x - actor->input.position.body_position.x;
|
||||
to_seat_direction.j =
|
||||
seat_point.y - actor->input.position.body_position.y;
|
||||
normalize2d(&to_seat_direction);
|
||||
facing_dot = actor->input.facing_vector.j*to_seat_direction.j +
|
||||
actor->input.facing_vector.i*to_seat_direction.i;
|
||||
within_range = distance_to_seat < 0.7f;
|
||||
correct_facing = facing_dot > 0.6f;
|
||||
could_potentially_fake =
|
||||
distance_to_seat < 1.1f && facing_dot > 0.0f;
|
||||
seat_weight = 10.0f / (distance_to_seat + 1.0f);
|
||||
|
||||
if (TEST_FLAG(
|
||||
variant_definition->flags,
|
||||
_actor_variant_definition_prefer_passenger_seat_bit))
|
||||
if (result)
|
||||
{
|
||||
if (!unit_seat_is_gunner(vehicle_index, seat_index))
|
||||
struct unit_datum *vehicle = vehicle_get(vehicle_index);
|
||||
real_vector2d to_seat_direction;
|
||||
real facing_dot;
|
||||
boolean within_range;
|
||||
boolean correct_facing;
|
||||
boolean could_potentially_fake;
|
||||
real seat_weight;
|
||||
|
||||
if (!allow_any_seat)
|
||||
{
|
||||
vehicle_specific_definition_get(vehicle->definition_index);
|
||||
unit_seat_is_driver(vehicle_index, seat_index);
|
||||
}
|
||||
|
||||
vector_from_points2d(
|
||||
(real_point2d const *)&actor->input.position.body_position,
|
||||
(real_point2d const *)&seat_point,
|
||||
&to_seat_direction);
|
||||
normalize2d(&to_seat_direction);
|
||||
facing_dot = dot_product2d(
|
||||
&to_seat_direction,
|
||||
(real_vector2d const *)&actor->input.facing_vector);
|
||||
within_range = distance_to_seat < 0.7f;
|
||||
correct_facing = facing_dot > 0.6f;
|
||||
could_potentially_fake =
|
||||
distance_to_seat < 1.1f && facing_dot > 0.0f;
|
||||
seat_weight = 10.0f / (distance_to_seat + 1.0f);
|
||||
|
||||
if (TEST_FLAG(
|
||||
variant_definition->flags,
|
||||
_actor_variant_definition_prefer_passenger_seat_bit))
|
||||
{
|
||||
if (!unit_seat_is_gunner(vehicle_index, seat_index))
|
||||
{
|
||||
seat_weight += 3.5f;
|
||||
}
|
||||
}
|
||||
else if (unit_seat_is_gunner(vehicle_index, seat_index))
|
||||
{
|
||||
seat_weight += 3.5f;
|
||||
}
|
||||
}
|
||||
else if (unit_seat_is_gunner(vehicle_index, seat_index))
|
||||
{
|
||||
seat_weight += 3.5f;
|
||||
}
|
||||
|
||||
if (entry_point)
|
||||
{
|
||||
*entry_point = entrance_point;
|
||||
}
|
||||
if (entry_facing)
|
||||
{
|
||||
entry_facing->i = computed_facing.i;
|
||||
entry_facing->j = computed_facing.j;
|
||||
entry_facing->k = computed_facing_z;
|
||||
}
|
||||
if (hint_point)
|
||||
{
|
||||
*hint_point = seat_hint_point;
|
||||
}
|
||||
if (seat_weight_reference)
|
||||
{
|
||||
*seat_weight_reference = seat_weight;
|
||||
}
|
||||
if (within_range_reference)
|
||||
{
|
||||
*within_range_reference = within_range;
|
||||
}
|
||||
if (correct_facing_reference)
|
||||
{
|
||||
*correct_facing_reference = correct_facing;
|
||||
}
|
||||
if (could_potentially_fake_reference)
|
||||
{
|
||||
*could_potentially_fake_reference = could_potentially_fake;
|
||||
if (entry_point)
|
||||
{
|
||||
*entry_point = entrance_point;
|
||||
}
|
||||
if (entry_facing)
|
||||
{
|
||||
*entry_facing = facing;
|
||||
}
|
||||
if (hint_point)
|
||||
{
|
||||
*hint_point = seat_hint_point;
|
||||
}
|
||||
if (seat_weight_reference)
|
||||
{
|
||||
*seat_weight_reference = seat_weight;
|
||||
}
|
||||
if (within_range_reference)
|
||||
{
|
||||
*within_range_reference = within_range;
|
||||
}
|
||||
if (correct_facing_reference)
|
||||
{
|
||||
*correct_facing_reference = correct_facing;
|
||||
}
|
||||
if (could_potentially_fake_reference)
|
||||
{
|
||||
*could_potentially_fake_reference = could_potentially_fake;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return result;
|
||||
}
|
||||
|
||||
static boolean action_vehicle_find_destination(
|
||||
|
||||
@@ -1961,6 +1961,7 @@ static void effect_generate_particles(
|
||||
particle_index,
|
||||
struct effect_particles_definition);
|
||||
short count_delta;
|
||||
long last_particle_count;
|
||||
long location_datum_index;
|
||||
struct effect_location_datum *instance;
|
||||
|
||||
@@ -1977,14 +1978,15 @@ static void effect_generate_particles(
|
||||
continue;
|
||||
}
|
||||
|
||||
last_particle_count = (long)(effect_evaluate_function_integral(
|
||||
particles->distribution_function,
|
||||
last_event_fraction) *
|
||||
effect->particle_counts[particle_index]);
|
||||
count_delta = (short)((long)(effect_evaluate_function_integral(
|
||||
particles->distribution_function,
|
||||
event_fraction) *
|
||||
effect->particle_counts[particle_index]) -
|
||||
(long)(effect_evaluate_function_integral(
|
||||
particles->distribution_function,
|
||||
last_event_fraction) *
|
||||
effect->particle_counts[particle_index]));
|
||||
last_particle_count);
|
||||
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\effects\\effects.c",
|
||||
@@ -2095,11 +2097,13 @@ static void effect_generate_particles(
|
||||
|
||||
if (TEST_FLAG(particles->flags, _effect_particle_attached_bit))
|
||||
{
|
||||
short node_index = instance->node_designator;
|
||||
|
||||
if (node_index != NONE)
|
||||
node_index &= (short)(FLAG(_effect_location_first_person_bit) - 1);
|
||||
|
||||
data.object_index = effect->object_index;
|
||||
data.node_index = instance->node_designator != NONE
|
||||
? (short)(instance->node_designator &
|
||||
(FLAG(_effect_location_first_person_bit) - 1))
|
||||
: NONE;
|
||||
data.node_index = node_index;
|
||||
data.initial_impulse = *global_zero_vector3d;
|
||||
}
|
||||
else
|
||||
@@ -2611,11 +2615,12 @@ static boolean effect_allowed_by_environment(
|
||||
struct location const *location,
|
||||
real_point3d const *world_point)
|
||||
{
|
||||
boolean allowed = environment == _effect_environment_anywhere;
|
||||
boolean allowed = FALSE;
|
||||
|
||||
switch (environment)
|
||||
{
|
||||
case _effect_environment_anywhere:
|
||||
allowed = TRUE;
|
||||
break;
|
||||
|
||||
case _effect_environment_air:
|
||||
|
||||
@@ -691,36 +691,38 @@ static void input_update_analog_button_state(
|
||||
byte value,
|
||||
boolean pressed)
|
||||
{
|
||||
byte new_threshold;
|
||||
|
||||
if (pressed)
|
||||
{
|
||||
if (value < ANALOG_BUTTON_PRESS_THRESHOLD_OFFSET)
|
||||
{
|
||||
value = 0;
|
||||
new_threshold = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
value -= ANALOG_BUTTON_PRESS_THRESHOLD_OFFSET;
|
||||
new_threshold = value - ANALOG_BUTTON_PRESS_THRESHOLD_OFFSET;
|
||||
}
|
||||
|
||||
if (value > *threshold)
|
||||
if (new_threshold > *threshold)
|
||||
{
|
||||
*threshold = value;
|
||||
*threshold = new_threshold;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (value > ANALOG_BUTTON_RELEASE_SATURATION)
|
||||
{
|
||||
value = UNSIGNED_CHAR_MAX;
|
||||
new_threshold = UNSIGNED_CHAR_MAX;
|
||||
}
|
||||
else
|
||||
{
|
||||
value += ANALOG_BUTTON_RELEASE_THRESHOLD_OFFSET;
|
||||
new_threshold = value + ANALOG_BUTTON_RELEASE_THRESHOLD_OFFSET;
|
||||
}
|
||||
|
||||
if (value < *threshold)
|
||||
if (new_threshold < *threshold)
|
||||
{
|
||||
*threshold = value;
|
||||
*threshold = new_threshold;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user