Extend the non-drawing oracle through ordered PView routes, dispatcher visibility and final instance payloads, and accepted retail selection parts. Lifecycle artifacts can now referee the later shadow scene without influencing production visibility or draw decisions.
Use an explicit generous work profile in multi-operation behavior tests. Their contract is failure isolation and retirement ordering, not whether cold JIT work fits the production two-millisecond frame budget.
Capture deterministic, landblock-aware fingerprints from the accepted partition only during lifecycle automation. This gives the F/G shadow scene an independent oracle without changing normal draw decisions or adding disabled-path per-entity cost.
Record the exact physical-display evidence, ownership fixes, budget semantics, and performance deltas that close Slice E. Reconcile the architecture, roadmap, milestones, issues, documentation map, and session instructions before the explicit F/G approval gate.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Separate loaded spatial residency from the world presentation gate so destination live objects acquire mesh owners behind portal space while drawing and simulation stay quiesced. Prevent unowned CPU-cache hits from recreating stale GPU staging work.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Retain composite candidate progress across live membership generations and run a stable follow-up pass after churn, preventing ACE object-stream updates from resetting portal readiness forever.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Construct the retail-sized world voice ledger independently of OpenAL device availability so world reveal suspension is a safe no-op on machines without an audio backend.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Keep the original exception stack when cleanup succeeds so connected lifecycle failures identify the real packet/event handler instead of the session wrapper.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Record route-lifetime streaming overruns and maximum operation costs, require canonical reveal/resource convergence at every connected checkpoint, and keep recenter semantics independent of the production wall-clock budget.
Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
Join destination scheduling to the canonical reveal generation, protect its share across every typed frame-budget dimension, and prevent stale work from clearing a replacement reservation. Remove forced incomplete materialization and project retail's centered portal wait cue while the authored tunnel remains active.
Tests: Release build clean; 91 focused reservation/reveal tests; full solution 8,158 passed, 5 skipped.
Co-authored-by: Codex <noreply@openai.com>
Publish the retail blocking-for-cells edge before deferred recenter work, freeze old-world presentation/simulation/audio, and advance full-window retirement from exact metered entity and owner cursors. This removes synchronous portal teardown without allowing retained owners to remain observable.
Complete Slice D4 by adding aggregate lifecycle occupancy and traffic facts, validating physical source reports, and including decoded audio under the typed startup budget. Exercise every domain under forced pressure and retain the real cache/fence convergence gates.
Complete Slice D3 by replacing the unbounded animation dictionary with a concurrent byte/count LRU and by putting the three retail alpha scratch owners behind one typed aggregate budget. Preserve immediate growth and draw order while reclaiming one-frame density spikes after sustained under-use. Close stale bounds-cache issue evidence without inventing a cache.
Define Slice D's transition and concurrency contract, add generation-safe asset handles and owner leases, and parse the existing cache ceilings through typed runtime budgets. Stale generations cannot release or revive replacements, and worker observations are bounded and coalesced.
Co-authored-by: Codex <noreply@openai.com>
Introduce explicit loaded, missing, and corrupt outcomes over typed pak keys, validate package/DAT identity at open, add zero-I/O TOC probes, preserve cancellation, and route the package path through RuntimeOptions. Production injection follows in the next checkpoint.
Define the typed prepared-source contract, runtime identity rules, production injection and lifetime order, activation probe cleanup, and the exact equivalence/performance gates for MP1c.
Capture the authoritative capped, uncapped, and dense-Arwic routes on the physical AMD display path. Preserve the portal allocation, exception-storm, and repeated-Caul memory failures as MP1c's before-state while unblocking the prepared-asset cutover.
Starting dotnet-counters in the process-creation race can suspend the CLR before the graphical host creates its window. Wait for the guaranteed in-world boundary, then attach counters and contention tracing during the route's warm-up interval.
Co-Authored-By: Codex <noreply@openai.com>
Free-running sleeps could label a sample as the old scene exactly when automation began the next teleport. Give every route stop an explicit post-input liveness dwell and post-checkpoint hold, then capture process and frame facts only after its named canonical barrier.
Co-Authored-By: Codex <noreply@openai.com>
The first-player-position line is emitted only when initial streaming must recenter. Treating it as a required login event made a valid same-landblock login time out while route automation was already running. Gate on in-world and the route's authoritative materialization barriers instead.
Co-Authored-By: Codex <noreply@openai.com>
Correct whole-frame GPU timestamps so they bracket only the accepted render transaction and associate delayed query results with the owning CPU frame. Add route-wide frame-history summaries, fixed-camera screenshot comparison, process counters, contention traces, a pinned Arwic workload, and credential-safe launch disclosure.
The reference hardware/display contract now keeps local and RDP populations separate and defines the screenshot and re-baseline rules needed by later prepared-content gates.
Co-Authored-By: Codex <noreply@openai.com>
An adversarial performance review found our own instruments cannot
measure the project's own performance gates:
- FrameProfiler aggregated CPU/GPU/alloc/stage samples into ~5-second
windows and reset the ring buffers after each report, so route-wide
p50/p95/p99 distributions across a whole soak could not be
reconstructed after the fact. ACDREAM_FRAME_HISTORY=<path> now opts
into a separate per-frame history (one record per frame, ~72
bytes/record, accumulated in memory with zero frame-thread I/O) that
a shutdown-only Dispose() writes as CSV. The aggregated [frame-prof]
report format and its existing metrics are unchanged.
- The canonical checkpoint JSON tracked cache residency (entry/byte
counts) but never LOH size/fragmentation, process-wide allocated
bytes, or cache hit/miss/eviction traffic — a committed audit JSON
showed 65% LOH fragmentation that no tracked instrument recorded,
and "does a revisit portal hit or miss the caches" was unanswerable
from an artifact alone. WorldLifecycleResourceSnapshot now carries
loh_size_bytes/loh_fragmentation_bytes (GCMemoryInfo.GenerationInfo
index 3), process_total_allocated_bytes (GC.GetTotalAllocatedBytes),
and Interlocked hit/miss/eviction counters for the CPU mesh cache,
decoded-texture cache, and the four bounded DAT-object caches
(portal/cell/highRes/language, aggregated).
- run-connected-r6-soak.ps1 unconditionally forced
ACDREAM_UNCAPPED_RENDER=1 with no capped mode, while its sibling
lifecycle-gate script correctly gated it behind a switch. Added
-Uncapped (default capped, matching the sibling script's pattern),
fixed the stationary dwell (12s -> 26s, past the 25s
LiveEntityLivenessController deadline the adjacent comment already
cited), and now write an env-disclosure.json into the automation
artifact directory before every launch listing every ACDREAM_* var
the script sets plus -Uncapped, since the prior audit could only see
ACDREAM_DUMP_MOVE_TRUTH and nothing else was ever recorded anywhere.
Cache counters are wired via the existing composition path
(ObjectMeshManager already owns the CPU mesh cache and the mesh
extractor directly; content.Dats is threaded into
WorldLifecycleResourceSnapshotSource the same way every other
composition consumer receives it). The DAT-object cache lives behind
IDatReaderWriter, a third-party interface from the DatReaderWriter
package that cannot be extended; RuntimeDatCollection (the one
production implementation) exposes the aggregate stats directly and a
pattern match reads them, degrading to zero for any test double —
no new static registry was introduced (GpuMemoryTracker remains the
one precedented process-wide static).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 1da2c33c875b41fa383dd79694ee2765f0e21896)
Two audit-verified caches grew monotonically for process lifetime, which is
fatal for the 30-bot long-uptime headless-fleet goal:
- DatSoundCache._waves (Core) memoized every decoded PCM WaveData forever
in a bare ConcurrentDictionary — no LRU, no byte budget.
- OpenAlAudioEngine._bufferByWaveId (App) retained a native OpenAL buffer
copy of the same PCM per wave id until engine disposal — a second,
independent unbounded cache.
DatSoundCache now bounds payload residency with a 32 MiB byte-budget LRU
(decoded PCM waves run ~100-500 KB each, so this holds a comfortable
working set). Missing/unsupported-format waves are memoized separately in
an unbounded-but-cheap negative-result set (bounded by the finite Wave dat
id space) so they can never compete with or get evicted alongside payload
entries. Concurrent first-touch decodes of the same wave id are deduped
via a shared Lazy<T> so racing callers don't pay for WaveDecoder.Decode
twice. AcDream.Core cannot reference AcDream.Content (code-structure rule
2), so the LRU is a local reimplementation mirroring
BoundedDatObjectCache/DecodedTextureCache's shape rather than a shared
dependency.
OpenAlAudioEngine._bufferByWaveId now bounds native buffer residency with
a 48 MiB byte-budget LRU (AlBufferBudgetTracker), evicting least-recently-
used buffers once oversized. alDeleteBuffers fails on a buffer still
attached to a source, so eviction queries live AL per-source state
(GetSourceInteger.Buffer) rather than tracking a second, easily-stale
copy — several call sites (Play3DWave, PlayUiWave) set a source's buffer
directly. The buffer EnsureBuffer just created is explicitly protected
from its own eviction pass, since the caller hasn't attached it to a
source yet at that point. Evicted waves simply replay through
DatSoundCache -> EnsureBuffer on next use, identical to a first play.
Verified before implementing: AudioHookSink is the only GetWave caller
(single per-frame render-thread path per AnimationHookRouter's own
threading doc), and PcmBytes is read only at DatSoundCache.Admit (byte
accounting) and EnsureBuffer's first-upload branch — confirmed dead after
AL upload on the steady-state replay path, so bounding either cache
independently is correctness-safe; a cold replay after both evict simply
falls back to a full re-decode + re-upload, identical to a first play.
AlBufferBudgetTracker's eviction/budget decision is extracted as pure
logic (no AL dependency) specifically so it's unit-testable: the existing
OpenAlResourceLifetimeTests fake exposes a null AL, which short-circuits
every native buffer call before it runs, so the engine's actual AL wiring
isn't testable headless.
Tests: 9 new DatSoundCacheTests (Core.Tests) covering eviction order, byte
accounting, negative-result memoization, oversize-single-entry handling,
and concurrent-access smoke tests; 10 new AlBufferBudgetTrackerTests
(App.Tests) covering the pure LRU/budget/protection logic. Full suite:
3214/2 skip (Core.Tests), 3471/3 skip (App.Tests) plus one pre-existing,
unrelated failure (LandblockBuildOriginTests.FarLoad_..., reproduces
identically with these changes stashed out — landblock streaming, not
audio).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 76c880d35bcf30b50e3f7b4cb8635dc9ab9e3ec7)
Three small verified MeshExtractor.cs fixes from an allocation audit:
- CellStruct clip-map clone guard: the CellStruct polygon-surface path
cloned decoded texture data unconditionally before applying clip-map
transparency, even though the comment above it says "if we got this
from the cache, we need to clone it." The GfxObj path already had the
correct pattern (clone only when textureDataIsCached). Wired the same
textureDataIsCached flag through CellStruct's TryGet/GetOrCreate/
RetainOrUse call sites and gated the clone on it, matching GfxObj
exactly — freshly-decoded arrays (the common case) now skip a
redundant clone + BlockCopy.
- Solid-color texture cache: isSolid surfaces (Base1Solid / NoPos-
stippled polys) allocated a fresh 32x32 RGBA array (4 KiB) on every
polygon, even for repeated colors. Added a MeshExtractor-scoped
ConcurrentDictionary<uint, byte[]> keyed by packed ARGB, bounded by
distinct colors observed (a few hundred at most) rather than call
volume. Traced every downstream consumer of the solid-color array
(clip-map clone-guard, translucency clone-guard, GL upload, pak
serialization) — none currently mutate a solid-color array in place,
since isSolid is mutually exclusive with the texture-decode branch
those guards live in — but wired textureDataIsCached = true at both
call sites anyway so the existing clone-before-mutate machinery
protects the shared array if that ever changes.
- Dropped the redundant .ToList() on both _dats.ResolveId(...) calls:
DatCollectionAdapter.ResolveId already returns a materialized List<T>
typed as IEnumerable; the immediate .OrderByDescending().FirstOrDefault()
enumerates once, so the extra copy was pure waste.
Added SolidColorTextureCacheTests (dat-gated, matching suite convention)
exercising the new cache directly via internal visibility.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit e9f9d7539966cc26343e45d653a751c5d1045810)
ParticleBatcher/ParticleEmitterRenderer/ActiveParticleEmitter (src/AcDream.App/Rendering/Wb/)
are an earlier WorldBuilder-derived particle-preview design with zero live call
sites: ParticleBatcher.Begin/AddParticle/Flush/End are only called from
ParticleEmitterRenderer.Render, which is only called from
ActiveParticleEmitter.Render, and `new ActiveParticleEmitter` has zero call
sites anywhere in the repo (verified by grep across src/tests/tools). The only
wiring was OpenGLGraphicsDevice.ParticleBatcher (property + null-init + Dispose)
and a single assignment in WbMeshAdapter.cs.
ParticleBatcher's constructor unconditionally allocated a ParticleInstance[65536]
managed array (~3.5 MiB) plus a matching GPU instance buffer, a shader, a VAO,
and 3 more GL buffers, none tracked by GpuMemoryTracker or ever drawn from.
ParticleEmitterRenderer's per-particle-type physics (Particle::Init vector-space
resolution, CalculatePosition integration) duplicates, without retail address
citations, what src/AcDream.Core/Vfx/ParticleSystem.cs already implements with
Particle::Init (0x0051c930) / Particle::Update (0x0051c290) citations and the
same ParticleType switch — the production path (ParticleSystem.cs +
AcDream.App/Rendering/ParticleRenderer.cs) supersedes it. No unique retail
knowledge is lost.
EmbeddedResourceReader.cs and the wb_particle.vert/frag shader sources are
deleted alongside it: EmbeddedResourceReader's own doc comment says it exists
solely so ParticleBatcher/ParticleEmitterRenderer can load WB-style shader
resource names, and GetEmbeddedResource had no other caller in the repo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 4afedafd085a5fadd1867418d3ec71610d9d15a7)
WorldSession.NetReceiveLoop wrapped its entire while loop in a single
try/catch, so ANY non-timeout SocketException permanently killed the
background receive thread: the catch block at the loop's end was empty
(misattributed the error to "socket closed during shutdown"), and the
finally called _inboundQueue.Writer.TryComplete(), which silently and
irrecoverably stopped all inbound processing for the rest of the
session — no log line, no recovery path, and LiveSessionHost.Reconnect
has zero production callers to notice.
The realistic trigger is a well-known Windows UdpClient quirk: an ICMP
"port unreachable" reply to an EARLIER Send (e.g. against a stale ACE
session that already tore down its socket) surfaces as a
WSAECONNRESET SocketException on this socket's NEXT, completely
unrelated Receive call. NetClient.Receive already swallows the
expected SocketError.TimedOut heartbeat case; anything else reaching
WorldSession was a real, transient, per-datagram error being treated
as session-fatal.
Three changes, root-cause not a band-aid:
- NetClient's constructor now disables SIO_UDP_CONNRESET reporting on
Windows, so a delayed ICMP error can't poison receives at all.
- NetReceiveLoop now catches SocketException PER ITERATION, logs it,
and continues polling instead of exiting. The existing clean-shutdown
paths (cancellation, ObjectDisposedException during Dispose) are
unchanged — only the non-timeout-socket-error case that used to kill
the loop is now recoverable.
- NetClient.Receive no longer calls the ReceiveTimeout setter (a
setsockopt syscall) on every single call — only when the requested
timeout differs from the last-applied value, cached in a new field.
This was an unrelated but adjacent finding (4x/sec syscall churn at
the 250ms heartbeat cadence) in the same audit.
No change to outbound wire behavior, ack cadence, heartbeat interval,
or datagram ordering — this is purely receive-loop resilience.
Tests: NetClientTests gained a SIO_UDP_CONNRESET construction smoke
test and two ReceiveTimeout-caching tests. A new
WorldSessionNetReceiveLoopResilienceTests drives the actual private
NetReceiveLoop method (via the existing internal
IWorldSessionTransport seam + reflection) with a scripted transport
that throws a non-timeout SocketException on the first call, proving
the loop survives it and keeps enqueueing subsequent datagrams — fully
deterministic, no real sockets. Full solution suite green: 3204/3206
Core, 3462/3465 App, 552/552 Core.Net (skips pre-existing).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit c72ce028a927e15b8a54a86bbd0661a723dc84ca)