docs(overhaul): bound S4 c3a retail fix round

This commit is contained in:
Erik 2026-09-04 13:34:52 +02:00
parent a86ec73ece
commit 8e0c6fb145

View file

@ -1432,3 +1432,117 @@ before the return matrix):
10. Allocate one object in the real cell-merge path: 10. Allocate one object in the real cell-merge path:
`ProductionCellObjectParticleMerge_WarmedPathAllocatesZeroBytes` first `ProductionCellObjectParticleMerge_WarmedPathAllocatesZeroBytes` first
expected 0, actual 3,072 managed bytes. expected 0, actual 3,072 managed bytes.
## 19. S4-c3a fix round 1 — authored particle CYpt and bounded preparation (lead, 2026-09-04)
The first retail-faithfulness lens returned **FAIL** on candidate
`44e2bc227ba74d10074cfe985eee094e10b7f438`. Ordinary object parts are
correct, but the billboard/mode-25 particle path orders by its rendered quad
center. That center is the vertex-AABB-derived `ParticleGfxInfo.CenterOffset`
after viewer-facing adjustment, not the GfxObj's authored `sort_center`.
Retail does not use the rendered center for CYpt:
`CPhysicsPart::UpdateViewerDistance @ 0x0050E030` reads the current GfxObj's
`sort_center` at offsets `+0x7C/+0x80/+0x84`, scales it, passes it with the
part `Position` to `Position::get_offset`, computes the length, and writes
CYpt. The paired bytes at `0x0050E036..0x0050E0AE` confirm that named-decomp
sequence. The same lens also found the stale statement that static records
are never re-sorted downstream.
The lead's pre-production audit found two coupled safety holes in the new
prepared-particle seam. `_preparedCellAlphaScratch` is a new retained list but
is absent from the alpha scratch accounting/retention bound. More importantly,
`PrepareForCellAlpha` reserves `_deferredAlpha` payload tokens before the
driver reaches the queue visibility edge. An exception from a later immediate
duplicate can strand unregistered source payload; ordinary capacity rejection
retains payload for entries that can never draw. These contradict §18.3's
source-owned rollback requirement. They are included in this same bounded
round rather than deferred to a predictable production-lens failure.
### 19.1 F1 — one authored particle sort point, separate from its draw center
Retain `GfxObj.SortCenter` explicitly in `ParticleGfxInfo`. Populate it
directly in `AuthoredParticleGfxInfo`; synthetic texture-only/default
billboards, which have no GfxObj, use zero. Do not derive it from vertex bounds
and do not replace or alter the visual `CenterOffset`.
For every billboard/mode-25 particle, compute the CYpt point before any
viewer-facing draw-frame adjustment:
`particle.Position + Transform(authoredSortCenter * particle.Size, ParticleOrientation)`.
Use the squared viewer distance from that point for `ParticleInstance` and
`ParticleSubmission`. The rendered quad position/axes remain byte-for-byte
the existing `CenterOffset`/facing result. The full-mesh path already applies
the equivalent `ObjectRenderData.SortCenter` through its model; keep the two
paths semantically identical and do not add a second sort.
Add a real GfxObj-backed billboard test whose authored SortCenter is distinct
from both its particle origin and vertex AABB center, with non-unit size and a
non-identity orientation. Drive the actual cell particle preparation and
driver merge against ordinary object alpha. It must prove that the authored
point determines the cross-source order while the captured billboard draw
center remains the existing visual center. Required mutations: using rendered
`pos`, using the AABB center, dropping size, or dropping orientation must each
fail a named first assertion.
### 19.2 F2 — reserve only at the queue visibility edge and roll back rejection
A prepared particle record retains only the information needed to reconstruct
its source payload when `Append` is called; preparation must not mutate
`_deferredAlpha`. At `Append`, reserve one tail token, call
`RetailAlphaQueue.TryAppend`, and roll back that exact tail token when the
append returns false or throws. A rejected first-use source must remain
registered by the queue and receive one flush/end/abort reset, but it owns no
rejected payload and never prepares or draws it. Accepted token indices remain
stable. An exception from a later row-5 or clip-immediate draw during
preparation must leave the source payload count unchanged.
Bound `_preparedCellAlphaScratch` by the physical two-list limit: retain no
more than the first 3,000 delayed candidates for CLIP and the first 3,000 for
ALPHA. Later candidates for a list cannot be admitted even in an otherwise
empty frame, so dropping only their prepared records is behavior-equivalent;
their immediate duplicate, if any, still executes. Include this list in an
explicit retained-byte/capacity diagnostic or an equivalent dedicated bound.
After a rejection storm its count is zero at reset/next preparation and its
retained capacity is no greater than the bounded geometric backing required
for 6,000 records (8,192 with `List<T>` growth). Do not change either queue's
3,000-entry capacity, FIFO append order, source registration, CLIP-then-ALPHA
drain, router decisions, or row-5 immediate state.
Pins cover: more than 3,000 candidates for each list; accepted/rejected token
counts; no rejected prepare/draw; flush, end, and abort cleanup; an injected
immediate-draw exception after at least one delayed candidate; bounded count,
capacity, and retained bytes; and the real warmed driver/particle/dispatcher
merge at 0 managed bytes. Mutations restoring eager reservation, omitting
false/exception rollback, or removing the per-list preparation cap must fail
at their first named count/capacity assertion.
### 19.3 F3 — prose and register truth
Correct `WalkFrameDriver.cs`'s `WalkFrameStaticRecords` comment: records retain
authored traversal/registration order until the ordinary-cell populator builds
the combined static/dynamic part list and performs the retail per-cell CYpt
sort. They are not final stream order. After F1, AP-241's positive authored-
part-order claim becomes true; AP-241/AP-242/AP-243 otherwise remain unchanged,
unique, inside the AP table, and the active physical count remains 162. No new
deviation is introduced by this exact repair.
### 19.4 Scope, return, and review order
Production edits are limited to `ParticleRenderer.cs`,
`ParticleSubmissionOrdering.cs`, and the comment-only correction in
`Walk/WalkFrameDriver.cs`. Tests may change only
`ParticleRendererRouteTests.cs` and `Walk/WalkFrameDriverTests.cs`; this packet
records the result. If the implementer proves one additional existing test
helper is the minimum real-path fixture seam, the lead must approve and record
it before commit. No router, queue, pipeline, shader, manifest, Content model,
serializer, package format, building-shell, EnvCell, or PView behavior changes.
Return one fix commit on top of `44e2bc227b`: `git diff --check`; Release 0W/0E;
the focused particle/driver/queue/order/bounds suite; the real 0-B pin; shader
contract lane; and a mutation ledger naming each actual first failure. No
graphical launch. The retail lens re-reviews F1, the paired bytes, F3, and the
unchanged S4-c2 behavior first. Only a retail PASS unlocks the sequential
production/gate-honesty lens over the entire c3a stack, with special attention
to F2. A failed re-review gets the one remaining bounded fix round; a further
finding after that would require a third round and stops the chunk.