fix(D.2b): Slice 2 — retail-exact doll pose, camera + heading (visual gate)

The paperdoll doll now matches retail: correct held pose, framing, and
facing. Three decomp-sourced fixes closed the visual gate.

Pose: cdb-confirmed m_didAnimation = 0x030003C0 (gmPaperDollUI), played
once + HELD (set_sequence_animation framerate=0, RedressCreature
0x004a3c22). Dumping the dat showed the 29-frame anim has only two
distinct keyframes — frame 0 (transitional, bent arm) and frames 1..28
(byte-identical: the settled stance, arms down + leg back) — so
ApplyPaperdollPose applies the LAST frame statically (no looping).

Camera: ported verbatim from UIElement_Viewport::SetCamera (decomp
0x004a5a39). position (0.12,-2.4,0.88); direction (0,0,0) => IDENTITY
view frame => look straight down +Y, ZERO yaw; FOV pi/4 (CreatureMode
ctor default 0x004543cf); ambient 0.3. The prior hand-tune aimed the
camera at mid-body, adding a ~2deg yaw that turned the doll's face away
— full-body framing comes from eye-height + FOV, not aiming.

Heading: retail Frame::set_heading(h) (0x00535e40) builds facing
(sin h, cos h); System.Numerics CreateFromAxisAngle(+Z, +h) rotates the
body's default +Y forward to (-sin h, cos h) — the X-lean was MIRRORED
(~22deg), the real cause of the turned-away face. Negate the angle to
land on retail's facing.

Wrap-up: stripped the temporary O/P pose-frame stepper + Slice2
diagnostics; divergence register AP-66 reworded, AP-67 (RTT doll render
vs in-cell CreatureMode::Render) + AP-68 (per-race UpdateForRace
unimpl) added; DollCameraTests pinned to the retail values + a zero-yaw
guard. tools/cdb/paperdoll-pose.cdb = the pose-DID capture script.
Build + full suite green (Core 1579 / Core.Net 343 / App 597 / UI 425).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-25 13:16:27 +02:00
parent 594942f127
commit 8fa66c23d5
8 changed files with 136 additions and 37 deletions

View file

@ -45,9 +45,13 @@ public static class DollEntityBuilder
/// </summary>
public const uint DollRenderId = 0xDA11_D012u;
// retail set_heading: 191.367905° about +Z, expressed as a Quaternion
// (axis-angle, axis = (0,0,1), θ = 191.367905° in radians).
private static readonly float _headingRad = 191.367905f * (MathF.PI / 180f);
// retail RedressCreature: CPhysicsObj::set_heading(191.367905°) (decomp 0x004a3c0a). Frame::set_heading(h)
// (decomp 0x00535e40) builds the facing vector as (sin h°, cos h°, 0): at h=0 the creature faces +Y (AC
// North), the heading increasing CLOCKWISE toward +X. We rotate the doll's default +Y-forward body about
// +Z; System.Numerics rotates +Y → (sin θ, cos θ), so to land on retail's (sin h, cos h) the angle is
// NEGATED (θ = h). Using +h mirrors the X-lean (~22° off → the face reads as turned away from the viewer).
private const float _headingDegrees = 191.367905f;
private static readonly float _headingRad = -_headingDegrees * (MathF.PI / 180f);
private static readonly Quaternion _dollRotation =
Quaternion.CreateFromAxisAngle(new Vector3(0f, 0f, 1f), _headingRad);