Merge claude/hopeful-maxwell-214a12 — D.2b UI Studio + faithful importer + Character window
UI Studio (preview panels through the production renderer), importer dat-fidelity (Fix A/B/C/4/5: the importer carries dat font/justification/color; boundary look=importer / state=runtime), and the Character window Attributes tab (reads as retail). 3062 tests green. Handoff: docs/research/2026-06-26-mockup-stage-handoff.md. # Conflicts: # docs/ISSUES.md # docs/architecture/retail-divergence-register.md
This commit is contained in:
commit
ca94b479bf
61 changed files with 125881 additions and 44 deletions
41
tests/AcDream.App.Tests/Rendering/DollCameraTests.cs
Normal file
41
tests/AcDream.App.Tests/Rendering/DollCameraTests.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public class DollCameraTests
|
||||
{
|
||||
[Fact]
|
||||
public void Eye_position_is_retail_verbatim()
|
||||
{
|
||||
var cam = new DollCamera { Aspect = 100f / 214f };
|
||||
Assert.True(Matrix4x4.Invert(cam.View, out var inv));
|
||||
var eye = inv.Translation;
|
||||
// Retail UIElement_Viewport::SetCamera position (decomp 0x004a5a51-0x004a5a61).
|
||||
Assert.Equal(0.12f, eye.X, 3);
|
||||
Assert.Equal(-2.4f, eye.Y, 3);
|
||||
Assert.Equal(0.88f, eye.Z, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Look_axis_is_pure_plus_y_zero_yaw()
|
||||
{
|
||||
// retail SetCameraDirection(0,0,0) ⇒ IDENTITY view frame ⇒ camera looks straight down +Y.
|
||||
// System.Numerics CreateLookAt: forward = -(M13, M23, M33). Any yaw (Target.x≠Eye.x) would put a
|
||||
// non-zero X here and turn the doll's face away — the bug this guards against.
|
||||
var cam = new DollCamera { Aspect = 100f / 214f };
|
||||
var forward = -new Vector3(cam.View.M13, cam.View.M23, cam.View.M33);
|
||||
Assert.Equal(0f, forward.X, 4);
|
||||
Assert.Equal(1f, forward.Y, 4);
|
||||
Assert.Equal(0f, forward.Z, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Projection_is_finite_and_uses_aspect()
|
||||
{
|
||||
var cam = new DollCamera { Aspect = 1.5f };
|
||||
Assert.True(float.IsFinite(cam.Projection.M11));
|
||||
Assert.NotEqual(0f, cam.Projection.M34); // perspective w = -z term
|
||||
}
|
||||
}
|
||||
84
tests/AcDream.App.Tests/Rendering/DollEntityBuilderTests.cs
Normal file
84
tests/AcDream.App.Tests/Rendering/DollEntityBuilderTests.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Core.World;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public class DollEntityBuilderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Builds_doll_entity_with_synthetic_guid_and_player_setup()
|
||||
{
|
||||
// SubPaletteRange uses: SubPaletteId, Offset (byte), Length (byte)
|
||||
var doll = DollEntityBuilder.Build(
|
||||
setupId: 0x0200_0001u,
|
||||
meshRefs: new List<MeshRef>(),
|
||||
basePaletteId: 0x04000ABCu,
|
||||
subPalettes: new (uint SubPaletteId, byte Offset, byte Length)[] { (0x0F00_0001u, 0, 8) },
|
||||
partOverrides: new (byte PartIndex, uint GfxObjId)[] { (2, 0x0100_0042u) });
|
||||
|
||||
Assert.Equal(0x0200_0001u, doll.SourceGfxObjOrSetupId);
|
||||
Assert.Equal(DollEntityBuilder.DollServerGuid, doll.ServerGuid);
|
||||
Assert.NotEqual(0u, doll.ServerGuid);
|
||||
Assert.Single(doll.PartOverrides);
|
||||
Assert.Equal((byte)2, doll.PartOverrides[0].PartIndex);
|
||||
Assert.Equal(0x0100_0042u, doll.PartOverrides[0].GfxObjId);
|
||||
Assert.NotNull(doll.PaletteOverride);
|
||||
Assert.Equal(0x04000ABCu, doll.PaletteOverride!.BasePaletteId);
|
||||
Assert.Single(doll.PaletteOverride.SubPalettes);
|
||||
Assert.Equal(0x0F00_0001u, doll.PaletteOverride.SubPalettes[0].SubPaletteId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Null_overrides_give_empty_collections_not_null()
|
||||
{
|
||||
var doll = DollEntityBuilder.Build(0x0200_0001u, new List<MeshRef>(), null, null, null);
|
||||
// No subpalettes => no PaletteOverride (mirrors GameWindow: only built when Count > 0)
|
||||
Assert.Null(doll.PaletteOverride);
|
||||
Assert.Empty(doll.PartOverrides);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Empty_subpalettes_give_null_palette_override()
|
||||
{
|
||||
// Mirror GameWindow: SubPalettes with Count == 0 => no override
|
||||
var doll = DollEntityBuilder.Build(
|
||||
0x0200_0001u,
|
||||
new List<MeshRef>(),
|
||||
basePaletteId: 0x04000001u,
|
||||
subPalettes: System.Array.Empty<(uint, byte, byte)>(),
|
||||
partOverrides: null);
|
||||
Assert.Null(doll.PaletteOverride);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Heading_is_normalized_quaternion_facing_viewer()
|
||||
{
|
||||
var doll = DollEntityBuilder.Build(0x0200_0001u, new List<MeshRef>(), null, null, null);
|
||||
Assert.True(System.MathF.Abs(doll.Rotation.LengthSquared() - 1f) < 1e-3f);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Position_is_world_origin()
|
||||
{
|
||||
var doll = DollEntityBuilder.Build(0x0200_0001u, new List<MeshRef>(), null, null, null);
|
||||
Assert.Equal(Vector3.Zero, doll.Position);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParentCellId_is_null_for_doll_scene()
|
||||
{
|
||||
var doll = DollEntityBuilder.Build(0x0200_0001u, new List<MeshRef>(), null, null, null);
|
||||
Assert.Null(doll.ParentCellId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MeshRefs_are_passed_through()
|
||||
{
|
||||
var refs = new List<MeshRef> { new MeshRef(0x01000001u, Matrix4x4.Identity) };
|
||||
var doll = DollEntityBuilder.Build(0x0200_0001u, refs, null, null, null);
|
||||
Assert.Same(refs, doll.MeshRefs);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue