D173 M-174/M-175: root-caused the intro puppet's animation to a decomp-verbatim table entry

M-174: tried firing_animation_groups[group][sub] as the source of the
puppet's initial animation -- zero hits, ruled out (negative result logged
so it isn't re-guessed next time).

M-175: instrumented modelSetAnimation itself (the one choke point every
caller funnels through) with a __builtin_return_address probe filtered to
the player's own bodyModel, then resolved the logged addresses with
addr2line. Found the exact call site on the first try: bondviewSetCameraMode
(bondview2.c:936-942), inside CAMERAMODE_SWIRL's intro-fly-in setup. It
reads stage_intro_anim_table[] (bondview.c:355), a decomp ground-truth
table with zero #ifdef PORT in the whole call chain -- entry [0] is
{PTR_ANIM_extending_left_hand, 95.0, -1.0, 0.02}, and PTR_ANIM_extending_
left_hand is confirmed index 61 in animation_table_ptrs1[], an exact match
to the aidx=61/animframe~96 measured live on both Dam and Facility in M-172.

Conclusion: the selection logic is fully decomp-verbatim, not a port bug.
If the puppet genuinely floats while playing this held pose, the remaining
candidates are narrowed to exactly two: (a) this specific clip's keyframe
data is mis-decoded for this port at/around frame 95 (asset-pipeline bug,
D252-style raw-byte dump would confirm), or (b) this is real N64 behavior
and something else (camera framing, or just how it always looked) is the
actual source of the "floating" reports. Distinguishing the two needs N64
reference footage or a raw asset dump, not more source tracing -- this
thread is static-exhausted for this session. Captures archived at
scratch/d173/ (gitignored, local).
This commit is contained in:
James Dansereau committed 2026-09-17 15:48:59 -04:00
1 parent 1b48577367
commit ca01cb9ba9
3 files changed
+41 -1

No files matched your search

+7 -1
View File
@@ -10452,7 +10452,13 @@ crash class C2 (Facility `-level_34`, Runway `-level_35`): `import_texture_i8` A
**M-173 (2026-09-17, same session, autonomous, decisive): the control experiment — normal in-level guards do NOT have this gap. The bug is specific to the player-puppet's own draw/setup path, not a universal chr-rendering issue.** Added a second probe (`chr.c`, `chrTick`'s very first line, before ANY position-update logic runs) logging `prop->pos` for **every** ticked chr, gated on the same `d243mProbeActive()`. Relaunched Dam (`-level_33`, `GE_D243M=1`, no player input needed — background guards animate on their own) and diffed the new "before" line against the existing `onscreen` "after" line for the same chr at the same frame. **Checked four different background guards, different actiontypes/animations (`aidx` 0/40/106), different world positions: all four show a before/after gap of ~0** (a one-time ≤1.6-unit settle on the very first tick or two, then flush thereafter) — nothing remotely like the player puppet's ~328-unit gap. This **rules out** "every chr's root joint sits above its true position by design, normal chr rendering just hides it" (the leading theory after M-172) — **normal chr rendering is correctly grounded.** The bug is isolated to whatever is different about the player's own third-person puppet (`solo_char_load`'s setup, or the player-specific body-render call site, `bondview2.c`) versus a normal guard's `chrTick`/`chrRenderProp` path. Archived: `scratch/d173/m173_guard_compare_dam.log`.
**Correction while writing this up (checked before committing to the claim): `chrTick` — where `subcalcmatrices`/the render-position computation actually happens (`chr.c:2777`) — is NOT a separate player-vs-guard code path; it's called identically for the player's own `prop` (`bondviewMoveAllPlayers`, `bondview2.c:10493`, `ret = chrTick(prop)`) and every guard (`chrlvActionTick`'s caller). So the divergence M-173 found is not a distinct "player draw path skips a step" — it's that the *same* shared machinery, fed *different inputs* (specifically: `bondview2.c:10845-10880`'s player-only `firing_animation_groups[group][sub]` animation-selection table, which no guard's `chrTick` call ever reaches — guards select animations via their own AI-driven logic), produces different output.** **Next, concrete, now scoped to that one selection call:** compare `firing_animation_groups[group][sub]`'s selected animation for the puppet's initial `group`/`sub` state against what a normal guard's equivalent idle/stand animation looks like structurally (same animation table, different entry) — if this specific table entry's keyframe data has an unusually high root-joint Y at its start frame (`fa->y`, table-constant, confirmed table-driven not garbage per M-172) relative to comparable guard-idle entries, that names the exact asset/data source of the ~328-unit figure. This is a data-inspection task (dump/compare specific animation table entries), not further code tracing — a good candidate for a scoped local-delegate dispatch next session, or a quick script against the extracted animation data if `tools_pc/` has a dumper for it.
**Correction while writing this up (checked before committing to the claim): `chrTick` — where `subcalcmatrices`/the render-position computation actually happens (`chr.c:2777`) — is NOT a separate player-vs-guard code path; it's called identically for the player's own `prop` (`bondviewMoveAllPlayers`, `bondview2.c:10493`, `ret = chrTick(prop)`) and every guard (`chrlvActionTick`'s caller).** So the divergence M-173 found is not a distinct "player draw path skips a step" — it's that the *same* shared machinery is fed a different animation.
**M-174 (same session): tried to name that animation-selection call statically via `firing_animation_groups[group][sub]` (`bondview2.c:10831`) — added a probe there, got ZERO hits for the intro puppet.** Useful negative result, not a dead end: that whole per-tick firing-animation selector is apparently unreached during `CAMERAMODE_SWIRL`/`INTRO`, ruling it out as the source (a second static guess that turned out wrong — logged so the next investigator doesn't retry it).
**M-175 (same session): stopped guessing and instrumented the actual choke point instead — every path that sets the player's `bodyModel` animation funnels through `modelSetAnimation` (`model.c:2857`), so added a probe there (filtered to `model == g_CurrentPlayer->bodyModel`, `#ifdef PORT`) logging `__builtin_return_address(0)`, then resolved the logged addresses with `addr2line` against the built exe. Found the exact call site on the first try this way: `bondviewSetCameraMode`, `bondview2.c:936-942`, inside the `CAMERAMODE_SWIRL` branch's intro-fly-in setup (`bondview2.c:910-957`).** This code reads a **decomp ground-truth table**, `stage_intro_anim_table[]` (`bondview.c:355-365`, zero `#ifdef PORT` anywhere in this call chain — completely unmodified decomp): entry `[0]` is `{PTR_ANIM_extending_left_hand, 95.0, -1.0, 0.02}` — animation **"extending_left_hand"**, **start frame 95.0**, play-to-end, speed **0.02** (near-frozen, a held pose not a moving loop). `PTR_ANIM_extending_left_hand` is confirmed index **61** in `animation_table_ptrs1[]` (`initanitable.c`) — an exact match to the `aidx=61` measured live in M-172 on both Dam and Facility, and the `startframe=95.0`/near-zero speed exactly explains the captured `animframe≈96-97` (a few ticks' worth of playback at 0.02/tick past the 95.0 start) staying essentially flat for the puppet's whole visible lifetime.
**Conclusion: the mechanism setting the puppet's pose is fully decomp-verbatim, not a port-layer bug in the *selection* logic.** If Bond's third-person model genuinely renders ~328 units above ground while playing this specific held pose, the remaining candidates are narrowed to exactly two, both **outside this call site**: **(a)** the `PTR_ANIM_extending_left_hand` clip's own keyframe/root-motion data is mis-decoded for this port at/around frame 95 specifically (an asset-pipeline bug, the same class of investigation D252 used — dump and hand-decode the raw ROM bytes for this one clip's frame-95 root translation and compare against what the port's `model.c` bitstream decoder computes) — bucket (a), ABI/format-class, allowed without sign-off; **or (b)** this is genuinely how the pose looks on real N64 too, and either the *camera's* positioning during this exact swirl segment (not the puppet's) is what's actually different on PC, or the "floating Bond" reports are describing real, original N64 behavior that just looks more jarring on PC's sharper rendering — the "reveal, not regression" pattern this project has hit before (D227, D245). **Distinguishing (a) from (b) needs either real N64 reference footage of this exact moment, or the D252-style raw-byte asset dump — not more source-code tracing; this thread is fully static-exhausted for this session.**
**M-160/M-164/M-165 (2026-09-17, see D243): the shared mechanism is now understood in much more depth, though still not confirmed as the actual PC/N64 divergence.** D243's investigation this session traced the general pattern this whole family plausibly shares: `chrUpdateAnim` (`chr.c:2398-2424`) doesn't move a puppet's world position directly — it advances whatever animation is currently attached (`modelTickAnim`), then reads the model's root-joint transform back into `prop->pos` via `getsuboffset` (`model.c:684`). When something else (a teleport, a spawn, a death transition) moves the puppet's *logical* position without also resetting/updating its *animation state* to match, the next `chrUpdateAnim` call overwrites the new position with wherever the stale animation's root motion happens to be — producing exactly "floats/sinks/shakes relative to where it should be, in a way that tracks the animation, not the intended position." This is a strong candidate mechanism for D173's level-start float (an animation not yet settled/attached correctly at spawn) and D292's death-sink (the death-pose transition possibly not resetting the model's root anchor the way the collision/death logic expects) — **but D243's own parallel investigation (M-165) found this same mechanism may be entirely decomp-faithful**, with the actual PC-specific divergence being *which phase* of an animation a chr happens to be on at the moment of transition (inherited from real-time-dependent prior gameplay, not deterministic) rather than a code-path difference. **Do not treat this as solved** — it reframes what to look for (an animation-identity/phase mismatch at the transition moment, not a position-write bug per se) and gives a concrete technique (`GE_D193A`'s `aidx=` reverse-resolve + a `model->animframe1` phase read, see D243 M-166) for whoever gets live access to test D173/D292 the same way D243's `GE_D243M`/`GE_D243X2` probes now do.