Opus dual-lens review of the three VM6 commits (0930c35d,39e8408c,6cc5e183) found two blockers and two should-fix issues; all landed here along with the review's nits and documentation corrections. Blockers: - A1: the procedural-scenery classifier tested bit 31 alone instead of the full top nibble (0xF000_0000 == 0x8000_0000), so it also matched LandblockStaticEntityIdAllocator's 0xC... namespace (fences/gates/ building shells with a cutout subset), the 0xDA11_D0xx paperdoll id, and the 0xFFFF_FF01 portal-tunnel id as procedural scenery — all three would have swayed. ProceduralSceneryIdAllocator.IsInNamespace now does the exact top-nibble test; FoliageWindClassification delegates to it. - A2: GroupKey (the receiver's instance-batching key) did not carry FoliageFlags while the caster's dedup key already did, so a scenery instance and a non-scenery instance sharing a mesh subset coalesced into one receiver InstanceGroup whose flags were last-writer-wins — disagreeing with the correctly-keyed caster. GroupKey now carries FoliageFlags, computed before key construction and set exactly once at group creation; the imperative re-stamp is gone, and CachedBatch's now-redundant FoliageFlags field is removed. Should-fix: - A3: the world receiver pass bound UniformAtmosphericFrame only by accident (leftover from the caster pass, which runs first each frame, since Vulkan binding state isn't reset between passes). DirectionalShadowFrameBinding now carries the caster's exact AtmosphericFrameBufferBinding and BindDirectionalShadowReceiver binds it explicitly. - A4: a Setup-composed tree's opaque trunk part never got the trunk flag because HasCutoutSubset is cached per GfxObj part, not per entity. FoliageWindClassification.ComputeEntityHasCutoutSubset now ORs HasCutoutSubset across an entity's resolved sibling parts once per entity, threaded into ClassifyBatches/AddDirectionalShadowBatches via a new optional override parameter. Nits: A5 hashes the per-vertex flutter seed relative to the instance origin instead of absolute world XY (fp32 sin() precision loss at far landblock corners), mirrored in both foliage_wind.glsl and FoliageWindModel; A7 documents the max(maxHeight, 0.5) divide-guard as a deliberate pseudocode divergence; A8 switches FoliageWindExclusions' construction to ToFrozenSet() and softens the "never stale" doc comment to "no slower than one frame behind." Tests added: top-nibble classification (0xFFFFFFFFu now correctly false), GroupKey inequality across entity-driven scenery/landblock- static classification, a caster-batch test proving the same pairing never coalesces, ComputeEntityHasCutoutSubset unit + end-to-end two-part-Setup tests, the caster→receiver AtmosphericFrame binding carry-through, flutter-hash translation invariance relative to instance origin, and a Storm-wind mid-height displacement floor guarding against a "no motion" regression. Docs: plan VM6 body corrected to the five-row WeatherKind table, "bits 1 and 2", "all four" caster shaders, and top-nibble wording throughout; the owner gate checklist's Rain/Storm step; the stale v1-only shader- interface compatibility entry; semantic-bindings-v1.md's v2 members folded into the main 192-byte block; the IA-25 register row's top- nibble wording; AtmosphericFrameInputs.cs's ABI size reference. foliage_wind.glsl's A5 change recompiled exactly the five shaders that include it (mesh_atmospheric.vert, the four directional_shadow_world_* casters) plus the manifest; no other .spv changed. Verify: Release build 0 warnings/0 errors. App hermetic-lane filter 6,041/0 failed (no environment-specific failures this run). RenderPackValidator 30/30. Full hermetic-filtered solution: 15,269/0 failed across 15 projects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
134 lines
8.5 KiB
Markdown
134 lines
8.5 KiB
Markdown
# Render-pack compatibility and failure handling v1
|
|
|
|
**Campaign phase id:** **TBD**
|
|
|
|
Compatibility is a declaration and activation result, not a promise inferred
|
|
from a GPU brand. The client keeps unsupported packs visible with one exact
|
|
reason, refuses to select an unavailable preset, and continues rendering the
|
|
authoritative acdream default (retail-faithful) path.
|
|
|
|
## Author responsibilities
|
|
|
|
- Declare every mandatory facility in `RequiredCapabilities`. Use
|
|
`OptionalCapabilities` only when the pack has a deterministic path that does
|
|
not need it.
|
|
- Gate each preset independently. Low must remain semantically correct; lower
|
|
shadow resolution or reach rather than silently removing trees, monsters,
|
|
players, buildings, alpha cutouts, or animated transforms.
|
|
- Keep resource estimates conservative and below the preset and 256 MiB SDK
|
|
ceilings. The host clamps dimensions and bytes before allocation. Its
|
|
optional-pack memory policy admits at most one eighth of the selected
|
|
adapter's probed device-local heap, capped at 256 MiB resident and 512 MiB
|
|
transient multisample storage; the lower value wins and is printed in an
|
|
unavailable-preset reason.
|
|
- Use only declared hooks, semantic inputs, resources, scene replays and base
|
|
pipeline variants. Pack code receives no arbitrary per-frame callback,
|
|
command buffer, gameplay owner, RHI object, or Vulkan handle.
|
|
- Treat registration as pure declaration publication. `OpenRead` must return a
|
|
new readable stream for the exact requested key and must not retain a world
|
|
generation or borrowed frame state.
|
|
- Ship SPIR-V words little-endian, four-byte aligned, no larger than 16 MiB per
|
|
asset, and compatible with the published v1 semantic binding ABI. Both the
|
|
SDK and client validate the binary stage, `main` entry point, descriptor
|
|
allowlist, exact uniform/push layouts, and read-only storage contract before
|
|
pipeline creation. Vertex and fragment keys are independent logical keys;
|
|
the selected candidate copies their blobs and never resolves them through
|
|
the host shader directory.
|
|
- Declare no more than 64 settings and keep their descriptor order stable. The
|
|
set-3/binding-8 shader mapping is positional: a persisted user override wins
|
|
the selected-preset override, which wins the declaration default. Boolean
|
|
becomes 0/1, Choice becomes its zero-based choice index, numeric strings use
|
|
invariant culture, and unused or defensively invalid slots are zero. The
|
|
selected descriptor validates every user string against kind, range, step,
|
|
and choices before activation.
|
|
|
|
## Client transaction
|
|
|
|
1. Discover the manifest and descriptor without opening assets or constructing
|
|
GPU objects.
|
|
2. Compare required capabilities and preset ceilings with the active physical
|
|
device's probed `maxImageDimension2D`, `maxImageArrayLayers`, device-local
|
|
heap bytes, and format/timestamp support. An unsupported pack remains
|
|
installed and its individual presets remain visible with exact
|
|
needed-versus-provided reasons.
|
|
3. After explicit selection, validate every referenced asset and shader
|
|
interface, then build every resource and pipeline in an isolated candidate.
|
|
4. Activate the complete candidate at a stable frame boundary. Until that
|
|
point retail keeps rendering.
|
|
5. If any step fails, retire the candidate through normal GPU-flight fences,
|
|
record one stable diagnostic, select `acdream default`, and do not retry
|
|
that pack again during the session.
|
|
|
|
No half-enabled graph is valid. A missing bloom shader does not leave shadows
|
|
active; a failed shadow pipeline does not leave a world-colour intermediate or
|
|
stale descriptor alive.
|
|
|
|
Auto is a logical selector rather than an allocated preset. It requires
|
|
asynchronous GPU timestamps, starts at Medium when Medium fits, otherwise
|
|
starts and stays at Low, and never promotes beyond the highest contiguous
|
|
compatible preset. If Low itself cannot fit, Auto fails safely to Retail and
|
|
reports the Low limit that failed.
|
|
|
|
Runtime Auto decisions use the active preset's declared inclusive-GPU p99,
|
|
pack-added CPU p99, and resident-GPU budgets. An over-budget Medium selection
|
|
can step down to Low; if Low then remains over any declared limit for 180
|
|
stable samples, the host atomically deactivates the complete pack, reports the
|
|
measured and budget values, and enters `FailedToRetail` without a retry loop.
|
|
This performance fallback is Auto-only. Explicit Low remains selectable when
|
|
only timestamp support is missing and is never silently reduced by removing
|
|
terrain, trees, buildings, monsters, players, alpha cutouts, or animated
|
|
casters. The built-in Low preset instead uses two 768 x 768 shadow maps and an
|
|
unfused six-pass, quarter-resolution separable post chain. An ordinary explicit
|
|
Low validation, candidate-build, or runtime failure still follows the complete
|
|
transactional fallback rules above.
|
|
|
|
## Diagnostic categories
|
|
|
|
| Category | Example user-facing reason | Recovery |
|
|
|---|---|---|
|
|
| Manifest | `plugin.json does not declare the renderPack kind` | Correct/reinstall the package |
|
|
| Managed ABI | `apiVersion 2 is unsupported; this SDK supports 1..1` | Use a compatible client or rebuild the pack |
|
|
| Pack ABI | `requires render-pack API 2; this client supports 1..1` | Same as above |
|
|
| Capability | `requires unsupported capability DirectionalShadowMaps` | Select a supported preset/device or retail |
|
|
| Declaration | `Pass 'blur' reads resource 'bloom-a' before it is written` | Correct the descriptor |
|
|
| User setting | `user override 'exposure' has invalid Float value '1,5'` | Correct/remove that stable setting-ID override; retail remains active |
|
|
| Asset | `asset 'bloom.frag.spv' is not valid SPIR-V` | Rebuild/reinstall the pack |
|
|
| Shader interface | `AtmosphericFrame must match ABI v1 (seven members, 160 bytes) or ABI v2 (nine members, 192 bytes)` | Recompile against the v1 or v2 binding table — v2 is additive over v1, so existing v1 modules remain valid and need no rebuild; this fires only when a module's `AtmosphericFrame` block matches neither accepted layout |
|
|
| Resource ceiling | `preset 'high' exceeds the pack memory ceiling` | Reduce the preset declaration |
|
|
| Auto performance | `Low remained over its declared performance budget for 180 stable samples` | Complete pack falls back to Retail; select explicit Low only after reviewing the measured limits |
|
|
| Candidate build | `pipeline creation failed for 'directional-shadow-world-cutout'` | Driver/asset diagnosis; retail for this session |
|
|
| Runtime/device | `selected pack failed validation on the fresh device` | Retail on the fresh renderer for this session; no retry loop |
|
|
| Removal/update | `selected pack is no longer installed` | Retail, while retaining the notice |
|
|
|
|
Diagnostics and screenshot metadata record pack ID, pack version, preset ID,
|
|
compatibility result and fallback reason. Enhanced screenshots are not retail
|
|
parity evidence.
|
|
|
|
## Update and removal
|
|
|
|
Pack IDs remain stable across compatible updates; increment `PackVersion` and
|
|
manifest `version` together. A preset or setting ID that persists must keep its
|
|
meaning. User values are persisted as invariant strings under the selected
|
|
pack ID and setting ID, so declaration reordering cannot retarget a value. If
|
|
an update removes or changes a persisted setting incompatibly, selection fails
|
|
atomically to retail with the unknown/invalid override reason instead of
|
|
silently applying it elsewhere. If an update removes the selected preset, the client falls back to a
|
|
compatible declared preset only after explicit policy permits it; otherwise it
|
|
selects retail. Removing or unloading a pack first withdraws registrations,
|
|
then retires GPU-flight resources, then releases the collectible load context.
|
|
|
|
The built-in atmospheric pack's `sun-shadow-*` setting IDs predate the
|
|
selected-celestial source contract. They remain stable persisted identifiers;
|
|
their current labels and semantics apply to directional shadows from whichever
|
|
authored celestial source the renderer selects.
|
|
|
|
Reconnect, portal travel, resize and world-generation replacement do not
|
|
re-register managed packs. Renderer-owned resources are recreated or retired
|
|
within the same generation/fence rules; pack assets never own gameplay,
|
|
streaming, collision, or physics lifetime.
|
|
|
|
For v1, device recreation is not an in-place `VK_ERROR_DEVICE_LOST` recovery
|
|
path. The host tears down the complete old renderer, context, and device, then
|
|
constructs and probes a new context/device. The default retail renderer remains
|
|
authoritative while the selected pack is validated as a fresh candidate; a
|
|
failed candidate stays on retail without an automatic retry loop.
|