perf(physics): cut production traversal to flat assets
Make prepared flat BSP data authoritative for gameplay while retaining the parsed graph only as an exact sampled referee. Fail production publication when collision package data is genuinely absent, keep idempotent already-cached publication valid, and move cell membership, floor lookup, camera diagnostics, and live/static shape bounds onto the flat representation. Validated by 8,402 Release tests, a strict dense-Arwic connected gate with 46,309/46,309 exact referee matches, and graceful shutdown. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
d9446030e6
commit
068a06518d
13 changed files with 743 additions and 43 deletions
|
|
@ -13,11 +13,13 @@ namespace AcDream.Core.Physics;
|
|||
/// Thread-safe cache of physics-relevant data extracted from GfxObj and Setup
|
||||
/// dat objects during streaming and live-object materialization. Prepared
|
||||
/// payloads are built/read off the hot path; one update-thread publisher
|
||||
/// installs each graph/flat pair. ConcurrentDictionary keeps diagnostics and
|
||||
/// tooling reads safe without a global lock.
|
||||
/// installs prepared production records and, during the I6 acceptance window,
|
||||
/// their graph referee. ConcurrentDictionary keeps diagnostics and tooling
|
||||
/// reads safe without a global lock.
|
||||
/// </summary>
|
||||
public sealed class PhysicsDataCache
|
||||
{
|
||||
private readonly bool _requirePreparedCollision;
|
||||
private readonly ConcurrentDictionary<uint, GfxObjPhysics> _gfxObj = new();
|
||||
private readonly ConcurrentDictionary<uint, GfxObjVisualBounds> _visualBounds = new();
|
||||
private readonly ConcurrentDictionary<uint, SetupPhysics> _setup = new();
|
||||
|
|
@ -32,7 +34,13 @@ public sealed class PhysicsDataCache
|
|||
_flatEnvCell = new();
|
||||
|
||||
public PhysicsDataCache()
|
||||
: this(requirePreparedCollision: false)
|
||||
{
|
||||
}
|
||||
|
||||
private PhysicsDataCache(bool requirePreparedCollision)
|
||||
{
|
||||
_requirePreparedCollision = requirePreparedCollision;
|
||||
if (PhysicsDiagnostics.CollisionShadowSampleEvery > 0)
|
||||
{
|
||||
CollisionShadow = new CollisionShadowVerifier(
|
||||
|
|
@ -41,15 +49,29 @@ public sealed class PhysicsDataCache
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the gameplay cache with Slice I6's prepared flat collision
|
||||
/// representation authoritative. The public constructor deliberately
|
||||
/// remains the graph-oracle fixture seam until the I6 referee is removed.
|
||||
/// </summary>
|
||||
public static PhysicsDataCache CreateProduction()
|
||||
{
|
||||
var cache = new PhysicsDataCache(requirePreparedCollision: true)
|
||||
{
|
||||
CollisionTraversalMode = CollisionTraversalMode.Flat,
|
||||
};
|
||||
return cache;
|
||||
}
|
||||
|
||||
internal CollisionShadowVerifier? CollisionShadow { get; set; }
|
||||
|
||||
public CollisionShadowStats CollisionShadowStats =>
|
||||
CollisionShadow?.Stats ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Slice I graph/flat differential selector. Production remains on the
|
||||
/// graph oracle until I6; tests use the flat value to run the same complete
|
||||
/// resolver from cloned inputs.
|
||||
/// Slice I graph/flat differential selector. Production is flat-authoritative
|
||||
/// from I6 onward. Graph remains an explicit fixture/oracle mode until the
|
||||
/// referee is removed after acceptance.
|
||||
/// </summary>
|
||||
internal CollisionTraversalMode CollisionTraversalMode { get; set; } =
|
||||
CollisionTraversalMode.Graph;
|
||||
|
|
@ -84,6 +106,15 @@ public sealed class PhysicsDataCache
|
|||
GfxObj gfxObj,
|
||||
FlatGfxObjCollisionAsset? prepared = null)
|
||||
{
|
||||
if (_requirePreparedCollision &&
|
||||
gfxObj.Flags.HasFlag(GfxObjFlags.HasPhysics) &&
|
||||
gfxObj.PhysicsBSP?.Root is not null &&
|
||||
prepared?.PhysicsBsp is null &&
|
||||
!_flatGfxObj.ContainsKey(gfxObjId))
|
||||
{
|
||||
throw MissingPreparedCollision("GfxObj", gfxObjId);
|
||||
}
|
||||
|
||||
if (prepared is not null)
|
||||
_flatGfxObj.TryAdd(gfxObjId, prepared);
|
||||
|
||||
|
|
@ -199,6 +230,11 @@ public sealed class PhysicsDataCache
|
|||
Setup setup,
|
||||
FlatSetupCollision? prepared = null)
|
||||
{
|
||||
if (_requirePreparedCollision &&
|
||||
prepared is null &&
|
||||
!_flatSetup.ContainsKey(setupId))
|
||||
throw MissingPreparedCollision("Setup", setupId);
|
||||
|
||||
if (prepared is not null)
|
||||
_flatSetup.TryAdd(setupId, prepared);
|
||||
|
||||
|
|
@ -234,6 +270,15 @@ public sealed class PhysicsDataCache
|
|||
FlatCellStructureCollisionAsset? preparedStructure = null,
|
||||
FlatEnvCellTopology? preparedTopology = null)
|
||||
{
|
||||
if (_requirePreparedCollision &&
|
||||
preparedStructure is null &&
|
||||
!_flatCellStruct.ContainsKey(envCellId))
|
||||
throw MissingPreparedCollision("CellStruct", envCellId);
|
||||
if (_requirePreparedCollision &&
|
||||
preparedTopology is null &&
|
||||
!_flatEnvCell.ContainsKey(envCellId))
|
||||
throw MissingPreparedCollision("EnvCell topology", envCellId);
|
||||
|
||||
if (preparedStructure is not null)
|
||||
_flatCellStruct.TryAdd(envCellId, preparedStructure);
|
||||
if (preparedTopology is not null)
|
||||
|
|
@ -242,7 +287,14 @@ public sealed class PhysicsDataCache
|
|||
// UCG Stage 1: register in the unified graph for ALL cells — before the
|
||||
// idempotency + null-BSP guards below, so BSP-less cells are still included.
|
||||
if (!CellGraph.Contains(envCellId))
|
||||
CellGraph.Add(UcgEnvCell.FromDat(envCellId, envCell, cellStruct, worldTransform));
|
||||
{
|
||||
CellGraph.Add(UcgEnvCell.FromDat(
|
||||
envCellId,
|
||||
envCell,
|
||||
cellStruct,
|
||||
worldTransform,
|
||||
preparedStructure?.ContainmentBsp));
|
||||
}
|
||||
|
||||
if (_cellStruct.ContainsKey(envCellId)) return;
|
||||
if (cellStruct.PhysicsBSP?.Root is null) return;
|
||||
|
|
@ -455,6 +507,13 @@ public sealed class PhysicsDataCache
|
|||
return resolved;
|
||||
}
|
||||
|
||||
private static InvalidOperationException MissingPreparedCollision(
|
||||
string kind,
|
||||
uint sourceId) =>
|
||||
new(
|
||||
$"Production {kind} 0x{sourceId:X8} has no prepared collision asset. " +
|
||||
"Gameplay must not extract or fall back to a parsed DAT graph.");
|
||||
|
||||
public GfxObjPhysics? GetGfxObj(uint id) => _gfxObj.TryGetValue(id, out var p) ? p : null;
|
||||
|
||||
public SetupPhysics? GetSetup(uint id) => _setup.TryGetValue(id, out var p) ? p : null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue