mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-23 06:00:25 +02:00
Recover recorded animation playback wrappers
This commit is contained in:
1 parent
859ebedaab
commit
b97d95fe79
6 files changed
+375
-16
No files matched your search
@@ -0,0 +1,87 @@
|
||||
# `recorded_animations.obj` playback worker and wrapper recovery (2026-08-27)
|
||||
|
||||
## Result
|
||||
|
||||
This wave advances `recorded_animations.obj` from 2/16 to 6/16 strict-exact
|
||||
functions. It adds the private thread lookup and all three public playback
|
||||
wrappers while preserving both accepted disposal functions.
|
||||
|
||||
| Strict function | Padded bytes | Relocations | Normalized SHA-256 |
|
||||
| --- | ---: | ---: | --- |
|
||||
| `_recorded_animations_dispose` | 48 | 4 | `b9b64bb030325c73231a51ab101f24b1d8ce3ec37789c76d2686751f0cc07ed0` |
|
||||
| `_recorded_animations_dispose_from_old_map` | 16 | 2 | `9dedd614496101c84c04e338b0fb2167843b37380b3f0e2a50007ce9c2bc7edc` |
|
||||
| `_code_000836c0` | 96 | 4 | `dfe89944f5a5133e3b05901bba5076f4bcda484539e5373342c7a97452e745b7` |
|
||||
| `_recorded_animation_play` | 32 | 1 | `ef585acc1d2013cd265d49eaa84ecdaf199a4240338ab525e2e5415f55138fa2` |
|
||||
| `_recorded_animation_play_and_delete` | 32 | 1 | `35ec6175e1878120e0ea7fdb6a81827fd940b345897631bc2401ac44c32740ea` |
|
||||
| `_recorded_animation_play_and_hover` | 32 | 1 | `c11e67670227db5072be50fc141406af4628cdaabce20cdbd8b1377aed52d2a3` |
|
||||
|
||||
The four new owners contribute 151 meaningful bytes: 85 bytes for the lookup
|
||||
and 22 bytes for each wrapper. Including the two disposal owners, the object
|
||||
now has 200/2,684 meaningful code bytes exact.
|
||||
|
||||
The recovered `_code_000839a0` playback worker is a real typed implementation,
|
||||
not a wrapper-enabling stub. It is 640/640 padded bytes, has the target's 38
|
||||
relocations and the same relocation destinations in the same semantic order,
|
||||
and emits the exact target assertion expression and error strings. Its
|
||||
normalized instruction bytes remain residual because the compiler assigns the
|
||||
long-lived unit and animation-index values to the opposite ESI/EDI roles and
|
||||
schedules several typed field loads differently. That residual is retained as
|
||||
the lawful foundation for the remaining object.
|
||||
|
||||
## Evidence and history audit
|
||||
|
||||
Before editing production, the complete reachable source history for
|
||||
`source/cutscene/recorded_animations.c` was swept through the scratch compiler.
|
||||
There were three unique blobs; the cumulative source was already best at 2/16.
|
||||
The historical `jonas/recorded-animation-wrappers-wave-20260820` tree contained
|
||||
only the symbol skeleton, not hidden implementations.
|
||||
|
||||
The prior accepted ledgers were read in full:
|
||||
|
||||
- `recorded_animations_obj_jonas_dispose_old_map_20260821.md`;
|
||||
- `recorded_animations_obj_jonas_dispose_first_shot_20260826.md`.
|
||||
|
||||
Their private-EAX warning was decisive: compiling the public donor wrappers as
|
||||
ordinary external forwards is not target-authentic. The worker and lookup had
|
||||
to exist in the same translation unit so VC7 could derive the private register
|
||||
ABI naturally.
|
||||
|
||||
Semantic source came from the pinned local donor mirrors:
|
||||
|
||||
- `build/audit/refs/halocea/src/blam/cutscene/recorded_animation_play_internal.c`;
|
||||
- `build/audit/refs/halocea/src/blam/memory/get_controlling_thread.c`;
|
||||
- the three HCEA playback wrapper sources under
|
||||
`build/audit/refs/halocea/src/blam/cutscene/`;
|
||||
- `build/audit/refs/stian-halo/src/halo/cutscene/recorded_animations.c`.
|
||||
|
||||
January split COFF and relocation-annotated disassembly remained the authority
|
||||
for control-flow order, field widths, private ABI, exact strings, calls,
|
||||
relocations, and padding. In particular, the January helper's loop is a
|
||||
`while (thread)` scan with an equality break. Expressing that ordinary source
|
||||
topology made `_code_000836c0` strict exact and simultaneously gave all three
|
||||
wrappers their exact EAX-plus-stack call shape.
|
||||
|
||||
Claude's active worktrees were read only. No file, index, object, or build
|
||||
artifact in those worktrees was modified.
|
||||
|
||||
## Policy and validation boundary
|
||||
|
||||
The retained implementation uses typed structures for the animation thread,
|
||||
debug entry, playback codec table, scenario block, and data-array iterator. It
|
||||
contains no assembly, volatile/register steering, pragma, intrinsic,
|
||||
attribute, barrier, raw address, raw byte-offset access, pointer/integer
|
||||
reconstruction, punning, undefined behavior, synthetic anchor, payload, or
|
||||
object-byte forcing. All declarations are C89-compatible and every function
|
||||
has an explicit return.
|
||||
|
||||
The scratch gate reports 6 exact, one residual, and nine unwritten functions.
|
||||
Direct production-object comparison proves all six accepted owners have equal
|
||||
padded bytes plus ordered relocation address/type/destination/addend identity.
|
||||
The final frozen source was rebuilt through one normal selected-object Ninja
|
||||
edge for the helper/CFG wave.
|
||||
|
||||
The campaign gate and branch sweep were also made concurrency-safe during this
|
||||
audit: alternate branch blobs now compile through `gate.py --source` from a
|
||||
PID-scoped scratch source. `branch_sweep.py` no longer overwrites or restores
|
||||
the production source, so it cannot collide with Claude or another active
|
||||
worker.
|
||||
@@ -0,0 +1,25 @@
|
||||
# `wind.obj` axis-countdown refinement (2026-08-27)
|
||||
|
||||
## Result
|
||||
|
||||
`wind.obj` remains 7/9 strict-exact functions, with `_code_0017f750` residual
|
||||
and `_code_0017fbd0` unwritten. The retained ordinary-C change replaces the
|
||||
three-axis index comparison with the target-backed two-role loop topology:
|
||||
`axis_index` selects the variance row while `axes_remaining` is the countdown.
|
||||
|
||||
The candidate remains 208 meaningful/padded bytes versus the target's 224 and
|
||||
retains all eight target relocation destinations. Its relocation-normalized
|
||||
differing-byte distance improves from 193 to 124. The target loop prologue,
|
||||
`mov ebx,3`, countdown `dec ebx`, and back edge now agree.
|
||||
|
||||
The remaining principal difference is the January compiler's full-width
|
||||
sign-bit clearing sequence around the floating sample key. The lawful source
|
||||
still canonicalizes `fabs` to the x87 `fabs` instruction. A split assignment
|
||||
was scratch-tested and produced the same x87 form. The historical exact
|
||||
experiment used inactive-union access and remains rejected; byte
|
||||
representation writes are outside campaign policy.
|
||||
|
||||
All current wind ledgers and reachable source-history blobs were audited before
|
||||
retention. The final gate reports seven exact, one residual, and one unwritten
|
||||
function, and the frozen source was rebuilt through one normal selected-object
|
||||
Ninja edge. No accepted sibling changed.
|
||||
@@ -87,14 +87,72 @@ symbols in this file:
|
||||
/* ---------- headers */
|
||||
|
||||
#include "cseries/cseries.h"
|
||||
#include "cseries/errors.h"
|
||||
#include "cutscene/recorded_animation_definitions.h"
|
||||
#include "game/players.h"
|
||||
#include "memory/data.h"
|
||||
#include "objects/objects.h"
|
||||
#include "scenario/scenario.h"
|
||||
#include "scenario/scenario_definitions.h"
|
||||
#include "units/units.h"
|
||||
|
||||
/* ---------- constants */
|
||||
|
||||
/* ---------- macros */
|
||||
enum
|
||||
{
|
||||
_recording_thread_finished_bit = 0,
|
||||
_recording_thread_unit_was_controlled_bit = 2,
|
||||
RECORDED_ANIMATION_VERSION = 4,
|
||||
};
|
||||
|
||||
/* ---------- structures */
|
||||
|
||||
struct animation_thread
|
||||
{
|
||||
short identifier;
|
||||
short pad2;
|
||||
long unit_index;
|
||||
word ticks_left;
|
||||
word flags;
|
||||
long relative_ticks;
|
||||
byte *event_stream;
|
||||
byte controller[0x40];
|
||||
byte animation_state[0xC];
|
||||
short version;
|
||||
short pad62;
|
||||
};
|
||||
|
||||
struct animation_thread_debug
|
||||
{
|
||||
boolean active;
|
||||
byte pad1[3];
|
||||
byte *event_stream;
|
||||
long event_stream_size;
|
||||
short animation_index;
|
||||
short padE;
|
||||
};
|
||||
|
||||
struct animation_playback
|
||||
{
|
||||
void (*initialize_event_stream)(
|
||||
void *animation_state,
|
||||
void *controller,
|
||||
byte **event_stream,
|
||||
byte unit_control_data_version);
|
||||
};
|
||||
|
||||
struct recorded_animation_playback_data
|
||||
{
|
||||
void *unused[4];
|
||||
struct animation_playback *playback_codec[RECORDED_ANIMATION_VERSION];
|
||||
};
|
||||
|
||||
/* ---------- macros */
|
||||
|
||||
#define animation_threads_debug_get(index) \
|
||||
(animation_threads_debug + ((index) & 0xFFFF))
|
||||
#define playback_codec (data_002dd160.playback_codec)
|
||||
|
||||
struct recorded_animations_globals_prefix
|
||||
{
|
||||
struct data_array *animation_threads;
|
||||
@@ -102,10 +160,22 @@ struct recorded_animations_globals_prefix
|
||||
|
||||
/* ---------- prototypes */
|
||||
|
||||
static struct animation_thread *code_000836c0(
|
||||
long unit_index,
|
||||
long *thread_index_reference);
|
||||
static boolean code_000839a0(
|
||||
long unit_index,
|
||||
short animation_index,
|
||||
word extra_flags);
|
||||
|
||||
extern boolean recorded_animation_controlling_unit(
|
||||
long unit_index);
|
||||
|
||||
/* ---------- globals */
|
||||
|
||||
extern void *animation_threads_debug;
|
||||
extern struct animation_thread_debug *animation_threads_debug;
|
||||
extern struct recorded_animations_globals_prefix bss_00435ca4;
|
||||
extern struct recorded_animation_playback_data data_002dd160;
|
||||
|
||||
/* ---------- public code */
|
||||
|
||||
@@ -132,4 +202,172 @@ void recorded_animations_dispose_from_old_map(
|
||||
return;
|
||||
}
|
||||
|
||||
boolean recorded_animation_play(
|
||||
long unit_index,
|
||||
short animation_index)
|
||||
{
|
||||
return code_000839a0(unit_index, animation_index, 0);
|
||||
}
|
||||
|
||||
boolean recorded_animation_play_and_delete(
|
||||
long unit_index,
|
||||
short animation_index)
|
||||
{
|
||||
return code_000839a0(unit_index, animation_index, 1 << 3);
|
||||
}
|
||||
|
||||
boolean recorded_animation_play_and_hover(
|
||||
long unit_index,
|
||||
short animation_index)
|
||||
{
|
||||
return code_000839a0(unit_index, animation_index, 1 << 4);
|
||||
}
|
||||
|
||||
/* ---------- private code */
|
||||
|
||||
static struct animation_thread *code_000836c0(
|
||||
long unit_index,
|
||||
long *thread_index_reference)
|
||||
{
|
||||
long thread_index = NONE;
|
||||
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)
|
||||
{
|
||||
thread_index = iterator.datum_index;
|
||||
break;
|
||||
}
|
||||
|
||||
thread = data_iterator_next(&iterator);
|
||||
}
|
||||
|
||||
if (thread_index_reference)
|
||||
*thread_index_reference = thread_index;
|
||||
|
||||
return thread;
|
||||
}
|
||||
|
||||
static boolean code_000839a0(
|
||||
long unit_index,
|
||||
short animation_index,
|
||||
word extra_flags)
|
||||
{
|
||||
boolean result = FALSE;
|
||||
long thread_index;
|
||||
struct animation_thread *thread;
|
||||
struct recorded_animation_definition const *animation;
|
||||
struct animation_thread_debug *debug;
|
||||
struct animation_playback *playback;
|
||||
|
||||
if (unit_index != NONE)
|
||||
{
|
||||
if (animation_index != NONE && animation_index < global_scenario_get()->recorded_animations.count)
|
||||
{
|
||||
object_get_and_verify_type(unit_index, _object_mask_unit);
|
||||
player_index_from_unit_index(unit_index);
|
||||
|
||||
thread = code_000836c0(unit_index, &thread_index);
|
||||
animation = TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->recorded_animations,
|
||||
animation_index,
|
||||
struct recorded_animation_definition);
|
||||
|
||||
if (!recorded_animation_controlling_unit(unit_index))
|
||||
{
|
||||
if (!thread)
|
||||
{
|
||||
thread_index = datum_new(bss_00435ca4.animation_threads);
|
||||
if (thread_index != NONE)
|
||||
thread = datum_get(bss_00435ca4.animation_threads, thread_index);
|
||||
}
|
||||
|
||||
if (thread)
|
||||
{
|
||||
thread->unit_index = unit_index;
|
||||
thread->relative_ticks = 0;
|
||||
thread->ticks_left = animation->length_in_ticks;
|
||||
thread->event_stream = tag_data_get_pointer(
|
||||
&animation->event_stream,
|
||||
0,
|
||||
animation->event_stream.size);
|
||||
|
||||
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->animation_index = animation_index;
|
||||
|
||||
thread->version = animation->version - 1;
|
||||
thread->flags &= ~(1 << _recording_thread_finished_bit);
|
||||
match_assert(
|
||||
"c:\\halo\\SOURCE\\cutscene\\recorded_animations.c",
|
||||
233,
|
||||
animation->version>0&&animation->version<=RECORDED_ANIMATION_VERSION&&playback_codec[animation->version-1]);
|
||||
|
||||
playback = playback_codec[thread->version];
|
||||
playback->initialize_event_stream(
|
||||
thread->animation_state,
|
||||
thread->controller,
|
||||
&thread->event_stream,
|
||||
animation->unit_control_data_version);
|
||||
|
||||
unit_set_actively_controlled(unit_index, TRUE);
|
||||
if (unit_controllable(unit_index))
|
||||
thread->flags |= 1 << _recording_thread_unit_was_controlled_bit;
|
||||
else
|
||||
thread->flags &= ~(1 << _recording_thread_unit_was_controlled_bit);
|
||||
|
||||
unit_set_controllable(unit_index, FALSE);
|
||||
unit_set_possessed(unit_index, TRUE);
|
||||
object_set_automatic_deactivation(unit_index, FALSE);
|
||||
thread->flags |= extra_flags;
|
||||
result = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
error(_error_silent, "Could not allocate space for a new animation");
|
||||
}
|
||||
}
|
||||
else if (thread)
|
||||
{
|
||||
char const *playing_name;
|
||||
|
||||
debug = animation_threads_debug_get(thread_index);
|
||||
playing_name = "<unknown>";
|
||||
if (debug->active)
|
||||
{
|
||||
playing_name = TAG_BLOCK_GET_ELEMENT(
|
||||
&global_scenario_get()->recorded_animations,
|
||||
debug->animation_index,
|
||||
struct recorded_animation_definition)->name;
|
||||
}
|
||||
|
||||
error(
|
||||
_error_silent,
|
||||
"trying to play %s while %s is playing",
|
||||
animation->name,
|
||||
playing_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
error(_error_silent, "can't play animation on unit");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error(_error_silent, "this animation doesn't exist");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error(_error_silent, "unit doesn't exist");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -137,9 +137,11 @@ static void code_0017f750(
|
||||
real const axis_scale[3] = { 0.1f, 0.2f, 0.07f };
|
||||
real magnitude = maximum_magnitude * (1.f / 3.f);
|
||||
long axis_index;
|
||||
long axes_remaining = 3;
|
||||
|
||||
*wind = *global_zero_vector3d;
|
||||
for (axis_index = 0; axis_index < 3; axis_index++)
|
||||
axis_index = 0;
|
||||
do
|
||||
{
|
||||
real sample_key = (real)fabs(
|
||||
((axis_scale[axis_index] * wind_globals.time * local_variation_rate)
|
||||
@@ -151,7 +153,9 @@ static void code_0017f750(
|
||||
wind->i += wind_globals.variance[axis_index][(short)sample_index].i;
|
||||
wind->j += wind_globals.variance[axis_index][(short)sample_index].j;
|
||||
wind->k += wind_globals.variance[axis_index][(short)sample_index].k;
|
||||
axis_index++;
|
||||
}
|
||||
while (--axes_remaining);
|
||||
|
||||
wind->i *= magnitude;
|
||||
wind->j *= magnitude;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""For each named unit, try every branch's version of its .c file and report
|
||||
any version that gates strictly better than the one on this branch.
|
||||
"""For each named unit, compile every branch's version of its .c file from
|
||||
scratch and report any version that gates strictly better than the current one.
|
||||
|
||||
Four objects were advanced this session by finding work already done in another
|
||||
lane; this automates the search.
|
||||
@@ -23,8 +23,11 @@ def unique_blobs(path):
|
||||
return blobs
|
||||
|
||||
|
||||
def gate(unit):
|
||||
r = subprocess.run(GATE + [unit], capture_output=True, text=True)
|
||||
def gate(unit, source=None):
|
||||
command = GATE + [unit]
|
||||
if source:
|
||||
command += ['--source', source]
|
||||
r = subprocess.run(command, capture_output=True, text=True)
|
||||
if 'COMPILE FAILED' in r.stdout:
|
||||
return None, 'compile-fail'
|
||||
m = re.search(r'== exact (\d+)\s+residual (\d+)\s+unwritten (\d+)', r.stdout)
|
||||
@@ -49,27 +52,27 @@ def sweep(unit):
|
||||
return
|
||||
cur = subprocess.run(['git', 'rev-parse', f'HEAD:{path}'], capture_output=True, text=True).stdout.strip()
|
||||
blobs = unique_blobs(path)
|
||||
orig = open(path, 'rb').read()
|
||||
base_score, base_desc = gate(unit)
|
||||
print(f'\n=== {unit} ({len(blobs)} unique blobs) current: {base_desc}', flush=True)
|
||||
best = (base_score, 'CURRENT', base_desc)
|
||||
scratch_source = f'scratch/_branch_sweep_{os.getpid()}.c'
|
||||
try:
|
||||
for blob, brs in blobs.items():
|
||||
if blob == cur:
|
||||
continue
|
||||
content = subprocess.run(['git', 'cat-file', 'blob', blob], capture_output=True).stdout
|
||||
open(path, 'wb').write(content)
|
||||
score, desc = gate(unit)
|
||||
open(scratch_source, 'wb').write(content)
|
||||
score, desc = gate(unit, scratch_source)
|
||||
tag = brs[0] + (f' (+{len(brs)-1})' if len(brs) > 1 else '')
|
||||
better = score is not None and base_score is not None and score > base_score
|
||||
print(f' {blob[:8]} {tag:52s} {desc}{" <== BETTER" if better else ""}', flush=True)
|
||||
if score is not None and (best[0] is None or score > best[0]):
|
||||
best = (score, tag, desc)
|
||||
finally:
|
||||
open(path, 'wb').write(orig)
|
||||
# no ninja here: gate.py compiles to its own scratch object, so the sweep
|
||||
# never reads build/base. Skipping it keeps concurrent sweeps safe (one
|
||||
# ninja per build dir at a time) - run a full ninja when the sweep ends.
|
||||
if os.path.exists(scratch_source):
|
||||
os.remove(scratch_source)
|
||||
# The sweep never edits the production source or reads build/base.
|
||||
# gate.py compiles each branch blob to its own scratch object.
|
||||
if best[1] != 'CURRENT':
|
||||
print(f' >>> BEST: {best[1]} {best[2]}', flush=True)
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import coff_compare as cc
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('unit')
|
||||
ap.add_argument('--source')
|
||||
ap.add_argument('--edits')
|
||||
ap.add_argument('--fn', action='append', default=[])
|
||||
ap.add_argument('--disas')
|
||||
@@ -16,7 +17,8 @@ ap.add_argument('--all', action='store_true')
|
||||
a = ap.parse_args()
|
||||
|
||||
unit = a.unit.replace('\\', '/')
|
||||
src = unit + '.c'
|
||||
unit_src = unit + '.c'
|
||||
src = a.source or unit_src
|
||||
bn = open('build.ninja').read()
|
||||
# the "build <obj>: cl <src>" line may be wrapped with a $ continuation, so match
|
||||
# on the object path alone rather than requiring the leading "build " on that line
|
||||
@@ -42,7 +44,7 @@ obj = f'scratch/_gate_{os.getpid()}.obj'
|
||||
if os.path.exists(obj):
|
||||
os.remove(obj)
|
||||
cl = r'C:\halo-worktrees\claude-finish-hs-20260816\xbox\bin\vc7\CL.Exe'
|
||||
incdir = os.path.dirname(src)
|
||||
incdir = os.path.dirname(unit_src)
|
||||
cmd = [cl, '/nologo', '/c'] + toks + ['/I' + incdir, '/Fo' + obj, scratch_src]
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if not os.path.exists(obj) or r.returncode != 0:
|
||||
|
||||
Reference in new issue
Block a user