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.
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>
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>
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>
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>
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>
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>
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>
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>
_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>
_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>
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>
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>
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.
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>
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>
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>
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>
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>
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>
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>
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>
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>