wip(physics): collision O(changed) delta-commit (O1-O3) - ON HOLD, feel-test failed
Publication-throughput rework per the D2 design (docs/research/ 2026-08-02-collision-throughput-handoff/design-note.md): O1 per-prefix installed-key ledgers replacing the seal's full-map scans; O2 per- landblock delta commit (LandblockReplacementApplyCursor against the active root) replacing whole-world TransferTo; O3 empty staging root, commit-time reflood (CObjCell::init_objects 0x0052B420 -> recalc_cross_cells 0x00515A30), journal/peer-rebase machinery deleted (~1,900 lines net). Automated gates green: Runtime 999, Core physics 2,135, App 4,039/3, Headless 79, complete solution 10,812/0/4; lifecycle gate PASS (connected-world-gate-20260802-193029). Soak 194423: publication-side acceptance fully met (37 -> 4 failures, all convergence dims zero, loadedLandblocks baseline-identical, waitCue 6/9 -> 1/9). COMMITTED AS WIP ON USER DIRECTION - NOT ACCEPTED. The user feel-test FAILED on this tree: monsters still pop into existence at close range, monsters spawned mid-air far ahead, static placements visibly wrong, plus 243x "Landblock already has a full retirement receipt" InvalidOperationException catch-retry loop during origin recenter (launch-feeltest-oclone.log). The 4 remaining soak failures (pendingLandblockRetirements 131/122 at the Caul->Sawato stops) and the implementer's "exposed pre-existing" classification are under re-judgment against that loop. Dual reviews were dispatched and then stopped mid-flight on user direction; NO review has passed this commit. Full problem inventory + next-agent instructions: docs/research/2026-08-02-collision-throughput-handoff/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c52ce14a07
commit
71604331cf
13 changed files with 6410 additions and 1894 deletions
|
|
@ -46,7 +46,8 @@ public sealed class CellGraph
|
|||
|
||||
public bool Contains(uint envCellId) => _envCells.ContainsKey(envCellId);
|
||||
|
||||
public void Add(EnvCell cell) => _envCells.TryAdd(cell.Id, cell);
|
||||
public void Add(EnvCell cell) =>
|
||||
_collisionWorld.Current.TryAddEnvCell(cell.Id, cell);
|
||||
|
||||
/// <param name="landblockPrefix">Any id in the cell's landblock; masked to (id & 0xFFFF0000).</param>
|
||||
public void RegisterTerrain(uint landblockPrefix, TerrainSurface terrain, Vector3 worldOrigin)
|
||||
|
|
@ -96,8 +97,27 @@ public sealed class CellGraph
|
|||
_terrain.TryRemove(lb, out _);
|
||||
for (uint low = 1u; low <= 0x40u; low++)
|
||||
_outdoorCells.TryRemove(lb | low, out _);
|
||||
foreach (var id in new List<uint>(_envCells.Keys))
|
||||
if ((id & 0xFFFF0000u) == lb) _envCells.TryRemove(id, out _);
|
||||
RemoveEnvCellPrefixKeys(lb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// O1: retire one prefix's EnvCells through the installed-key ledger —
|
||||
/// O(prefix keys), never a whole-map scan. Removal tombstones the
|
||||
/// captured slot list, so index iteration stays exact.
|
||||
/// </summary>
|
||||
private void RemoveEnvCellPrefixKeys(uint prefix)
|
||||
{
|
||||
CollisionWorldState world = _collisionWorld.Current;
|
||||
List<uint>? slots = world.EnvCellKeys.SlotsForPrefix(prefix);
|
||||
if (slots is null)
|
||||
return;
|
||||
int limit = slots.Count;
|
||||
for (int index = 0; index < limit; index++)
|
||||
{
|
||||
uint id = slots[index];
|
||||
if (id != 0u)
|
||||
world.RemoveEnvCell(id);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -113,8 +133,7 @@ public sealed class CellGraph
|
|||
{
|
||||
CurrCell = null;
|
||||
}
|
||||
foreach (var id in new List<uint>(_envCells.Keys))
|
||||
if ((id & 0xFFFF0000u) == lb) _envCells.TryRemove(id, out _);
|
||||
RemoveEnvCellPrefixKeys(lb);
|
||||
}
|
||||
|
||||
/// <summary>The universal id->cell resolver (retail CObjCell::GetVisible).</summary>
|
||||
|
|
@ -170,7 +189,10 @@ public sealed class CellGraph
|
|||
private readonly List<KeyValuePair<uint, EnvCell>> _envCells = new();
|
||||
private readonly HashSet<uint> _stagingIds = new();
|
||||
private readonly List<uint> _removeIds = new();
|
||||
private IEnumerator<KeyValuePair<uint, EnvCell>>? _enumerator;
|
||||
private List<uint>? _keySlots;
|
||||
private int _keySlotLimit;
|
||||
private bool _keySlotsCaptured;
|
||||
private int _cursor;
|
||||
private int _phase;
|
||||
|
||||
internal LandblockReplacementBuilder(
|
||||
|
|
@ -181,44 +203,48 @@ public sealed class CellGraph
|
|||
_active = active;
|
||||
_staging = staging;
|
||||
_prefix = landblockId & 0xFFFF0000u;
|
||||
_enumerator = staging._envCells.GetEnumerator();
|
||||
}
|
||||
|
||||
internal bool Advance()
|
||||
{
|
||||
if (_phase == 0)
|
||||
{
|
||||
if (_enumerator!.MoveNext())
|
||||
// O1: enumerate the staging root's installed target-prefix
|
||||
// EnvCell keys via the ledger instead of scanning the map.
|
||||
if (TryTakeNextPrefixKey(
|
||||
_staging._collisionWorld.Current.EnvCellKeys,
|
||||
out uint stagingId))
|
||||
{
|
||||
KeyValuePair<uint, EnvCell> pair = _enumerator.Current;
|
||||
if ((pair.Key & 0xFFFF0000u) == _prefix
|
||||
&& (pair.Key & 0xFFFFu) >= 0x0100u)
|
||||
if ((stagingId & 0xFFFFu) >= 0x0100u
|
||||
&& _staging._envCells.TryGetValue(
|
||||
stagingId,
|
||||
out EnvCell? cell))
|
||||
{
|
||||
_envCells.Add(pair);
|
||||
_stagingIds.Add(pair.Key);
|
||||
_envCells.Add(
|
||||
new KeyValuePair<uint, EnvCell>(stagingId, cell));
|
||||
_stagingIds.Add(stagingId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_enumerator.Dispose();
|
||||
_enumerator = _active._envCells.GetEnumerator();
|
||||
_phase = 1;
|
||||
return false;
|
||||
}
|
||||
if (_phase == 1)
|
||||
{
|
||||
if (_enumerator!.MoveNext())
|
||||
// O1: active-side removal capture through the ledger — no
|
||||
// cross-frame live enumerator over the active map.
|
||||
if (TryTakeNextPrefixKey(
|
||||
_active._collisionWorld.Current.EnvCellKeys,
|
||||
out uint activeId))
|
||||
{
|
||||
uint id = _enumerator.Current.Key;
|
||||
if ((id & 0xFFFF0000u) == _prefix
|
||||
&& (id & 0xFFFFu) >= 0x0100u
|
||||
&& !_stagingIds.Contains(id))
|
||||
if ((activeId & 0xFFFFu) >= 0x0100u
|
||||
&& !_stagingIds.Contains(activeId)
|
||||
&& _active._envCells.ContainsKey(activeId))
|
||||
{
|
||||
_removeIds.Add(id);
|
||||
_removeIds.Add(activeId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
_enumerator.Dispose();
|
||||
_enumerator = null;
|
||||
bool hasTerrain = _staging._terrain.TryGetValue(
|
||||
_prefix,
|
||||
out var terrain);
|
||||
|
|
@ -233,12 +259,36 @@ public sealed class CellGraph
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool TryTakeNextPrefixKey(PrefixKeyIndex ledger, out uint key)
|
||||
{
|
||||
if (!_keySlotsCaptured)
|
||||
{
|
||||
_keySlots = ledger.SlotsForPrefix(_prefix);
|
||||
_keySlotLimit = _keySlots?.Count ?? 0;
|
||||
_keySlotsCaptured = true;
|
||||
_cursor = 0;
|
||||
}
|
||||
while (_cursor < _keySlotLimit)
|
||||
{
|
||||
uint candidate = _keySlots![_cursor++];
|
||||
if (candidate != 0u)
|
||||
{
|
||||
key = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
key = 0u;
|
||||
_keySlots = null;
|
||||
_keySlotsCaptured = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal PreparedCellGraphLandblock? Prepared { get; private set; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_enumerator?.Dispose();
|
||||
_enumerator = null;
|
||||
_keySlots = null;
|
||||
_keySlotsCaptured = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue