refactor(rendering): prepare retained particle routing
Derive attached particle owners from the exact retained PView route stream with a zero-allocation warm path. Correct G4 gate provenance, record the current production-only RDP sample, and keep G5 old-route deletion explicitly blocked until the reapplied retained cutover passes its connected visual gate.
This commit is contained in:
parent
20f9fadb12
commit
c730632075
7 changed files with 374 additions and 20 deletions
|
|
@ -1632,10 +1632,13 @@ port in any phase — no separate listing here.
|
|||
> shadow referee, production cutover, and acceptance matrix. The user
|
||||
> explicitly approved Slices F–L, including the F/G and J gates, on
|
||||
> 2026-07-24. Slice F and G0–G4 are implemented; 46,599 automated G4
|
||||
> comparisons have zero mismatches. The 2026-07-25 connected visual gate
|
||||
> accepted the retained scene after independent portal scheduling/private-view/
|
||||
> ACE-revisit ownership regressions were corrected; G5 old-path deletion and
|
||||
> production-only profiling are now active. The historical exact cutover
|
||||
> comparisons have zero mismatches. The first retained-path visual gate exposed
|
||||
> independent portal scheduling/private-view/ACE-revisit ownership regressions.
|
||||
> Their correction passed visually while the draw cutover was temporarily
|
||||
> reverted, which proves the correction but not the retained source.
|
||||
> `20f9fadb` reapplies the cutover over those fixes; its connected portal/revisit
|
||||
> re-gate is pending before G5 old-path deletion and production-only profiling.
|
||||
> The historical exact cutover
|
||||
> rollback remains
|
||||
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
||||
> retained UI/frame work, exact bounded light selection, and
|
||||
|
|
|
|||
|
|
@ -108,10 +108,11 @@ publication, retirement, upload, warmup, or resource backlog. Capped/uncapped
|
|||
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
||||
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
||||
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
||||
G0–G4 are implemented; G4's automated gate has zero mismatches and its
|
||||
2026-07-25 connected visual gate passes after the portal scheduling/private
|
||||
viewport/ACE revisit regression correction. G5 deletion and production-only
|
||||
profiling are active. Slice H is
|
||||
G0–G4 are implemented and G4's automated gate has zero mismatches. The portal
|
||||
scheduling/private-viewport/ACE-revisit correction passed visually while the
|
||||
retained draw cutover was temporarily reverted, so `20f9fadb` reapplies the
|
||||
cutover over those fixes for the pending connected retained-path re-gate. G5
|
||||
deletion and production-only profiling remain gated on that pass. Slice H is
|
||||
complete: retained UI/frame work, exact bounded light selection, and
|
||||
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
|
||||
**Date:** 2026-07-24
|
||||
|
||||
**Status:** approved 2026-07-24; Slice F and G4 complete, G5 active
|
||||
**Status:** approved 2026-07-24; Slice F complete, G4 retained-path re-gate
|
||||
active, G5 prepared but not unblocked
|
||||
|
||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||
|
||||
|
|
@ -27,8 +28,8 @@ retail-faithful gameplay
|
|||
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
||||
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
||||
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
||||
| G4 — production cutover | complete | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The 2026-07-25 connected visual pass accepted the retained scene after its independent portal scheduling/ownership regressions were corrected. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||
| G5 | active | The accepted visual gate unblocks production-only performance/lifecycle closeout and old-route deletion. |
|
||||
| G4 — production cutover | re-gate active | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The portal correction was visually accepted while the cutover was temporarily reverted by `2c848d41`; that proves the correction but cannot accept the retained draw source. Commit `20f9fadb` reapplies the cutover over the correction and is the current connected visual re-gate. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||
| G5 | prepared | Old-route deletion and the production-only reference matrix remain blocked only on the connected `20f9fadb` retained-path re-gate. An allocation-free retained-route particle-owner selector and steady RDP sample are prepared without changing the running gate binary. |
|
||||
|
||||
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
||||
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
||||
|
|
@ -646,12 +647,15 @@ the scene journal was empty, source and binary hashes matched, and every
|
|||
process closed gracefully. The gate corrected whole-retained-world composite
|
||||
warmup discovery by sourcing the exact published 3×3 destination neighborhood;
|
||||
that correction is draw-path independent. Fixed-camera artifacts are captured.
|
||||
The user completed the connected visual gate on 2026-07-25. The retained scene
|
||||
itself remained exact; the pass exposed independent portal scheduling, private
|
||||
viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
||||
The first connected retained-path gate exposed independent portal scheduling,
|
||||
private-viewport ownership, and ACE revisit-liveness defects. Their root-cause
|
||||
correction is recorded in
|
||||
`docs/research/2026-07-25-portal-regression-closeout.md`. G4 is accepted and
|
||||
old-route deletion plus G5 production-only profiling are unblocked.
|
||||
`docs/research/2026-07-25-portal-regression-closeout.md`. The user's subsequent
|
||||
pass occurred while exact cutover `ef1d2633` was temporarily reverted by
|
||||
`2c848d41`, so it proves those corrections but not the retained draw source.
|
||||
`20f9fadb` reapplies the cutover over the accepted corrections. G4 acceptance,
|
||||
old-route deletion, and the G5 production-only matrix remain gated on the
|
||||
connected retained-path recheck.
|
||||
|
||||
### G5 — Physical performance and lifecycle gate
|
||||
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ The first connected retained-scene visual pass reported:
|
|||
4. doors, signs, portals, NPCs, and other server-spawned objects disappeared
|
||||
after repeated portal travel.
|
||||
|
||||
This note records the root causes and the accepted correction. It does not
|
||||
reduce world radius, texture quality, particles, readiness, or retail
|
||||
presentation.
|
||||
This note records the root causes and the correction. It does not reduce world
|
||||
radius, texture quality, particles, readiness, or retail presentation. The
|
||||
correction's visual pass occurred on the temporary old-draw rollback; the
|
||||
retained-source re-gate is tracked separately below.
|
||||
|
||||
## Retail and protocol anchors
|
||||
|
||||
|
|
@ -99,7 +100,11 @@ This ACE adaptation remains explicit in divergence AP-69.
|
|||
- Complete App Release suite: 3,813 passed / 3 skipped.
|
||||
- Release solution build: green with zero warnings.
|
||||
- Complete Release solution suite: 8,322 passed / 5 skipped.
|
||||
- User connected visual gate:
|
||||
- User connected correction gate on rollback route (`2c848d41`):
|
||||
- paperdoll remains visible;
|
||||
- portal transit is materially faster;
|
||||
- server-spawned objects remain after repeated travel.
|
||||
- Retained production source:
|
||||
- `20f9fadb` reapplies exact cutover `ef1d2633` over the correction;
|
||||
- complete Release suite: 8,326 passed / 5 skipped;
|
||||
- connected portal/revisit visual re-gate pending before G4 acceptance.
|
||||
|
|
|
|||
43
docs/research/2026-07-25-slice-g5-production-profile.md
Normal file
43
docs/research/2026-07-25-slice-g5-production-profile.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# Slice G5 production profile — 2026-07-25
|
||||
|
||||
## Status
|
||||
|
||||
Work in progress. The retained-scene cutover is running at `20f9fadb`, but G5
|
||||
old-route deletion remains blocked on its connected portal/revisit visual
|
||||
re-gate. This note records production-only evidence gathered without enabling
|
||||
the referee or lifecycle automation.
|
||||
|
||||
## First steady RDP sample
|
||||
|
||||
The client was idle in-world at 60 Hz with the normal capped Release
|
||||
presentation, High quality, a 4/12 near/far radius, and no developer or
|
||||
automation UI. The title reported 173/625 landblocks, 10,383 entities, and 51
|
||||
active animations. Five seconds of process CPU plus three one-second Windows
|
||||
GPU counter samples measured:
|
||||
|
||||
| Metric | Value |
|
||||
|---|---:|
|
||||
| CPU | 2.68% of 16 logical processors, about 42.9% of one logical core |
|
||||
| Working set | 255.8 MiB |
|
||||
| Private bytes | 1,193.9 MiB |
|
||||
| Threads / handles | 26 / 663 |
|
||||
| GPU 3-D engine | 55.74% average, 56.26% maximum |
|
||||
| Dedicated GPU process memory | 422 MiB |
|
||||
| Shared GPU process memory | 141 MiB |
|
||||
|
||||
This is an RDP presentation sample, not the required physical-local reference
|
||||
matrix and not evidence of a plateau across travel. It establishes that the
|
||||
post-cutover retained path is active and gives G5 a pre-deletion production
|
||||
point. The complete capped/uncapped nine-stop, dense-Arwic, lifecycle,
|
||||
allocation, GC, stage-timing, and screenshot/instance-set matrix remains
|
||||
mandatory after the visual re-gate.
|
||||
|
||||
## Prepared old-route dependency removal
|
||||
|
||||
`RenderFrameRouteOwnerSelector` derives attached particle owner IDs from the
|
||||
exact retained PView route ranges. It supports exact-route replace/union and
|
||||
all-range exclusion without allocation after warmup. This removes the final
|
||||
particle-routing reason for normal production to build
|
||||
`InteriorEntityPartition`; it is not wired into the running visual-gate binary.
|
||||
Four focused tests and the 3,821-pass / 3-skip App Release suite are green
|
||||
through an isolated output path.
|
||||
120
src/AcDream.App/Rendering/Scene/RenderFrameRouteOwnerSelector.cs
Normal file
120
src/AcDream.App/Rendering/Scene/RenderFrameRouteOwnerSelector.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
/// <summary>
|
||||
/// Projects the retained PView route stream into particle-owner identities.
|
||||
/// This keeps attached-effect routing on the same visibility decision as mesh
|
||||
/// submission without walking spatial/gameplay entities a second time.
|
||||
/// </summary>
|
||||
internal static class RenderFrameRouteOwnerSelector
|
||||
{
|
||||
public static void Replace(
|
||||
HashSet<uint> destination,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
destination.Clear();
|
||||
Union(
|
||||
destination,
|
||||
in view,
|
||||
route,
|
||||
routeIndex,
|
||||
cellId);
|
||||
}
|
||||
|
||||
public static void Union(
|
||||
HashSet<uint> destination,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
VisitExact(
|
||||
destination,
|
||||
in view,
|
||||
route,
|
||||
routeIndex,
|
||||
cellId,
|
||||
remove: false);
|
||||
}
|
||||
|
||||
public static void ExceptRoute(
|
||||
HashSet<uint> destination,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(destination);
|
||||
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
ReadOnlySpan<RenderProjectionRecord> candidates =
|
||||
view.RouteCandidates;
|
||||
for (int index = 0; index < ranges.Length; index++)
|
||||
{
|
||||
RenderFrameCandidateRange range = ranges[index];
|
||||
if (range.Route != route)
|
||||
continue;
|
||||
|
||||
ApplyRange(
|
||||
destination,
|
||||
candidates,
|
||||
in range,
|
||||
remove: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void VisitExact(
|
||||
HashSet<uint> destination,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
bool remove)
|
||||
{
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
ReadOnlySpan<RenderProjectionRecord> candidates =
|
||||
view.RouteCandidates;
|
||||
for (int index = 0; index < ranges.Length; index++)
|
||||
{
|
||||
RenderFrameCandidateRange range = ranges[index];
|
||||
if (range.Route != route
|
||||
|| range.RouteIndex != routeIndex
|
||||
|| range.CellId != cellId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ApplyRange(
|
||||
destination,
|
||||
candidates,
|
||||
in range,
|
||||
remove);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyRange(
|
||||
HashSet<uint> destination,
|
||||
ReadOnlySpan<RenderProjectionRecord> candidates,
|
||||
in RenderFrameCandidateRange range,
|
||||
bool remove)
|
||||
{
|
||||
ReadOnlySpan<RenderProjectionRecord> owners =
|
||||
candidates.Slice(range.Offset, range.Count);
|
||||
for (int index = 0; index < owners.Length; index++)
|
||||
{
|
||||
uint localEntityId = owners[index].Source.LocalEntityId;
|
||||
if (localEntityId == 0)
|
||||
continue;
|
||||
|
||||
if (remove)
|
||||
destination.Remove(localEntityId);
|
||||
else
|
||||
destination.Add(localEntityId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
using AcDream.App.Rendering.Scene;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class RenderFrameRouteOwnerSelectorTests
|
||||
{
|
||||
private static readonly RenderSceneGeneration Generation =
|
||||
RenderSceneGeneration.FromRaw(1);
|
||||
|
||||
[Fact]
|
||||
public void ReplaceAndUnionUseExactRouteIdentityAndDeduplicateOwners()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint> { 999 };
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.Union(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
|
||||
Assert.Equal([10u, 20u, 30u], owners.Order());
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptRouteRemovesOwnersAcrossEveryRangeOfThatRoute()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint>();
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||
|
||||
Assert.Equal([50u], owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingRouteClearsOnReplaceAndDoesNotMutateOnUnion()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint> { 10, 20 };
|
||||
|
||||
RenderFrameRouteOwnerSelector.Union(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex: 9,
|
||||
cellId: 0x01010009);
|
||||
Assert.Equal([10u, 20u], owners.Order());
|
||||
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
routeIndex: 9,
|
||||
cellId: 0x01010009);
|
||||
Assert.Empty(owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WarmSelectionAllocatesNothing()
|
||||
{
|
||||
var exchange = new RenderFrameExchange();
|
||||
PublishRoutes(exchange, sequence: 1);
|
||||
RenderFrameView view = exchange.BorrowLatest(Generation, 1);
|
||||
var owners = new HashSet<uint>();
|
||||
owners.EnsureCapacity(16);
|
||||
|
||||
SelectOwners(owners, in view);
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int iteration = 0; iteration < 1_000; iteration++)
|
||||
SelectOwners(owners, in view);
|
||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
Assert.Equal([50u], owners);
|
||||
exchange.Release(in view);
|
||||
}
|
||||
|
||||
private static void SelectOwners(
|
||||
HashSet<uint> owners,
|
||||
in RenderFrameView view)
|
||||
{
|
||||
RenderFrameRouteOwnerSelector.Replace(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
routeIndex: 0,
|
||||
cellId: 0);
|
||||
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||
owners,
|
||||
in view,
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||
}
|
||||
|
||||
private static void PublishRoutes(
|
||||
RenderFrameExchange exchange,
|
||||
ulong sequence)
|
||||
{
|
||||
RenderProjectionRecord owner10 = Projection(1, 10);
|
||||
RenderProjectionRecord owner20 = Projection(2, 20);
|
||||
RenderProjectionRecord owner30 = Projection(3, 30);
|
||||
RenderProjectionRecord owner40 = Projection(4, 40);
|
||||
RenderProjectionRecord owner50 = Projection(5, 50);
|
||||
RenderProjectionRecord anonymous = Projection(6, 0);
|
||||
RenderFrameWriter writer =
|
||||
exchange.BeginBuild(Generation, sequence);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||
0,
|
||||
0,
|
||||
[owner10, owner20, anonymous]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
0,
|
||||
0,
|
||||
[owner20, owner30]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||
1,
|
||||
0,
|
||||
[owner40]);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.DynamicLast,
|
||||
0,
|
||||
0,
|
||||
[owner20, owner40, owner50]);
|
||||
writer.SetSourceDigest(
|
||||
new RenderSceneDigest(
|
||||
Generation,
|
||||
default,
|
||||
default));
|
||||
writer.Publish();
|
||||
}
|
||||
|
||||
private static RenderProjectionRecord Projection(
|
||||
ulong projectionId,
|
||||
uint localEntityId) =>
|
||||
new()
|
||||
{
|
||||
Id = RenderProjectionId.FromRaw(projectionId),
|
||||
OwnerIncarnation = RenderOwnerIncarnation.FromRaw(1),
|
||||
Source = new RenderSourceMetadata(
|
||||
LocalEntityId: localEntityId,
|
||||
ServerGuid: 0,
|
||||
SourceId: 0,
|
||||
ParentCellId: 0,
|
||||
EffectCellId: 0,
|
||||
BuildingShellAnchorCellId: 0,
|
||||
TransformFingerprint: default,
|
||||
GeometryFingerprint: default,
|
||||
AppearanceFingerprint: default),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue