Complete recorded animations update and debug renderer

This commit is contained in:
Jonas Volman committed 2026-08-27 21:42:43 -07:00
1 parent 7582c5c0a8
commit 8cdede3edc
2 files changed
+514 -6

No files matched your search

@@ -0,0 +1,103 @@
# `recorded_animations.obj` continuation wave (2026-08-27)
## Result
This wave advances `recorded_animations.obj` from 6/16 to 12/16
strict-exact functions. The object now has no unwritten function owners:
12 are strict exact and the remaining four are typed, target-sized residuals
with the target relocation counts.
The strict-exact owners account for 1,648/2,800 padded target bytes.
| Strict function | Padded bytes | Relocations | Normalized SHA-256 |
| --- | ---: | ---: | --- |
| `_code_000836c0` | 96 | 4 | `dfe89944f5a5133e3b05901bba5076f4bcda484539e5373342c7a97452e745b7` |
| `_recorded_animation_controlling_unit` | 96 | 4 | `f21618f605218178bd0239d32d5361e014e232c079b62c3efde6d64fecdcfef0` |
| `_recorded_animation_play` | 32 | 1 | `ef585acc1d2013cd265d49eaa84ecdaf199a4240338ab525e2e5415f55138fa2` |
| `_recorded_animation_play_and_delete` | 32 | 1 | `35ec6175e1878120e0ea7fdb6a81827fd940b345897631bc2401ac44c32740ea` |
| `_recorded_animation_play_and_hover` | 32 | 1 | `c11e67670227db5072be50fc141406af4628cdaabce20cdbd8b1377aed52d2a3` |
| `_recorded_animations_clear_debug_storage` | 64 | 7 | `1211ce37dc4823572de1d952fab7bec9bfd63c39ac60cad7e77b3c10d3603e2e` |
| `_recorded_animations_dispose` | 48 | 4 | `b9b64bb030325c73231a51ab101f24b1d8ce3ec37789c76d2686751f0cc07ed0` |
| `_recorded_animations_dispose_from_old_map` | 16 | 2 | `9dedd614496101c84c04e338b0fb2167843b37380b3f0e2a50007ce9c2bc7edc` |
| `_recorded_animations_initialize` | 128 | 14 | `62d7955bddb2ac2c835aad58824e6de31cd285e0c65afd49c7325dd4cf8ed262` |
| `_recorded_animations_initialize_for_new_map` | 80 | 9 | `974d8b0cddba4e676cb8649ac9b1efbfa2e238ef8e1fe20f223dc475581781e8` |
| `_recorded_animations_update` | 560 | 34 | `d0606972324bba256754b94bd47bef61e499de94bc830df2dcbb5cff364c5f05` |
| `_render_debug_recording` | 464 | 28 | `a888551deefc05cfa73b658369687feeb95c6c0fe75561a53f9122ef938ea885` |
The retained residual owners are:
| Residual function | Target/candidate padded bytes | Target/candidate relocations | Remaining difference |
| --- | ---: | ---: | --- |
| `_code_000839a0` | 640/640 | 38/38 | normalized instruction SHA |
| `_recorded_animation_get_time_left` | 144/144 | 8/8 | normalized instruction SHA |
| `_recorded_animation_kill` | 96/96 | 4/4 | normalized instruction SHA |
| `_recorded_animation_verify` | 272/272 | 14/14 | normalized instruction SHA |
## Recovered behavior
The update owner is now a complete typed implementation. It advances each
playback codec, validates the relative tick count and debug stream cursor,
applies unit control, detects corrupt early termination, restores unit state,
performs the optional destroy/hover completion actions, clears the debug slot,
and retires the datum. Its 192 decoded instructions match the target exactly.
The debug renderer is also complete and strict exact. It builds the original
tabbed debug report in the 0x2800-byte local buffer, resolves scenario object
and recording names through typed tag blocks, applies the 200/300 tab stops,
renders the string, and restores the tab-stop state. Its 150 decoded
instructions and all 28 relocation identities match the target.
This wave also closes initialization, new-map debug clearing, debug storage
clearing, and the controlling-unit query. `recorded_animation_verify`, the
time-left query, the kill operation, and the private playback worker are kept
as lawful semantic residuals rather than forced through compiler artifacts.
## Evidence and experiments
The January split COFF remained the byte, relocation, string, line-number,
field-width, and control-flow authority. Semantic recovery used the pinned
local donors under:
- `build/audit/refs/stian-halo/src/halo/cutscene/recorded_animations.c`;
- `build/audit/refs/halocea/src/blam/cutscene/recorded_animations_update.c`;
- the recorded-animation playback and lookup donors documented by the prior
playback wave.
The update target's relocation order exposed the two omitted stream checks and
the dynamic `animation %s appears corrupt` assertion. Restoring those ordinary
source blocks first produced an equal 560-byte body; matching the natural
positive-valid-object branch, branch-local lifetimes, direct flag fields, and
the original stringized expression then made the owner strict exact.
The renderer donor produced an instruction-for-instruction match on its first
typed implementation apart from the two independent zero assignments. Reversing
their source order recovered the target schedule. Naming the two standalone
globals directly recovered the target relocation identities without raw
addresses or synthetic aliases.
For `recorded_animation_verify`, a typed 0x40-byte `unit_control_data` aggregate
recovered the target size and relocation count. Its remaining difference is
the compiler's choice of two interchangeable local stack slots. Natural loop,
break, nested-if, shared-result, and early-return forms were evaluated for the
two small query/kill residuals; the retained forms preserve target sizes and
relocation counts without steering constructs.
Claude's active work was treated read-only throughout. No Claude worktree,
index, object, build output, or session state was modified.
## Policy and validation
The retained source uses typed animation-thread, codec, debug-entry, scenario,
object, unit-control, tag-block, and data-iterator access. It contains no
assembly, volatile/register steering, pragmas, intrinsics, barriers, raw
addresses, byte-offset access, pointer reconstruction, punning, undefined
behavior, synthetic anchors, byte payloads, or object patching. Declarations
remain C89-compatible and every function returns explicitly.
The frozen whole-object scratch gate reports `12 exact / 4 residual / 0
unwritten`. One selected-object production Ninja build completed successfully,
and direct post-build comparison reproduced `12 / 4 / 0`. `git diff --check`
passed; Git emitted only its line-ending notice. The production compiler also
reported one pointer-level warning at the `game_state_data_new` assignment,
caused by this translation unit's missing visible allocator prototype; the
32-bit emitted owner nevertheless matches the target strictly.
+411 -6
View File
@@ -92,8 +92,10 @@ symbols in this file:
#include "game/players.h"
#include "memory/data.h"
#include "objects/objects.h"
#include "render/render_debug.h"
#include "scenario/scenario.h"
#include "scenario/scenario_definitions.h"
#include "text/draw_string.h"
#include "units/units.h"
/* ---------- constants */
@@ -101,12 +103,31 @@ symbols in this file:
enum
{
_recording_thread_finished_bit = 0,
_recording_thread_killed_bit = 1,
_recording_thread_unit_was_controlled_bit = 2,
_recording_thread_delete_unit_on_complete_bit = 3,
_recording_thread_hover_vehicle_on_complete_bit = 4,
RECORDED_ANIMATION_VERSION = 4,
};
/* ---------- structures */
struct unit_control_data
{
char animation_state;
char aiming_speed;
word control_flags;
short weapon_index;
short grenade_index;
short zoom_level;
short pad;
real_vector3d throttle;
real primary_trigger;
real_vector3d facing_vector;
real_vector3d aiming_vector;
real_vector3d looking_vector;
};
struct animation_thread
{
short identifier;
@@ -116,7 +137,7 @@ struct animation_thread
word flags;
long relative_ticks;
byte *event_stream;
byte controller[0x40];
struct unit_control_data controller;
byte animation_state[0xC];
short version;
short pad62;
@@ -126,8 +147,8 @@ struct animation_thread_debug
{
boolean active;
byte pad1[3];
byte *event_stream;
long event_stream_size;
byte *event_stream_start;
long stream_length;
short animation_index;
short padE;
};
@@ -139,6 +160,11 @@ struct animation_playback
void *controller,
byte **event_stream,
byte unit_control_data_version);
boolean (*apply_event_stream)(
void *animation_state,
struct unit_control_data *controller,
long *relative_ticks,
byte **event_stream);
};
struct recorded_animation_playback_data
@@ -168,6 +194,15 @@ static boolean code_000839a0(
short animation_index,
word extra_flags);
void unit_control(
long unit_index,
struct unit_control_data const *control_data);
void hs_object_destroy(
long object_index);
void vehicle_hover(
long vehicle_index,
boolean hover);
extern boolean recorded_animation_controlling_unit(
long unit_index);
@@ -176,9 +211,39 @@ extern boolean recorded_animation_controlling_unit(
extern struct animation_thread_debug *animation_threads_debug;
extern struct recorded_animations_globals_prefix bss_00435ca4;
extern struct recorded_animation_playback_data data_002dd160;
extern boolean debug_recording;
extern short debug_recording_newlines;
/* ---------- public code */
#define animation_threads bss_00435ca4.animation_threads
void recorded_animations_initialize(
void)
{
animation_threads = game_state_data_new(
"recorded animations",
64,
sizeof(struct animation_thread));
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
108,
animation_threads);
animation_threads_debug = match_malloc(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
111,
0x400);
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
112,
animation_threads_debug);
return;
}
#undef animation_threads
void recorded_animations_dispose(
void)
{
@@ -202,6 +267,346 @@ void recorded_animations_dispose_from_old_map(
return;
}
void recorded_animations_clear_debug_storage(
void)
{
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
0x99,
animation_threads_debug);
memset(animation_threads_debug, 0, 0x400);
return;
}
void recorded_animations_update(
void)
{
struct data_iterator iterator;
struct animation_thread *thread;
data_iterator_new(&iterator, bss_00435ca4.animation_threads);
thread = data_iterator_next(&iterator);
while (thread)
{
if (object_try_and_get_and_verify_type(thread->unit_index, _object_mask_unit))
{
if (!TEST_FLAG(thread->flags, _recording_thread_finished_bit))
{
struct animation_thread_debug *thread_debug;
long *relative_ticks;
boolean finished;
thread->ticks_left--;
relative_ticks = &thread->relative_ticks;
finished = !playback_codec[thread->version]->apply_event_stream(
thread->animation_state,
&thread->controller,
relative_ticks,
&thread->event_stream);
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
347,
thread->relative_ticks>=0);
thread_debug = animation_threads_debug_get(iterator.datum_index);
if (thread_debug->active)
{
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
354,
thread->event_stream-thread_debug->event_stream_start<thread_debug->stream_length||(thread->event_stream-thread_debug->event_stream_start==thread_debug->stream_length&&finished));
}
thread->relative_ticks++;
unit_control(thread->unit_index, &thread->controller);
SET_FLAG(
thread->flags,
_recording_thread_finished_bit,
finished);
}
else
{
struct animation_thread_debug *thread_debug;
thread_debug = animation_threads_debug_get(iterator.datum_index);
if (thread_debug->active &&
!TEST_FLAG(thread->flags, _recording_thread_killed_bit) &&
thread->ticks_left != 0)
{
match_vassert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
373,
FALSE,
csprintf(
temporary,
"animation %s appears corrupt",
TAG_BLOCK_GET_ELEMENT(
&global_scenario_get()->recorded_animations,
thread_debug->animation_index,
struct recorded_animation_definition)->name));
}
thread_debug->active = FALSE;
unit_set_controllable(
thread->unit_index,
TEST_FLAG(thread->flags, _recording_thread_unit_was_controlled_bit));
unit_set_possessed(thread->unit_index, FALSE);
unit_set_actively_controlled(thread->unit_index, FALSE);
object_set_automatic_deactivation(thread->unit_index, TRUE);
if (TEST_FLAG(
thread->flags,
_recording_thread_delete_unit_on_complete_bit))
{
hs_object_destroy(thread->unit_index);
}
if (TEST_FLAG(
thread->flags,
_recording_thread_hover_vehicle_on_complete_bit))
{
vehicle_hover(thread->unit_index, TRUE);
}
datum_delete(bss_00435ca4.animation_threads, iterator.datum_index);
}
}
else
{
datum_delete(bss_00435ca4.animation_threads, iterator.datum_index);
}
thread = data_iterator_next(&iterator);
}
return;
}
void recorded_animations_initialize_for_new_map(
void)
{
data_make_valid(bss_00435ca4.animation_threads);
recorded_animations_clear_debug_storage();
return;
}
void render_debug_recording(
void)
{
char string[0x2800];
struct data_iterator iterator;
struct scenario_object_name *object_name;
short tab_stops[2];
short newline_index;
long string_length;
struct animation_thread *thread;
struct object_datum *object;
struct animation_thread_debug *thread_debug;
char const *recording_name;
if (debug_recording)
{
string_length = 0;
newline_index = 0;
tab_stops[0] = 200;
tab_stops[1] = 300;
while (newline_index < debug_recording_newlines)
{
string_length += sprintf(string + (short)string_length, "|n");
newline_index++;
}
string_length += sprintf(
string + (short)string_length,
"recording name|tticks left|tobject name");
data_iterator_new(&iterator, bss_00435ca4.animation_threads);
thread = data_iterator_next(&iterator);
while (thread)
{
object = object_try_and_get_and_verify_type(
thread->unit_index,
_object_mask_all);
thread_debug = animation_threads_debug_get(iterator.datum_index);
if (!TEST_FLAG(thread->flags, _recording_thread_finished_bit) &&
object &&
object->object.name_index != NONE)
{
object_name = TAG_BLOCK_GET_ELEMENT(
&global_scenario_get()->object_names,
object->object.name_index,
struct scenario_object_name);
recording_name = "<unknown>";
if (thread_debug->active)
{
recording_name = TAG_BLOCK_GET_ELEMENT(
&global_scenario_get()->recorded_animations,
thread_debug->animation_index,
struct recorded_animation_definition)->name;
}
string_length += sprintf(
string + (short)string_length,
"|n%s|t",
recording_name);
string_length += sprintf(
string + (short)string_length,
"%d|t",
thread->ticks_left);
string_length += sprintf(
string + (short)string_length,
"%s",
object_name->name);
}
thread = data_iterator_next(&iterator);
}
string[0x400] = 0;
draw_string_set_tab_stops(tab_stops, NUMBEROF(tab_stops));
render_debug_string(TRUE, string);
draw_string_set_tab_stops(tab_stops, 0);
}
return;
}
void recorded_animation_verify(
struct recorded_animation_definition const *animation)
{
struct unit_control_data controller;
byte animation_state[0xC];
byte *stream;
byte *playback_stream;
long size;
long relative_ticks = 0;
long ticks_left;
boolean finished;
stream = animation->event_stream.address;
playback_stream = stream;
size = animation->event_stream.size;
ticks_left = (word)animation->length_in_ticks;
playback_codec[animation->version-1]->initialize_event_stream(
animation_state,
&controller,
&playback_stream,
animation->unit_control_data_version);
do
{
ticks_left--;
finished = !playback_codec[animation->version-1]->apply_event_stream(
animation_state,
&controller,
&relative_ticks,
&playback_stream);
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
428,
ticks_left>=0);
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
429,
relative_ticks>=0);
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
430,
playback_stream-stream<size||(playback_stream-stream==size&&finished));
relative_ticks++;
}
while (!finished);
return;
}
boolean recorded_animation_controlling_unit(
long unit_index)
{
boolean result = FALSE;
struct data_iterator iterator;
struct animation_thread *thread;
data_iterator_new(&iterator, bss_00435ca4.animation_threads);
thread = data_iterator_next(&iterator);
if (thread)
{
while (thread->unit_index != unit_index ||
TEST_FLAG(thread->flags, _recording_thread_finished_bit))
{
thread = data_iterator_next(&iterator);
if (!thread)
break;
}
if (thread)
result = TRUE;
}
return result;
}
void recorded_animation_kill(
long unit_index)
{
struct data_iterator iterator;
struct animation_thread *thread;
data_iterator_new(&iterator, bss_00435ca4.animation_threads);
thread = data_iterator_next(&iterator);
while (thread)
{
if (thread->unit_index == unit_index)
break;
thread = data_iterator_next(&iterator);
}
if (thread)
{
thread->flags |=
(1 << _recording_thread_finished_bit) |
(1 << _recording_thread_killed_bit);
}
return;
}
long recorded_animation_get_time_left(
long unit_index)
{
long result = 0;
struct data_iterator iterator;
struct animation_thread *thread;
data_iterator_new(&iterator, bss_00435ca4.animation_threads);
thread = data_iterator_next(&iterator);
if (thread)
{
while (thread->unit_index != unit_index)
{
thread = data_iterator_next(&iterator);
if (!thread)
break;
}
}
match_assert(
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
312,
!thread||thread->unit_index==unit_index);
if (thread)
result = thread->ticks_left;
return result;
}
boolean recorded_animation_play(
long unit_index,
short animation_index)
@@ -299,8 +704,8 @@ static boolean code_000839a0(
debug = animation_threads_debug_get(thread_index);
debug->active = TRUE;
debug->event_stream = thread->event_stream;
debug->event_stream_size = animation->event_stream.size;
debug->event_stream_start = thread->event_stream;
debug->stream_length = animation->event_stream.size;
debug->animation_index = animation_index;
thread->version = animation->version - 1;
@@ -313,7 +718,7 @@ static boolean code_000839a0(
playback = playback_codec[thread->version];
playback->initialize_event_stream(
thread->animation_state,
thread->controller,
&thread->controller,
&thread->event_stream,
animation->unit_control_data_version);