diff --git a/src/AcDream.App/Rendering/FlyCamera.cs b/src/AcDream.App/Rendering/FlyCamera.cs index 9dcd2a5..eaeb65f 100644 --- a/src/AcDream.App/Rendering/FlyCamera.cs +++ b/src/AcDream.App/Rendering/FlyCamera.cs @@ -11,7 +11,7 @@ public sealed class FlyCamera : ICamera public float FovY { get; set; } = MathF.PI / 3f; public float Aspect { get; set; } = 16f / 9f; - public float MoveSpeed { get; set; } = 100f; // world units per second + public float MoveSpeed { get; set; } = 35f; // world units per second (AC cell size = 24) public float MouseSensitivity { get; set; } = 0.003f; private const float PitchLimit = 1.5533f; // ~89 degrees diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 733c026..a49f78d 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -705,9 +705,16 @@ public sealed class GameWindow : IDisposable if (resolvedOverridesByPart is not null && resolvedOverridesByPart.TryGetValue(partIdx, out var partOverrides)) surfaceOverrides = partOverrides; - // Pre-multiply scale so it happens FIRST (local mesh coords) before - // the part-local transform and the entity-root transform. - var transform = scale == 1.0f ? mr.PartTransform : scaleMat * mr.PartTransform; + // Multiplication order matches offline scenery hydration: + // `PartTransform * scaleMat`. In row-vector semantics this means + // "apply PartTransform first (which includes the part-attachment + // translation), then scale in the resulting space." Using the + // opposite order (`scaleMat * PartTransform`) scales in mesh-local + // space first, which leaves the part-attachment offset unscaled — + // for multi-part entities like the Nullified Statue that causes + // the parts to drift relative to each other ("distorted") and the + // base anchor to end up below the ground ("sinks into foundry"). + var transform = scale == 1.0f ? mr.PartTransform : mr.PartTransform * scaleMat; meshRefs.Add(new AcDream.Core.World.MeshRef(mr.GfxObjId, transform) {