Commit Graph
112 Commits
Author SHA1 Message Date
Jonas Volman c5ce04c77c Fix biped limp-noodle interface ownership 2026-09-02 20:41:56 -07:00
Jonas Volman cd10033235 Reconstruct actors creation and spawn wave 2026-09-02 19:59:10 -07:00
Jonas Volman 60957d0cb2 Recover remaining small HUD frontier 2026-09-02 04:42:30 -07:00
Jonas Volman 5ea4a47ca6 bipeds: preserve January point helper ownership 2026-09-02 02:22:06 -07:00
Jonas Volman fede67af5b Preserve Units through owner header cleanup 2026-09-02 02:05:08 -07:00
Jonas Volman 92fd55d6eb Reconstruct nine biped functions 2026-09-02 01:57:40 -07:00
Jonas Volman 4277f892c1 Admit audited Claude reconstruction gains
Retain 18 strict exact-function gains across Actions, Actor Combat, Decals, Game Engine, Object Lights, and Bipeds after removing donor bodies that regressed exact objects, emitted unowned code, or violated source-credibility rules. Name authenticated private symbols, move declarations to owner headers, record honest residual parks, and preserve units.obj at 189/189.
2026-08-31 20:42:01 -07:00
Jonas Volman 9f74e9937f Import 909 authenticated January and HCEA symbol names 2026-08-31 16:11:03 -07:00
Jonas Volman 69ce74cc76 Recover biped export and speech priority leaves 2026-08-31 12:15:39 -07:00
Jonas Volman 179ea4a366 Recover small actor bitmap biped and weapon owners 2026-08-31 11:53:54 -07:00
Jonas Volman 6b797dc3de Recover biped limp and flight pair 2026-08-31 09:17:24 -07:00
Jonas Volman 777a30ba1d Place shared prototypes in owner headers 2026-08-30 23:58:08 -07:00
Jonas Volman 677d098217 Recover Players callers without codegen steering 2026-08-30 23:42:05 -07:00
Jonas Volman e8dd0dfeab Recover recorded animations naturally and park scheduling tie 2026-08-30 21:42:26 -07:00
Jonas Volman 84b67aef73 Remove inert Units control-flow artifacts 2026-08-30 21:06:53 -07:00
Jonas Volman ca0846407c Integrate AI script vehicle dispatch cluster 2026-08-29 21:58:49 -07:00
Jonas Volman 74eeadef77 ai: reconstruct AI debug exact checkpoint 2026-08-29 11:00:04 -07:00
Jonas Volman ce3ca83f34 ai: harvest twelve exact debug functions 2026-08-29 08:56:49 -07:00
Jonas Volman 937c659bb3 Finalize strict units object integration 2026-08-28 21:31:49 -07:00
Jonas VolmanandClaude Opus 5 748da59547 Close unit_export_function_values without a compiler barrier
Remove the _ReadWriteBarrier declaration, the #pragma intrinsic and the
barrier call that UEF-8 introduced, and recover January's schedule from
ordinary C89.

The barrier did exactly one thing: it stopped VC7's list scheduler from
hoisting the output-pointer formation above tag_get. Two source controls
replace it.

First, index the arrays instead of walking hand-written pointers. The
sibling object_export_function_values in source/objects/objects.c is
strict-exact and indexes; January's object shows VC7 strength-reducing
that into the same walking pointers and homed down-counter, with the
cdecl cleanup immediately after the call and both bases formed by lea
and no copy. Writing the unit exporter the same way makes the whole
preamble byte-exact and takes the residual from 30 bytes to 9. The
walking pointers in the target are compiler output, not source, which is
why every pointer-form experiment in the UEF matrix plateaued.

Second, bind animation->private_loop_frame_index to a named local used
twice, in the predicate and in the division. The last nine bytes were a
load-order swap; source operand order does not control it, but a two-use
local does. UEF-12 had tested named locals and seen them eliminated, but
it bound both operands with a single use each, and a single-use copy is
propagated away.

_unit_export_function_values is 384/384 bytes, 18/18 relocations, and its
normalized SHA-256 equals the target's 4149e4cd...b20c35;
section_infos_equal is True. The emitted section is raw byte-for-byte
identical to the previously accepted barrier build.

Verified: clean forced rebuild reproduces; compare_object_exact_sets
reports 189 exact both ways with lost_exact empty; verdicts diff over all
8246 functions gains 0 and regresses 0; halobetacache_build,
libcmt_build, semantic_progress and progress pass; object-admission,
semantic-match and parked audits pass; pytest tools 203 passed;
git diff --check clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:05 -07:00
Jonas VolmanandClaude Opus 5 978fd91b7e Make unit_update byte-identical to January
Split the camo regrowth guard's three-conjunct condition into nested ifs
with explicit else arms assigning the default rate, and scope the set
flag to the branch that uses it. Behaviour-identical: game_engine_running
is still evaluated exactly once, the short-circuit order is unchanged,
and every path that previously left set FALSE now assigns 1.f/120.f.

That flips VC7 from eager to lazy placement of the partially redundant
[ebp+8] reload. January duplicates it per predecessor -- 0x49c on the
main path, 0x4f6 at the bypass merge, 0x533 in the else arm, with the
jne going to 0x4f9 to skip the merge copy -- where we had hoisted a
single dominating copy to 0x48a.

_unit_update is now 1642 instructions with normalized_sha256 EQUAL to
January: zero differing bytes, from 632.

section_infos_equal still reports False on 2 of 259 relocations, both
the same function-local static spelled two ways: build/split has
`unit_update'::`114'::last_time where CL emits
?last_time@?HC@??unit_update@@9@9. That is the known naming gap in
config/symbols.json, not a codegen difference -- all 259 addresses and
types match and the other 69 differing records are forgiven by
symbolic_target.

CL /Zs /W3 unchanged at 43 diagnostics.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 51d3958e3b Name the two animation flag bits in unit_update (byte-neutral)
units.c:5569 and 5582 tested and cleared animation flags by raw bit
number, which the campaign's own source rules forbid. The 2011 HCEX PDB
resolves both enumerators: _unit_animation_fallen_on_front_bit is 3 and
_unit_animation_ignore_translation_bit is 2, matching our units.h and
the emitted masks (shr cl,3 and and byte ptr [ebx+0x248],0FBh).

The names also make the resurrect ternary read correctly: fallen on the
front resurrects front, otherwise back.

Verified byte-neutral: census stays 188/1/0 code, data unchanged, and
_unit_update is bit-identical. CL /Zs /W3 unchanged at 43 diagnostics.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 bc6208e46d Close unit_update_animation by fixing code_0019b160's parameter order
The 2011 HCEX PDB compiland dump records this helper as
unit_animation_update(long, long, struct animation_state *) -- the
animation pointer is the THIRD parameter. Our reconstruction had it
first. Reordering the prototype, the definition and all four call sites
to (long unit_index, long animation_graph_index, struct animation_state
*animation) also puts unit_index first, matching the convention used
everywhere else in this file.

That was the whole residual. VC7 assigns its custom register convention
by parameter order, so the wrong order changed which argument landed in
which register at every code_0019b160 call site -- the 23 differing
bytes all began at 0x195, inside the first of those argument setups.
The "one missing scratch allocation" reading of the residual was a
symptom, not the cause.

units.obj code census 187/2/0 -> 188/1/0; data unchanged.
_unit_update_animation and _code_0019b160 are both byte-exact.
CL /Zs /W3 unchanged at 43 diagnostics with identical codes.

Only _unit_update remains.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 c462b1df7f Adopt the PDB's parameter linkage and January's local names (byte-neutral)
The 2011 HCEX PDB records unit_update_animation's second parameter as
struct unit_animation_update_data * -- non-const. Drop the const from
both the prototype and the definition.

Adopt January's local names from the same source: old_world_matrix and
exit_velocity in unit_update_animation, last_aiming_vector and
object_matrix in unit_update.

Also apply the layout implied by January's own assert line numbers. The
two match_assert calls in unit_update_animation carry lines 0xb61 = 2913
and 0xb73 = 2931, so seventeen lines hold the whole if, declaration and
seven-arm switch. That fits only with the declaration carrying its
initialiser and no default arm, so write
short desired_base_seat_index = NONE; and drop default: break;.

Verified byte-neutral: census stays 187/2/0 code and unchanged data;
unit_update_animation stays at 23 differing bytes and unit_update at its
one instruction. CL /Zs /W3 unchanged at 43 diagnostics.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 884f9db1e1 Close unit_animation_set_state with a graph-index carrier local
Route the three animation_choose_random_permutation_internal call sites
through a long animation_graph_index local, matching the byte-exact
unit_start_animation_impulse idiom at units.c:8535 and units.c:3245.
unit_animation_set_state was the file's only site that inlined the field
expression into the call.

That flips one allocator decision: January spends EDI on the unit_seat
pointer (mov edi,eax / lea eax,[edi+0x58] / mov [ebp-0x1c],edi, then
direct mov ecx,[edi+0x40] and [edi+0x44] later) and homes animation_index
at [ebp-0xc); we did the reverse and reloaded the pointer at every use.
The frame going 0x18 -> 0x1c and all three or edx,0ffffffffh sites were
consequences, not separate differences.

units.obj code census 186/3/0 -> 187/2/0; data unchanged at 129/0/0.

Also reproduce January's setcc spelling in unit_update_animation: split
the shared alert/suspicious case arm and write the in_combat arm with no
byte-typed intermediate. setcc r8 ; movzx r16,r8 occurs zero times in all
9352 January functions; the new shape emits January's
xor ebx,ebx / test cl,cl / setne bl / add ebx,2 and matches January's
relocation layout exactly (relocation_infos_equal was False, now True).
Still non-exact: 23 bytes, all register choices.

CL /Zs /W3 unchanged at 43 diagnostics with identical codes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 c5f84ee42a Adopt the PDB's types and linkage for animation_set_state (byte-free)
Zero owners changed: the emitted object is byte-identical, verified by
function body hash and by a whole-object comparison across all 415
owners and 447 sections.  Census stays 186/3/0, /Zs /W3 stays at 43
warnings.  This is fidelity only.

DIA2Dump on the June-2011 HCEX.pdb reports, for unit_animation_set_state:
`Type: short, interpolation_frame_count` and `static function: true`.
Both are adopted:

  - interpolation_frame_count becomes short, code_00198230 goes back to
    returning short with `short result = 6;`, and the now-redundant
    (short) cast at object_start_interpolation is dropped.  This reverts
    the "code_00198230 returns long" reconstruction landed in f38ea306,
    which was only ever justified by removing one `movsx eax,ax`; the
    short spelling removes it too and matches the PDB, so the long
    spelling has no remaining evidence behind it.
  - unit_animation_set_state becomes static.  Nothing outside units.c
    references it - it appears in no header and in no other translation
    unit - so this cannot break a link.

Both changes were measured byte-identical individually and together
before landing, by SHA1 of the reloc-zeroed function bodies rather than
by equal counters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 0ae5430283 Home animation_set_state's result via goto, as January does
Replaces the previous commit's `result = code_001981f0(...)` spelling
with the one January actually used.  All SIX of the function's target
idioms now match: `xor dl,dl`, `mov dl,1`, `test dl,dl`, the homed
`mov byte ptr [ebp-2],1`, its `mov al,[ebp-2]` reload at the return, and
the `xor al,al` early epilogue.  The head 0x83-0x99 is now
instruction-for-instruction identical to January including register
choices.

    -           result = code_001981f0(new_state);
    -           if (!result) { return result; }
    +           if (!code_001981f0(new_state))
    +           {
    +                   result = FALSE;
    +
    +                   goto done;
    +           }
    ...
    +done:
        return result;

`goto <label>;` is house style here - eleven other files in source/ use
it.  Behaviour is unchanged: the only path to the label sets result FALSE
first, so `return result` returns FALSE exactly as `return FALSE` did.

Why it works where every earlier attempt failed: VC7 constant-folds a
definition that is consumed by an immediately following `return`, which
is why `result = FALSE; return result;` has always been byte-identical to
base.  A `goto` to a label in front of the common return survives
allocation, so `result` genuinely has two reaching definitions and is
homed; VC7 then tail-duplicates that exit into `xor al,al`, reproducing
January's 0x2de epilogue.

    landed before  raw 232/232  reg+slot 63/34 blocks  51 -> no result home
    landed now     raw 210/194  reg+slot 68/31 blocks       result homed

reg+slot reads three bytes worse, which is normalisation noise; raw diff
bytes, block counts and the idiom set all improve, and the structural
match is what counts.

The size now reads 1392 against 1408.  That is honest rather than a
regression: real code is 1195 versus January's 1203, and the previous
1408 was a 16-byte COMDAT alignment artifact.  A separate drift analysis
confirms there is no block where the target has code we lack - 366
instructions against 365 - so the whole remainder is one allocator
decision: January homes a seventh carrier, the inner short
animation_index, sharing [ebp-0xc] with interpolation_frame_count, giving
sub esp,0x1c where we keep it in di and get 0x18.

Census unchanged at 186/3/0; exactly one owner changed; /Zs /W3 identical
at 43 warnings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 7492e29c84 Reproduce animation_set_state's xor dl,dl; raw 387 -> 232
_unit_animation_set_state stays 1408/84 and nonexact, but the residual
drops sharply: raw diff bytes 387 -> 232, reg+slot 86 -> 63, LCS blocks
91 -> 56.  It now emits January's `xor dl,dl` as well as `mov dl,1` and
`test dl,dl`, so all three of changed_state's idioms match.  Census
unchanged at 186/3/0; exactly one owner changed; /Zs /W3 identical at
43 warnings.

Two edits, each worse alone and only useful together (387 -> 413 and
387 -> 315 respectively, versus 232 combined):

  1. changed_state and result are declared without initialisers and
     assigned as statements immediately after old_state_is_none.
     Nothing reads either variable in between, so this is
     behaviour-neutral.  On its own it merely relocates the home; with
     edit 2 it is what lets changed_state's FALSE definition live in DL
     instead of memory, which removes the store, the jump over the
     landing pad, and the `mov dl,[ebp-2]` reload - nine bytes that
     January spends two on.

  2. the code_001981f0 failure assigns through result:
     `result = code_001981f0(new_state); if (!result) { return result; }`
     rather than `if (!code_001981f0(new_state)) { return FALSE; }`.
     code_001981f0 returns exactly TRUE or FALSE, so returning result is
     identical to returning FALSE and the continuing path still has
     result == TRUE.  Giving result a second reaching definition is what
     stops VC7 folding it to a constant.

`result` is still not homed at [ebp-2] as January homes it, and the
remaining 63 reg+slot bytes are dominated by the ebx/edi web assignment:
January puts weapon_class_animation_index in ebx and keeps unit_seat
live in edi, homing animation_index at [ebp-0xc] shared with
interpolation_frame_count; we keep animation_index in ebx and reload
unit_seat at every use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 92428f2991 Put animation_set_state's changed_state in DL, as January does
_unit_animation_set_state stays 1408/84 and nonexact, but the genuine
structural residual drops sharply: raw diff bytes 399 -> 387 and
reg+slot 117 -> 86 over 45 -> 41 blocks.  It now reproduces January's
`mov dl,1` and `test dl,dl`, i.e. changed_state is finally enregistered
in a byte register rather than living in memory as a 16-bit local.
Census unchanged at 186/3/0; exactly one owner changed; the /Zs /W3
profile is identical at 43 warnings.

Three coupled edits; edits 2 and 3 are each inert or worse alone and
only work together:

  1. code_00198230 returns long instead of short, and its own result is
     long.  We were emitting a `movsx eax,ax` at 0x34e that January does
     not have.  _code_00198230 itself stays byte-EXACT.
  2. changed_state becomes boolean rather than short.  A short can never
     occupy a byte register, so this is a precondition for DL.
  3. The chosen_animation_index temporary goes away; the
     animation_choose_random_permutation_internal call is passed straight
     into code_0019b0b0.

Edit 3 is the load-bearing one and it identifies a real reconstruction
defect.  The first block enregisters exactly N values and changed_state
sits on the boundary: with chosen_animation_index present in any spelling
- nested block or if-block top, initialised or assigned, short or long -
changed_state is evicted to memory and the object shrinks to 1392.
Removing any other local in that block is byte-identical.  So January's
source almost certainly has no such temporary.

Also settled, against an earlier ledger: the changed_state live range
crosses exactly two calls, both to _code_00198230's sibling
_code_00198190, which is byte-identical in both builds and clobbers only
eax and ecx.  No callee's register footprint is the lever here.

_code_00198230, _code_00198190, _code_001981f0 and _code_0019b0b0 all
remain EXACT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 cae99e0ad5 Close unit_died exactly; unit_update to 6 differing bytes
units.obj code owners 185/4/0 -> 186/3/0 (data 129/0/0 unchanged).
Acceptance gate against 0b11e59e: exactly two owners changed, both
intended; the other 218 byte-identical. /Zs /W3 warning profile is
unchanged at 43 warnings, same kinds.

_unit_died  624/28 EXACT, body sha1 19cad1b19297.  Split the single
if (!feigned) / else into two if (!feigned) blocks, leaving only
feign_death_timer = 0 in the arm that carries the else.  Semantics are
identical - feigned is a parameter and nothing between the two guards
touches it - but the extra block gives the two arms divergent exits,
and that is what makes VC7 sink the feign arm past the join instead of
placing it immediately after the not-feigned arm.

The mechanism recorded in earlier ledgers was wrong and cost two lanes.
VC7 does not require the sibling arm to end in a multi-exit construct.
The real trigger is that the candidate arm's exit must not land where
the sibling's fall-through lands.  Everything previously catalogued as a
separate defect in this function - the duplicated store in both SET_FLAG
arms, the two near jmps to the join, the or esi,-1 materialised inside
the join, and the two near je at the seat if - fell out of the placement
automatically.

_unit_update  5984/262 -> 5936/259, and 190 -> 6 differing bytes over 3
blocks.  Two changes:

  - The whole current_weapon_index block moves inside the preceding
    if (!TEST_FLAG(unit_definition->unit.flags, _unit_has_no_aiming_bit))
    guard.  This IS a behaviour change: a unit with the no-aiming flag no
    longer runs the weapon-control flag computation.  It is landed because
    the emitted evidence is decisive - it supplies the third dominating
    branch that makes VC7 call unit_animation_busy instead of inlining it,
    it emits no extra bytes because the aiming guard's false exit and the
    current_weapon_index == NONE exit coincide, and it takes the function
    from 190 to 6 differing bytes.  A unit that cannot aim not driving a
    weapon is also the reading the structure suggests.
  - Two cosmetic parenthesis pairs dropped at the aiming-limit
    assignments.  (a*b)/c and a*b/c parse identically in C89 but VC7
    keeps the parenthesisation in its floating-point IL, and the
    unparenthesised form is what reproduces January's x87/integer
    interleave.

_unit_is_busy, _unit_animation_busy and _unit_try_and_exit_seat all stay
EXACT.  The remaining 6 bytes are a camouflage-regrowth rematerialisation
split: January reloads unit_index on both successors of the
cause_for_camo_regrowth test, we hoist one copy above the branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
Jonas VolmanandClaude Opus 5 76f5b4247d Close euler_aiming_update and preprocess_node_orientations exactly
units.obj code owners 183/6/0 -> 185/4/0 (data 129/0/0 unchanged).
Acceptance gate against c9b31750: exactly four owners changed, all
intended; the other 216 are byte-identical.

_unit_euler_aiming_update  1744/78 EXACT.  Four coupled source facts,
none of which works alone:
  - aiming_yaw_velocity + aiming_pitch_velocity become one
    real_euler_angles2d aiming_angular_velocity;
  - end_aiming_angles and end_aiming_vector move to function scope;
  - further_vector moves into the innermost block;
  - dot_product3d's arguments swap, matching the adjacent
    cross_product3d call's operand order.
The local's type shape is a first-class input to VC7's frame
allocator, so the earlier "scope is exhaustively closed" result was
only closed at fixed type shape.

_unit_preprocess_node_orientations  1920/87 EXACT.  The acceleration
loop is rewritten over a zero-based counter with an affine
FRONT + k subscript, which is what makes VC7 strength-reduce it to a
byte-offset induction variable; the derived index must stay unnamed.
relative_aiming_angles moves into the aiming-capable block and the
looking guard splits into nested ifs with looking_bounds in the outer
one.  This depends on _unit_seat_animation_push_impact minus
_unit_seat_animation_acceleration_front_back being a positive
compile-time constant (3), so the do/while to for conversion cannot
skip an iteration.

_unit_update  61 -> 37 differing blocks.  Two of the three changes are
real decompilation defects: the looking branch was clipping and
updating unit->unit.aiming_vector where January uses looking_vector
(offset 0x210, not 0x1ec).  The third assigns light_change_state only
in the true arm, which also corrects a frame-slot swap.  The residual
48 bytes are one per-call-site inline decision.

_unit_animation_set_state  92 -> 91 blocks.  Hoists the
interpolation_frame_count/changed_state pair above the looking block
and widens the two inner animation_index locals to long, which
reproduces two target-only movsx r32, word ptr [mem] loads.  Adds two
C4244 narrowings at the short parameter, consistent with January
having declared them long.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 21:17:04 -07:00
bnunu fd9c8eb095 Publish Units, HS, and campaign tooling progress
Carry forward three Units reconstruction advances, complete hs_library_external at 36/36 target-owned functions and 3824/3824 padded bytes, and admit it as Matching.

Also harden parked-function validation for assembly transcriptions and enable MSVC header dependency tracking. Full 568-object builds and all 205 tooling tests pass; the parked manifest has zero stale or invalid entries.
2026-08-28 08:17:11 -07:00
bnunu a2dde305b8 Match Vehicles 6290 and improve physics residuals 2026-08-28 01:48:03 -07:00
Jonas VolmanandClaude Fable 5 b1c484ae9b Improve unit_update_animation to two bytes from exact
Census unchanged at 183/6/0; this owner goes from 11 LCS blocks / ratio 0.964
to 4 blocks / 0.987, two bytes short of exact. Landed rather than stranded,
since a shape left out of the tree for "closing nothing" has twice turned out
to matter.

Three recoveries:

- The animation_update_result==2 switch orders its cases leap_start, dying,
  exiting_seat, opening/closing, entering_seat. The post-switch join block is
  placed immediately after the last case block in source order, so
  entering_seat must be last; that single constraint fixes both the leap
  block's position at 0x24c and the missing add esp,4 tail-merge at 0x3b8,
  because the leap block sitting between unit_close's cleanup and the join was
  what blocked the cross-jump. opening/closing contributes no block of its own
  and its position among the middle arms is free.
- The unit_get re-fetch after object_start_interpolation assigns a fresh
  block-scoped local rather than reusing unit, which keeps unit in edi and lets
  the two stores use eax as January does.
- desired_base_seat_index is declared inside the parent_object_index==NONE
  block, which is what turns cmp eax,-1 into January's cmp eax,ebx at 0x72.

The two-byte residual is a reproducible binary switch in the in_combat arm: any
shape with a byte local receiving the comparison result gives 1280/64 and four
blocks, and any shape without one gives 1312/64 and sixty-five. Seven further
shapes measured here -- a byte, short or pointer local holding the loaded value
rather than the comparison, an initialiser form, a ternary, and a local alias
for data -- all land in the 1312 bucket, confirming the boundary. January needs
the load materialised without a byte comparison local; no admissible shape found
so far does both.

Wave gates: /Zs exit 0 with no warnings; dry run proving exactly one CL edge;
one emission; owner gate pinned to a4ba37a9 shows exactly one changed owner of
220, the intended one; determinism replay identical across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:44:27 -07:00
Jonas VolmanandClaude Fable 5 a4ba37a969 Close throw_grenade_release exactly: units.obj reaches 183/6/0
Census 182/7/0 -> 183/6/0 code, data unchanged at 129/0/0. This closes the
object's most-worked residual, which had survived roughly 200 measured source
variants across five sessions.

The residual was two transpositions in one seven-instruction window: January
issues the FP op first in each FP/integer pair where we issued the push first.
The lever is the declared type of the scalar fed to the inlined
point_from_line3d expansion. Loading each grenade_origin component into a double
temp and narrowing with an explicit (real) cast at the call makes the dependent
x87 op issue one slot later, which drains the extra integer op into the multiply
shadow and reproduces January's pairing exactly.

The casts are load-bearing: without them the float constants promote to double
and fmul dword becomes fmul qword. Retyping all three components as separate
double temps, or as real temps, is much worse (43-44 blocks); a single reused
double temp is exact.

The mechanism came from a sibling investigation that found it on an 80-byte
miniature in hs.obj rather than on this function, then classified every
function in the build that differs only by a transposition. That scan named
this owner as a candidate.

Wave gates: /Zs exit 0 with no warnings; contained deletion with a dry run
proving exactly one CL edge; one emission; owner gate pinned to 6f18872a shows
exactly one changed owner of 220, the intended one; determinism replay identical
across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:41:19 -07:00
Jonas VolmanandClaude Fable 5 6f18872a72 Close three more units owners exactly: units.obj reaches 182/7/0
Census 179/10/0 -> 182/7/0 code, data unchanged at 129/0/0.

code_001a0cf0 (1568/43). Five switch arms each ended in the call plus return;
making them all break with one shared code_0019ea70 call after the switch lets
VC7 tail-duplicate the call and epilogue back into every arm, which reproduces
January's five ret sites. The right-direction arm additionally needs the
negation hoisted into a real temp so fchs issues one slot earlier. The handoff's
"return-block specialisation signal" for this owner was a false lead: the
21-vs-19 al-write gap was one two-colour register swap in the tail, with
alignment_vector in ecx and the scratch chain in eax, and all fifteen blocks
were downstream of it.

unit_set_or_test_seat_and_weapon_label (544/12). Four coupled edits: all three
loop counters are short in January, not just seat_index (the third homed slot is
the whole frame difference); showing_acceleration must be left uninitialised and
reached by falling past a goto, otherwise its initialiser sinks between a cmp
and its jne and forces the anim_2 ternary to be materialised instead of
branch-folded; weapon_label is tested for NULL, not for an empty string, which
was also a live null dereference since callers pass NULL; and the animation
field assignment order is seat, base_seat, weapon_index, weapon_type_index.

unit_cause_player_melee_damage (1600/57). The intermediate temp is double with
an explicit (real) cast at each use. The declared type of a floating-point
intermediate shifts VC7's x87/integer interleave without changing any emitted
instruction: a double temp makes the dependent op issue a slot earlier, which is
what drains the extra integer op into the fmul shadow. The casts are
load-bearing; without them the float constants promote and fmul dword becomes
fmul qword.

Wave gates: /Zs exit 0 with no warnings; contained deletion with a dry run
proving exactly one CL edge; one emission; owner gate pinned to 97fc1230 shows
exactly three changed owners of 220, all intended; determinism replay identical
across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:39:20 -07:00
Jonas Volman c405ccb448 Document exhaustive Vehicles frontier audit 2026-08-27 22:37:29 -07:00
Jonas VolmanandClaude Fable 5 97fc12301b Close unit_place exactly: units.obj reaches 179/10/0
Census 178/11/0 -> 179/10/0 code, data unchanged at 129/0/0.

The residual was two coupled levers, which is why every previous attempt --
roughly 400 measured variants, including all 120 permutations of the local
declarations -- came out worse:

1. Split the unit.flags read-modify-write into a named temp, so the value is
   held in a register across the intervening frame_index store. On its own this
   is not enough; VC7 sinks it back past that store and re-folds it to
   or dword ptr [mem], imm.
2. The object.flags store must come AFTER the unit.flags store. With the object
   store first, VC7 re-folds the unit.flags update every time.

A sweep of all 210 legal orderings of the seven statements finds exactly seven
that are exact; the landed one is object-flags load, clamp, damage SET_FLAG,
unit-flags load-or, frame store, unit-flags store, object-flags store.

Two secondary facts: exactly one of the two dword read-modify-writes gets a
register temp unless both are forced, and forcing the other simply swaps which
one folds; and the eax/edx role swap seen in near misses is a size-driven
allocator preference (or eax,imm32 is five bytes against six for edx) that
resolves once the ordering is right.

Wave gates: /Zs exit 0 with no warnings; contained deletion with a dry run
proving exactly one CL edge; one emission; owner gate pinned to 8cdede3e shows
exactly one changed owner of 220, the intended one; determinism replay identical
across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 22:33:42 -07:00
Jonas Volman 7582c5c0a8 Advance vehicles update residual and document full provenance 2026-08-27 21:01:12 -07:00
Jonas VolmanandClaude Fable 5 a49bc6a76d Land the two stranded units recoveries from the 0828b decode
Census unchanged at 178/11/0 code and 129/0/0 data -- neither owner can close
(both are dominated by allocation ties, which have no cost field to key on) --
but both reconstructions become materially more faithful, and both carry
behaviour corrections that had been sitting unlanded since 2026-08-28b because
their package closed no owner.

unit_update_animation: nest the world-matrix call,
matrix4x3_transform_vector(object_get_world_matrix(unit_index, &world_matrix),
&root_velocity, &root_velocity), using the real_matrix4x3 * that
object_get_world_matrix returns. This explains the long-standing add esp,8
versus add esp,0x40 at 0x337. LCS blocks 25 -> 11, ratio 0.908 -> 0.964.

unit_animation_set_state: the recovered January shape -- weapon_type as a bare
getter statement rather than a named local, short index types, the throw-grenade
case listed before the melee cases, seat cases grouped at the end, the early
return in the animation_index == NONE arm instead of if (result) wrappers, and
interpolation_frame_count = 6 on an aiming change (a real behaviour fix). This
reproduces the target epilogue structure exactly -- two ret instructions where
the old shape emitted one -- and moves the instruction count to 471 against the
target 473, from 464. Ratio 0.702 -> 0.746.

Wave gates: /Zs exit 0 with no warnings; contained deletion with a dry run
proving exactly one CL edge; one emission; owner gate pinned to ddb5c240 shows
exactly two changed owners of 220, both intended; determinism replay identical
across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 20:32:54 -07:00
Jonas VolmanandClaude Fable 5 ddb5c2402a Close unit_export_function_values exactly: units.obj reaches 178/11/0
Census 177/12/0 -> 178/11/0 code, data stays 129/0/0. This is the last owner
reachable by a known lever.

unit_export_function_values needs a _ReadWriteBarrier() between the two getter
calls and the pointer initialisations. Without it the preamble hoists one
pointer above the deferred cdecl cleanup and splits lea edi,[eax+0x198] into
mov+add, leaving four LCS blocks; 70 admissible shapes were measured inert
(a 64-way cross product of pointer spellings, count forms, statement orders and
C89 declaration styles, plus six structural forms). With it the owner is
byte-exact at 384/18.

The declaration pair follows the form already used elsewhere in this tree, in
source/game/players.c, source/objects/object_shadows.c and
source/rasterizer/xbox/rasterizer_xbox_hardware_geometry.c. Compiles clean with
no warnings; the intrinsic emits no code.

Landed on the project owner's explicit direction, who judged the exact owner
worth the construct.

Wave gates: /Zs exit 0 with no warnings; contained deletion with a dry run
proving exactly one CL edge; one emission; owner gate pinned to b97d95fe shows
exactly one changed owner of 220, the intended one; determinism replay
identical across all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 20:24:20 -07:00
Jonas VolmanandClaude Fable 5 859ebedaab Close two units owners exactly; reconstruct all four absent owners
Census 175/10/4 code + 128/0/1 data -> 177/12/0 code + 129/0/0 data.
Two code owners become byte-exact, one data owner closes, and units.obj no
longer has a single absent owner.

EXACT:
- code_00198fd0 (208/8). VC7 does return-block specialisation: it duplicates
  the epilogue per value-producing path and folds the value into al, and this
  is all-or-nothing per function and disabled by any second return statement.
  Rewriting the function to a single return -- switch cases assign and break,
  the body moves into default:, the parent and else branches assign instead of
  returning -- turns it on and all four target epilogues fall out at once.
  Confirmed against seven already-exact single-return neighbours.
- unit_animation_start_action (400/18), previously absent. The prior note that
  it needs two carrier statics is wrong: January emits no helper symbols, and
  inserting the statics or hand-duplicating them both measure worse. Three
  levers: a long action_index local for the switch and the melee test, a mixed
  join construct (post-switch test for the weapon class, a goto label in case 9
  for the weapon type) to place each join block where January places it, and
  animation_graph_index assigned late so it lands in eax.

Also reconstructed, faithful but not yet exact:
- unit_cause_player_melee_damage (1600/57), three bytes from exact -- one
  adjacent push/faddp transposition. Closes the data owner __real@3d0f5c29
  exactly, so every data owner is now closed. January references
  vehicle_accelerate as an external too, so the local prototype matches the
  target rather than introducing anything new.
- unit_place (320/12), three blocks; ordering the flag updates before the
  frame_index store reproduces the target schedule through the whole clamp.
- unit_export_function_values (384/18), four blocks, all preamble emission
  order. It also resolves a reference from object_types.obj that nothing
  currently defines.

Wave gates: /Zs clean; contained deletion with a dry run proving exactly one
CL edge; one emission; owner gate pinned to the base revision shows exactly
five changed owners of 220, all intended; determinism replay identical across
all 220 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 20:16:50 -07:00
Jonas Volman 7729651043 Recover vehicles object to 33 exact functions 2026-08-27 19:36:26 -07:00
Jonas VolmanandClaude Fable 5 a33530b742 Recover six units semantics; throw_grenade_release to exact size
Six source-level corrections to source/units/units.c, each verified against
the January split:

- unit_throw_grenade_release: scope the locals into the blocks that use them
  and read the grenade index back through unit->unit.grenade_object_index for
  object_get_origin. 768/26 -> 784/26, matching the target size and relocation
  count; divergence falls 232 -> 4 instructions.
- unit_update: the zoom scale is keyed on the weapon definition zoom_level_count
  (short at +0x3da), not on current_zoom_level, which was being normalised by
  itself.
- code_001a0cf0 and unit_update_animation: test object.damage_flags with
  _object_dead_bit (+0xb6 bit 2), not object.functions_active_flags with
  _unit_debug_function_active_bit (+0xd3).
- unit_set_or_test_seat_and_weapon_label: assign unit->unit.animation.seat_index
  alongside the other animation fields, and restore the anim_4 == NONE guard on
  showing_acceleration (anim_4 was computed and never read). 496/12 -> 528/12.

Wave gates: /Zs parse clean; contained deletion with a dry run proving exactly
one CL edge; one ordinary emission; census unchanged at 175/10/4 code and
128/0/1 data; byte-level owner gate shows exactly five changed owners, all
intended, out of 216; determinism replay identical across 216 owners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 19:04:31 -07:00
Jonas Volman 53d0a2ac9f Match four typed vehicles leaves exactly 2026-08-27 18:58:33 -07:00
Jonas Volman bfcde334ad Match vehicles quaternion setter exactly 2026-08-27 18:41:52 -07:00
Jonas Volman b5bac3e213 Match vehicles update callbacks exactly 2026-08-27 18:37:50 -07:00
Jonas Volman 21ab835f97 Recover typed biped BSP disconnect 2026-08-27 02:07:09 -07:00
Jonas Volman 2df4a22314 Recover typed biped melee stop 2026-08-27 01:56:48 -07:00
Jonas VolmanandClaude Fable 5 b34e67e622 Match Units enter seat base collapse
Naming the first animation-graph-index retrieval through the function's own
long local ends unit_definition's register residency at the load; VC7
collapses it as January does. units.obj 175/189 code, 128/129 data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-26 13:04:35 -07:00