fix(app): lift cell meshes 2cm + diagnose animation-registration rejections
The user reported the ground-floor of buildings flickers between the cell mesh floor and the terrain polygon underneath. Classic Z-fight: both surfaces are coincident in Z because buildings rest ON the terrain, and depth-buffer precision picks a winner per-pixel per frame. Add a 2cm Z lift to every EnvCell transform so the cell floor wins cleanly. Human-scale invisible. Separately: NPCs in Phase 6.4 aren't visibly breathing. To tell whether they're being rejected by our registration filter (framerate == 0, single-frame cycle, or one-frame animation) vs. just having short cycles the user can't see, add four rejection counters around the idleCycle check and print them in the summary line next to the other spawn diagnostics. Next run will tell us exactly which bucket eats NPCs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d379a75984
commit
5253c7bfe8
1 changed files with 30 additions and 1 deletions
|
|
@ -72,6 +72,11 @@ public sealed class GameWindow : IDisposable
|
|||
private int _liveDropReasonNoSetup;
|
||||
private int _liveDropReasonSetupDatMissing;
|
||||
private int _liveDropReasonNoMeshRefs;
|
||||
// Phase 6.4 animation-registration diagnostics
|
||||
private int _liveAnimRejectNoCycle;
|
||||
private int _liveAnimRejectFramerate;
|
||||
private int _liveAnimRejectSingleFrame;
|
||||
private int _liveAnimRejectPartFrames;
|
||||
|
||||
public GameWindow(string datDir, WorldGameState worldGameState, WorldEvents worldEvents)
|
||||
{
|
||||
|
|
@ -449,9 +454,18 @@ public sealed class GameWindow : IDisposable
|
|||
// the WorldEntity at identity so the renderer's
|
||||
// model = PartTransform * entityRoot = cellTransform * I
|
||||
// gives the correctly positioned cell mesh.
|
||||
//
|
||||
// Z lift: buildings sit ON the terrain mesh, so the ground
|
||||
// floor of every building is coincident in Z with the terrain
|
||||
// polygon beneath it. Without a bias the two fight for the
|
||||
// same depth and flicker as the camera moves. A small lift
|
||||
// (2 cm) is invisible from human scale but breaks the tie
|
||||
// cleanly in the cell mesh's favor.
|
||||
var cellOrigin = envCell.Position.Origin + lbOffset
|
||||
+ new System.Numerics.Vector3(0f, 0f, 0.02f);
|
||||
var cellTransform =
|
||||
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||
System.Numerics.Matrix4x4.CreateTranslation(envCell.Position.Origin + lbOffset);
|
||||
System.Numerics.Matrix4x4.CreateTranslation(cellOrigin);
|
||||
|
||||
var cellMeshRef = new AcDream.Core.World.MeshRef(envCellId, cellTransform);
|
||||
|
||||
|
|
@ -921,6 +935,17 @@ public sealed class GameWindow : IDisposable
|
|||
// Phase 6.4: register for per-frame playback if we resolved a real
|
||||
// cycle with a non-zero framerate and at least two frames in the
|
||||
// cycle (single-frame poses are static and don't need ticking).
|
||||
// Diagnostic: log why we did / didn't register so we can tell
|
||||
// which entities fall through the filter.
|
||||
if (idleCycle is null)
|
||||
_liveAnimRejectNoCycle++;
|
||||
else if (idleCycle.Framerate == 0f)
|
||||
_liveAnimRejectFramerate++;
|
||||
else if (idleCycle.HighFrame <= idleCycle.LowFrame)
|
||||
_liveAnimRejectSingleFrame++;
|
||||
else if (idleCycle.Animation.PartFrames.Count <= 1)
|
||||
_liveAnimRejectPartFrames++;
|
||||
|
||||
if (idleCycle is not null && idleCycle.Framerate != 0f
|
||||
&& idleCycle.HighFrame > idleCycle.LowFrame
|
||||
&& idleCycle.Animation.PartFrames.Count > 1)
|
||||
|
|
@ -950,6 +975,10 @@ public sealed class GameWindow : IDisposable
|
|||
// waiting for a graceful shutdown.
|
||||
if (_liveSpawnReceived % 20 == 0)
|
||||
{
|
||||
Console.WriteLine(
|
||||
$"live: animated={_animatedEntities.Count} " +
|
||||
$"animReject: noCycle={_liveAnimRejectNoCycle} fr0={_liveAnimRejectFramerate} " +
|
||||
$"1frame={_liveAnimRejectSingleFrame} partFrames={_liveAnimRejectPartFrames}");
|
||||
Console.WriteLine(
|
||||
$"live: summary recv={_liveSpawnReceived} hydrated={_liveSpawnHydrated} " +
|
||||
$"drops: noPos={_liveDropReasonNoPos} noSetup={_liveDropReasonNoSetup} " +
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue