From 8e0c6fb145e923d68c87778618608fe6c7137d7b Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 4 Sep 2026 13:34:52 +0200 Subject: [PATCH] docs(overhaul): bound S4 c3a retail fix round --- .../s4-depth-alpha-packet.md | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md b/docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md index f644c24d..36687326 100644 --- a/docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md +++ b/docs/research/2026-09-01-overhaul/s4-depth-alpha-packet.md @@ -1432,3 +1432,117 @@ before the return matrix): 10. Allocate one object in the real cell-merge path: `ProductionCellObjectParticleMerge_WarmedPathAllocatesZeroBytes` first 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-2–5 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-2–5 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` 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.