fix(world+streaming): trees-in-sky Z, O(1) anim, teleport near-ring + immediate unloads
Three apparatus-confirmed fixes from the world-load/FPS deep-dive (all live-verified).
1. trees-in-sky — scenery ground-Z now samples THIS landblock's OWN heightmap
(TerrainSurface.SampleZFromHeightmap, lock-step with the physics terrain) instead
of the global PhysicsEngine.SampleTerrainZ query. At build time the landblock isn't
registered in physics yet, so that query could only return null OR a STALE
neighbour's height — the previous location's terrain, still registered after a
teleport recenter — planting scenery at the old altitude (+250..500m, confirmed via
the [scenery-z-stale] probe). Own-heightmap is correct in every case; the query is
removed. (GameWindow.BuildSceneryEntitiesForStreaming)
2. FPS per-hop — TickAnimations recovered each animated entity's server guid via an
O(N) ReferenceEquals reverse scan over ALL _entitiesByServerGuid (which never
evicts, so N climbs every teleport — the drops-with-each-hop sink). Replaced with
ae.Entity.ServerGuid: O(1), exact-equivalent (the dict key IS entity.ServerGuid).
(GameWindow.TickAnimations)
3. teleport arrival + bulk floating terrain — two streaming fixes:
- Near-ring eager-apply: a teleport applies the destination's 3x3 surroundings
(StreamingController.PriorityRadius) and holds the fade until they're resident
(PhysicsEngine.IsNeighborhoodTerrainResident), so the player arrives in a loaded,
collidable world instead of one landblock in the void.
- Immediate unloads: DrainAndApply no longer throttles UNLOADS at the per-frame
load budget — they're cheap (free GPU buffers, no upload). A teleport produced
~600 unloads draining at 4/frame, leaving the previous region resident for
seconds (floating terrain) and accumulating across rapid hops (951 resident vs a
625 window). Only GPU-upload LOADS are metered now. Cut out-of-window resident
650 -> 63 and resident 951 -> 688 (live-verified via [resid-audit]).
Includes gated-off diagnostic probes (ACDREAM_PROBE_SCENERY_FRAME / _BLDG_REACH /
_STREAM_RESID) used to root-cause the above — zero-cost when unset, same pattern as
the committed tp-probe.
The pre-existing teleport-induced "terrain arcs in the sky" (present in the dd2eb8b
baseline too, with NONE of this work) are a SEPARATE bug — investigated next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8fbde99441
commit
15e320490d
6 changed files with 382 additions and 61 deletions
|
|
@ -448,6 +448,138 @@ public static class PhysicsDiagnostics
|
|||
$"[tp-probe] {point,-6} id=0x{id:X8} t={Environment.TickCount64} {extra}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trees-in-sky frame-split probe (2026-06-22 — REMOVABLE apparatus). The
|
||||
/// streaming worker bakes each landblock's SCENERY world position against the
|
||||
/// <c>_liveCenter</c> that is current when it BUILDS the landblock, while the
|
||||
/// render thread positions that landblock's TERRAIN against the
|
||||
/// <c>_liveCenter</c> that is current when it APPLIES it. A teleport recenters
|
||||
/// <c>_liveCenter</c> between those two moments, so a landblock built before the
|
||||
/// recenter and applied after it has its scenery offset from its terrain by
|
||||
/// <c>deltaLB × 192m</c> in XY — the "arc of trees floating in the sky" after a
|
||||
/// teleport. When enabled, <c>BuildSceneryEntitiesForStreaming</c> records the
|
||||
/// build-time center and <c>ApplyLoadedTerrainLocked</c> emits one
|
||||
/// <c>[scenery-frame]</c> line per landblock comparing build vs apply center; a
|
||||
/// non-zero <c>deltaLB</c> on a misplaced landblock confirms the mechanism.
|
||||
/// Initial state from <c>ACDREAM_PROBE_SCENERY_FRAME=1</c>. Zero cost when off.
|
||||
/// </summary>
|
||||
public static bool ProbeSceneryFrameEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_SCENERY_FRAME") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// One <c>[scenery-frame]</c> line. Self-guards on
|
||||
/// <see cref="ProbeSceneryFrameEnabled"/>. A non-zero <c>deltaLB</c> means this
|
||||
/// landblock's scenery was baked against a different streaming center than its
|
||||
/// terrain was applied against — the trees-in-sky frame split.
|
||||
/// </summary>
|
||||
public static void LogSceneryFrame(
|
||||
uint landblockId, int buildCx, int buildCy, int applyCx, int applyCy)
|
||||
{
|
||||
if (!ProbeSceneryFrameEnabled) return;
|
||||
int dx = buildCx - applyCx, dy = buildCy - applyCy;
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[scenery-frame] lb=0x{landblockId:X8} build=({buildCx},{buildCy}) apply=({applyCx},{applyCy}) deltaLB=({dx},{dy}) offsetM=({dx * 192f:F1},{dy * 192f:F1})"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One <c>[scenery-z-stale]</c> line (self-guards on
|
||||
/// <see cref="ProbeSceneryFrameEnabled"/>). Emitted when, at scenery build, the
|
||||
/// physics-engine terrain sample (<c>SampleTerrainZ</c>, which searches ALL
|
||||
/// registered landblocks) disagrees materially with the landblock's OWN
|
||||
/// heightmap. That divergence means the physics query resolved the world point
|
||||
/// into a STALE neighbor landblock — the previous location's terrain, still
|
||||
/// registered after a teleport recenter (which removes only the single stale
|
||||
/// center landblock) until streaming unloads it — and returned ITS height. The
|
||||
/// scenery is then planted at the old location's altitude: trees-in-sky. The
|
||||
/// own-heightmap value (<paramref name="ownZ"/>) is the correct Z.
|
||||
/// </summary>
|
||||
public static void LogSceneryZStale(
|
||||
uint landblockId, float worldX, float worldY, float physicsZ, float ownZ)
|
||||
{
|
||||
if (!ProbeSceneryFrameEnabled) return;
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[scenery-z-stale] lb=0x{landblockId:X8} world=({worldX:F1},{worldY:F1}) physicsZ={physicsZ:F2} ownZ={ownZ:F2} deltaZ={physicsZ - ownZ:F2}"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lost-building-collision probe (2026-06-22 — REMOVABLE apparatus). Building
|
||||
/// WALL collision is the dat-baked <c>PhysicsDataCache.CacheBuilding</c> channel
|
||||
/// (first-wins, never removed), reached at resolve time only when a candidate
|
||||
/// landcell in the player's swept cell-walk hits <c>GetBuilding(cellId)</c>. When
|
||||
/// enabled: <c>[bldg-cache]</c> logs each CacheBuilding (CACHED vs first-wins
|
||||
/// skip) so we can see whether/when a landblock's buildings cache; and
|
||||
/// <c>[bldg-reach]</c> logs, per cell-walk in a landblock that HAS cached
|
||||
/// buildings, which of them landed in the player's candidate set
|
||||
/// (<c>reachable=YES/NO</c>). NO while standing at a wall ⇒ the building is cached
|
||||
/// but the player's post-teleport cell resolution excludes it (#145 family);
|
||||
/// empty <c>cachedInLb</c> ⇒ the landblock's CacheBuilding never ran (apply
|
||||
/// backlog). Initial state from <c>ACDREAM_PROBE_BLDG_REACH=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeBuildingReachEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_BLDG_REACH") == "1";
|
||||
|
||||
public static void LogBuildingCache(uint landcellId, bool cached)
|
||||
{
|
||||
if (!ProbeBuildingReachEnabled) return;
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[bldg-cache] landcell=0x{landcellId:X8} {(cached ? "CACHED" : "skip(first-wins)")}"));
|
||||
}
|
||||
|
||||
public static void LogBuildingReach(
|
||||
uint seedCell, int candidateCount,
|
||||
IReadOnlyCollection<uint> cachedInLb, IReadOnlyCollection<uint> inCandidateSet)
|
||||
{
|
||||
if (!ProbeBuildingReachEnabled) return;
|
||||
if (cachedInLb.Count == 0) return; // only interesting in a landblock with buildings
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[bldg-reach] seed=0x{seedCell:X8} cands={candidateCount} cachedInLb=[{HexList(cachedInLb)}] inSet=[{HexList(inCandidateSet)}] reachable={(inCandidateSet.Count > 0 ? "YES" : "NO")}"));
|
||||
}
|
||||
|
||||
private static string HexList(IReadOnlyCollection<uint> ids)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (!first) sb.Append(',');
|
||||
sb.Append(System.FormattableString.Invariant($"0x{id:X8}"));
|
||||
first = false;
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Floating-terrain probe (2026-06-22 — REMOVABLE apparatus). The thin terrain
|
||||
/// "traces" at the horizon after rapid multi-hop teleporting are landblocks rendering
|
||||
/// at a STALE world position — their Chebyshev distance from the current streaming
|
||||
/// center exceeds the window (FarRadius), so their world origin is off by the teleport
|
||||
/// distance. When enabled: <c>[apply-oob]</c> fires when a landblock is APPLIED while
|
||||
/// out-of-window (an in-flight load from a PREVIOUS center, applied against the new one);
|
||||
/// <c>[resid-audit]</c> (throttled) lists every RESIDENT landblock sitting out-of-window
|
||||
/// (un-cleared old-location terrain that never unloaded). A persistent non-zero
|
||||
/// <c>outOfWindow</c> count is the floating terrain; whether it arrives via apply-oob or
|
||||
/// lingers as residents tells us which mechanism. Initial state from
|
||||
/// <c>ACDREAM_PROBE_STREAM_RESID=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeStreamResidEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_STREAM_RESID") == "1";
|
||||
|
||||
public static void LogApplyOob(uint landblockId, int cheby, int farRadius, float ox, float oy)
|
||||
{
|
||||
if (!ProbeStreamResidEnabled) return;
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[apply-oob] lb=0x{landblockId:X8} cheby={cheby} (>{farRadius}) origin=({ox:F0},{oy:F0}) — applied OUTSIDE the streaming window (in-flight stale-location apply)"));
|
||||
}
|
||||
|
||||
public static void LogResidentAudit(
|
||||
int centerX, int centerY, int residentTotal, IReadOnlyCollection<uint> outOfWindow)
|
||||
{
|
||||
if (!ProbeStreamResidEnabled) return;
|
||||
if (outOfWindow.Count == 0) return; // only interesting when stale terrain is resident
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[resid-audit] center=({centerX},{centerY}) resident={residentTotal} outOfWindow={outOfWindow.Count} oob=[{HexList(outOfWindow)}]"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A6.P3 issue #98 step-walk investigation (2026-05-23). When true,
|
||||
/// emits one <c>[step-walk]</c> line at selected points in the transition
|
||||
|
|
@ -575,6 +707,9 @@ public static class PhysicsDiagnostics
|
|||
ProbeSweptEnabled = false;
|
||||
ProbeStepWalkEnabled = false;
|
||||
ProbeTeleportEnabled = false;
|
||||
ProbeSceneryFrameEnabled = false;
|
||||
ProbeBuildingReachEnabled = false;
|
||||
ProbeStreamResidEnabled = false;
|
||||
|
||||
// Side-channel fields
|
||||
LastBspHitPoly = null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue