feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -1797,7 +1797,15 @@ tool run closes it).
|
|||
|
||||
## #392 — A refused/failed fullscreen enter leaves `fullscreen: true` persisted against a windowed client
|
||||
|
||||
**Status:** OPEN — filed 2026-08-13 from the #376/#388 blast review (M4).
|
||||
**Status:** DONE — 2026-08-22. `IRuntimeDisplayWindowTarget` now returns
|
||||
the observed native fullscreen post-condition. Both startup and live-save
|
||||
controllers reconcile that result back through their own storage boundary,
|
||||
so a refused/failed enter immediately restores `fullscreen: false` in the
|
||||
retained Config row and `settings.json`; a failed leave likewise preserves
|
||||
the true native state. Focused startup, target, persistence, and observer
|
||||
tests pin the result seam and the requested→applied publication contract.
|
||||
|
||||
**Original filing:**
|
||||
The save path persists the Full Screen flag BEFORE the apply runs; when
|
||||
the state-aware apply then refuses (mode not offered / catalog absent) or
|
||||
the native switch fails, the client stays windowed while settings.json and
|
||||
|
|
@ -6869,7 +6877,14 @@ it. Do #297 FIRST — #298 depends on it.
|
|||
`#153` closed 2026-07-30 on the AD-30 hold + arrival StopCompletely +
|
||||
canonical outbound + reveal-barrier evidence chain). TS-50/TS-51/TS-53 are
|
||||
tracked in the divergence register.
|
||||
- **Deferred visual fidelity:** `#226` retail landscape detail overlay.
|
||||
- **Resolved visual fidelity (2026-08-21):** `#226` implements retail's
|
||||
building/EnvCell detail overlay through the existing Building Detail
|
||||
Textures preference. The reachable retail `ChangeRegion` caller disables
|
||||
landscape detail, so no separate landscape-detail item remains queued.
|
||||
The same Track A closeout ports retail's incident-face-averaged shared
|
||||
terrain vertex normals without changing positions, indices, or collision;
|
||||
terrain subdivision was rejected because quantized source samples cannot
|
||||
recover detail and the retail-correct normal interpolation is now present.
|
||||
- **Deferred frame-pacing fidelity:** `#235`, capped/RDP jump presentation
|
||||
aliases the retail 30 Hz object clock; uncapped Release presentation is
|
||||
smooth and physics, collision, and wire state remain correct.
|
||||
|
|
@ -9190,42 +9205,67 @@ the full 6,558-pass / 5-skip suite remains green.
|
|||
|
||||
---
|
||||
|
||||
## #226 — Retail landscape detail-texture overlay is not rendered
|
||||
## #226 — Retail building/EnvCell detail-texture overlay is not rendered
|
||||
|
||||
**Status:** OPEN — deferred visual fidelity; the user-visible tiling regression
|
||||
in #155 is fixed
|
||||
**Status:** IMPLEMENTED + CONNECTED-VISUAL-VERIFIED 2026-08-21
|
||||
**Severity:** LOW
|
||||
**Filed:** 2026-07-20
|
||||
**Component:** rendering / terrain material
|
||||
**Component:** rendering / building and environment materials
|
||||
|
||||
**Description:** Retail can overlay a high-frequency landscape detail texture,
|
||||
faded by viewer distance and gated by the Environment Detail Textures setting.
|
||||
acdream now repeats every base/overlay/road surface at its authored
|
||||
`TerrainTex.TexTiling`, which fixed the stretched/blurry symptom in #155, but
|
||||
does not yet render this separate optional detail layer.
|
||||
**Description:** Retail overlays a category-scoped detail texture on building
|
||||
shells and interior/EnvCell geometry, faded by viewer distance and gated by
|
||||
the Building Detail Textures preference. acdream's existing “Building
|
||||
Detail Textures” checkbox persisted that preference but previously had no
|
||||
renderer consumer. Outdoor landscape detail is forced off by the reachable
|
||||
Sept-2013 retail preference caller and is not this issue's user-visible target.
|
||||
|
||||
**Root cause / status:** The earlier #155 investigation conflated two retail
|
||||
mechanisms. `bb5acab9` ported the behavior that produced the observed mismatch:
|
||||
`TexMerge::CopyAndTile`/`Merge` pass each source's authored base tiling into the
|
||||
terrain composition. The still-missing detail pass is a distinct
|
||||
`LScape::GenerateDetailSurfaces`/`ACRender::landPolyDraw` path. The first
|
||||
experimental detail-array implementation sampled the wrong neutral/data
|
||||
contract and was reverted rather than shipping a darkened ground. TS-52 records
|
||||
the current divergence.
|
||||
terrain composition. #226 now resolves Dereth category 1/2 detail surfaces,
|
||||
uploads their authored texture/tiling with retail wrap/linear sampling, and
|
||||
replays building and EnvCell built-mesh subsets with the exact
|
||||
`DESTCOLOR + INVSRCALPHA` blend. This includes opaque, ClipMap, straight-alpha,
|
||||
additive, and inverse-alpha material subsets; transparent base/detail commands
|
||||
remain adjacent in acdream's authoritative shared alpha order with depth writes
|
||||
disabled (retail bypasses delayed alpha while detail is installed; retaining
|
||||
the accepted queue is the registered bounded ordering seam). The existing
|
||||
persisted checkbox is read at draw time. Opaque object replay is restricted to
|
||||
coalesced command runs containing a building, with mixed commands filtered per
|
||||
instance in the shader. Its depth-equal, non-A2C overlay inherits the exact
|
||||
per-sample coverage written by the opaque/A2C base, including ClipMap edges.
|
||||
Ordinary objects and landscape remain excluded; the base pass is untouched
|
||||
when the option is off. The first experimental
|
||||
landscape array used the wrong target, topology, neutral point, and blend and
|
||||
was reverted rather than shipping a darkened ground.
|
||||
|
||||
**Files:** `src/AcDream.App/Rendering/TerrainAtlas.cs`;
|
||||
`src/AcDream.App/Rendering/TerrainModernRenderer.cs`;
|
||||
`src/AcDream.App/Rendering/Shaders/terrain_modern.frag`.
|
||||
`src/AcDream.App/Rendering/Wb/WbDrawDispatcher.Rhi.cs`;
|
||||
`src/AcDream.App/Rendering/Wb/EnvCellRenderer.Rhi.cs`;
|
||||
`src/AcDream.App/Rendering/Shaders/mesh_detail.vert`;
|
||||
`src/AcDream.App/Rendering/Shaders/mesh_detail.frag`.
|
||||
|
||||
**Research:** `docs/research/2026-07-13-retail-terrain-texture-tiling-pseudocode.md`
|
||||
covers the now-shipped base contract. The detail symbols cited above must be
|
||||
distilled into a dedicated pseudocode note as the first #226 implementation
|
||||
step; the reverted experiment remains available in git history.
|
||||
covers the already-shipped base contract. The dedicated, corrected detail
|
||||
contract is `docs/research/2026-08-21-retail-building-detail-texturing-pseudocode.md`;
|
||||
its evidence source is
|
||||
`docs/research/2026-08-21-terrain-and-atmospheric-rendering-findings.md`.
|
||||
|
||||
**Acceptance:** With retail Environment Detail Textures enabled, close ground
|
||||
shows the same high-frequency detail and distance fade without changing base
|
||||
color/brightness. Disabling it produces the already-accepted authored base
|
||||
tiling.
|
||||
**Acceptance:** Toggling the existing “Building Detail Textures” checkbox
|
||||
visibly changes nearby building and interior surfaces without a restart.
|
||||
Enabled detail is full through 10 m positive view depth, fades linearly to an
|
||||
exact no-op at 50 m, and preserves retail's measured slight brightening.
|
||||
Disabling it submits no detail replay and preserves the already-accepted base
|
||||
render. Landscape, ordinary objects, physics, and collision remain unchanged.
|
||||
The automated gates cover the setting gate, data/blend/fade contract,
|
||||
built-mesh subset eligibility, opaque command filtering and A2C coverage,
|
||||
transparent depth/order seam, Vulkan descriptor
|
||||
binding and total/per-stage storage-descriptor limits, shader artifacts, and
|
||||
build. The connected Facility Hub A/B/A gate applied the real Config checkbox
|
||||
on -> off -> restored-on: nearby static walls/floor changed immediately, the
|
||||
restored frame returned to the original-on image (right-wall RGB MAE 2.132
|
||||
on/off versus 0.007 on/restored), the persisted preference was observed false
|
||||
during B and restored true, and logout was ACE-confirmed graceful.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -12052,9 +12092,11 @@ field through `TerrainAtlas`, uploads a layer-indexed table, and applies it in
|
|||
the modern shader while leaving cell-scale alpha masks unchanged. The user
|
||||
confirmed the outdoor textures now match the expected scale.
|
||||
|
||||
The optional high-frequency Environment Detail Textures pass is a different
|
||||
retail mechanism. It remains deferred under #226/TS-52 and does not keep this
|
||||
fixed user-visible regression open.
|
||||
The high-frequency detail pass is a different retail mechanism. #226 completed
|
||||
its reachable user-visible target on 2026-08-21: building shells and EnvCell
|
||||
geometry. Retail's reachable `ChangeRegion` caller passes zero landscape-detail
|
||||
surfaces, so the former TS-52 landscape premise is retired and does not keep
|
||||
this fixed user-visible regression open.
|
||||
|
||||
**Files:** `src/AcDream.App/Rendering/TerrainAtlas.cs`;
|
||||
`src/AcDream.App/Rendering/TerrainModernRenderer.cs`;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -2070,6 +2070,8 @@ Native macOS graphical support is not committed by this track. The current
|
|||
mandatory renderer requires modern OpenGL capabilities beyond Apple's native
|
||||
OpenGL ceiling; revisit macOS only if a supported graphics backend is chosen.
|
||||
|
||||
**Future / unscheduled — Campaign AR:** the opt-in [Atmospheric Rendering / Shader Packs campaign](2026-08-21-atmospheric-rendering.md) makes moving authored sun-and-moon directional shadows from trees, monsters, players, and buildings its Tier-2 headline while preserving acdream's current retail-faithful renderer as the default and leaving physics, collision, gameplay, and network behavior unchanged; the project owner assigned Campaign AR on 2026-08-22 without displacing active M4 gameplay work. The [celestial source contract](../research/2026-08-22-dereth-celestial-shadow-sources.md) selects sun, dominant moon, then secondary moon by rendered direction while retaining AC's single authored directional-energy channel; sun rays and volumetrics remain sun-only. The previously referenced #268 + TS-8 package is complete and retired. Stage 1's automated correctness, performance, lifetime, locked-restore, Release, evidence, documentation, and project-owner live gates completed on 2026-08-22 after the opt-in exposure correction. Stage 2 connected, performance, lifetime, package-lifecycle, physical-hardware, and closeout evidence is active.
|
||||
|
||||
---
|
||||
|
||||
## Cross-cutting work tracked in parallel
|
||||
|
|
|
|||
931
docs/plans/2026-08-21-atmospheric-rendering.md
Normal file
931
docs/plans/2026-08-21-atmospheric-rendering.md
Normal file
|
|
@ -0,0 +1,931 @@
|
|||
# Campaign AR — Atmospheric Rendering / Shader Packs
|
||||
|
||||
**Date:** 2026-08-21
|
||||
**Status:** STAGE 2 ACTIVE — the approved authored sun-and-moon shadow-source
|
||||
extension and every non-physical Stage 1 correctness, performance, lifetime,
|
||||
Release, evidence, and documentation gate are complete. After the live
|
||||
sun/moon, source-transition, temporal-stability, desktop-performance, and
|
||||
exposure correction round, the project owner accepted Stage 1 on 2026-08-22.
|
||||
Stage 2 connected, lifetime, package-lifecycle, physical-hardware, and final
|
||||
closeout evidence is now active.
|
||||
**Phase id:** **Campaign AR** — assigned by the project owner on 2026-08-22
|
||||
**Scheduling:** originally held for the post-M7 rendering-polish pass; the
|
||||
project owner explicitly authorized implementation on 2026-08-21. This
|
||||
owner-directed campaign is now Campaign AR and does not displace the active M4
|
||||
gameplay work. The previously referenced #268 + TS-8 stat-chain package is
|
||||
already complete and retired, so it is no longer a scheduling dependency.
|
||||
|
||||
## Goal
|
||||
|
||||
Add an opt-in enhanced-graphics system whose headline feature is **real-time
|
||||
directional shadows cast by trees, monsters, players, and buildings as
|
||||
Dereth's authored sun and moons move across the sky**. The same system can add
|
||||
bloom, filmic tonemapping, colour grading, vignette, sun rays, and later
|
||||
volumetric shafts, with useful quality levels on weak through high-end
|
||||
hardware. Sun rays and volumetric shafts remain sun-only effects; the approved
|
||||
moon scope applies to Tier 2 directional shadows.
|
||||
|
||||
Campaign AR executes in two stages. Stage 1 fixed the dense-scene transform
|
||||
ceiling and shadow quality, completed authored sun/dominant-moon/secondary-moon
|
||||
source selection, and finished every automated gate that did not require the
|
||||
project owner's physical-display judgment. The owner accepted the subsequent
|
||||
live visual/performance round on 2026-08-22 after the default Atmospheric
|
||||
exposure was corrected from 1.0 to 0.80. Stage 2 is active and limited to the
|
||||
remaining connected, lifetime, package-lifecycle, physical-hardware, evidence,
|
||||
and final owner-acceptance rows; it does not add another renderer feature tier.
|
||||
|
||||
Stage 1's dense-scene regression is pinned by the connected failures already
|
||||
captured on 2026-08-22: Atmospheric fell back at 68,395, 67,581, and even
|
||||
65,538 combined world matrices against the old 65,536-matrix binding ceiling.
|
||||
The corrected connected launch must exceed that historical workload without
|
||||
persisting acdream-default fallback or splitting the authoritative pose data.
|
||||
|
||||
The enhancement is a shader pack, not a rewrite of acdream's renderer or AC's
|
||||
art. **acdream's current retail-faithful renderer** remains the default,
|
||||
authoritative path. References below to the “default” or “retail-faithful” path
|
||||
always mean acdream—not the original retail executable.
|
||||
|
||||
The evidence and constraints for this design are recorded in the
|
||||
[terrain and atmospheric rendering findings](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md),
|
||||
especially [the measured renderer baseline](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md#5-renderer-state-relevant-to-atmospheric-work)
|
||||
and [the requested tier model](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md#6-wanted-work--atmospheric-rendering-user-stated).
|
||||
The approved celestial identity, priority, transform, and direction-versus-
|
||||
energy contract are pinned by the
|
||||
[Dereth celestial shadow-source research](../research/2026-08-22-dereth-celestial-shadow-sources.md).
|
||||
|
||||
## Opt-in contract
|
||||
|
||||
1. **acdream's current renderer is the default.** With no pack selected, the current render graph,
|
||||
shaders, render targets, submissions, lighting, colours, and screenshots
|
||||
remain authoritative. No enhancement resource or pass is created.
|
||||
2. **Selection is explicit.** Installing a pack does not enable it. The user
|
||||
selects one pack and one quality preset in Display settings. `acdream
|
||||
default (retail-faithful)` is always present and cannot be removed.
|
||||
3. **One pack owns the enhancement graph.** Packs do not stack. This prevents
|
||||
ambiguous pass ordering, incompatible HDR conventions, and unbounded GPU
|
||||
cost.
|
||||
4. **The renderer owns the RHI.** A pack declares assets, semantic pass hooks,
|
||||
capabilities, resources, and quality variants. It never receives Vulkan
|
||||
handles or mutates the authoritative scene, streaming, gameplay, or
|
||||
physics owners.
|
||||
5. **Failure returns to acdream's default renderer.** Unsupported capabilities,
|
||||
malformed assets, shader/pipeline candidate-creation failure, or an invalid
|
||||
pass graph disables the complete pack and restores acdream's default path
|
||||
with a visible reason. A half-enabled pack is never rendered. A terminal
|
||||
`VK_ERROR_DEVICE_LOST` cannot render either path on the lost device; it tears
|
||||
down that renderer/device lifetime, and retail remains authoritative while a
|
||||
fresh renderer/device is constructed and the pack is validated again.
|
||||
6. **Divergence is honest.** Enhanced screenshots are intentionally not retail
|
||||
parity evidence. The default path remains the comparison oracle and the
|
||||
enhancement choice is recorded in diagnostics and screenshot metadata.
|
||||
7. **No scheduling claim.** Rendering phases stay frozen until the M7 polish
|
||||
pass unless the project owner explicitly reprioritizes this work.
|
||||
|
||||
## Capability tiers
|
||||
|
||||
The costs below are **planning estimates**, not measurements. They are
|
||||
incremental GPU p50 targets for a representative discrete GPU at 1920x1080;
|
||||
every slice must replace them with physical-hardware measurements. Tier-1
|
||||
pixel effects scale with output resolution, so 4K has roughly four times the
|
||||
1080p fragment workload. Shadow-map cost depends more on caster count, map
|
||||
resolution, and cascade count than on output resolution.
|
||||
|
||||
| Tier | Contents | Prerequisite | Rough incremental GPU cost at 1080p |
|
||||
|---|---|---|---:|
|
||||
| acdream default | Current authoritative retail-faithful rendering | Current mandatory Vulkan/RHI capabilities | 0 ms |
|
||||
| 1 | Bloom, ACES filmic tonemap, colour grade, vignette | Main-world colour intermediate and fullscreen passes | 0.35–0.80 ms |
|
||||
| 1 | Screen-space sun rays (crepuscular) | Authored sun screen position plus an occlusion mask; **no shadow maps** | 0.20–0.50 ms |
|
||||
| 2 | **Moving authored sun-and-moon cascaded directional shadows from trees, monsters, players, and houses/buildings** | A second scene pass, selected-celestial view/projection matrices, sampled depth maps, caster pipeline variants | 1.50–3.00 ms |
|
||||
| 2+ | Sun-only volumetric light shafts | Reuse Tier-2 shadow infrastructure only while the selected source is the authored sun, plus authored weather | 0.15–0.40 ms |
|
||||
| Later | SSAO and water reflections | Scene depth plus normal inputs and separate designs | Not budgeted here |
|
||||
| Out | True PBR | AC lacks authored per-texture normal/roughness/metalness maps | Not planned |
|
||||
|
||||
Tier numbers express prerequisites, not a forced bundle. A pack may offer
|
||||
Tier 1 without shadows. Tier 2 always includes the complete shadow-caster
|
||||
classes; weak-hardware presets reduce range, cascade count, and resolution
|
||||
rather than silently dropping monsters, trees, or buildings.
|
||||
|
||||
## Tier 2 headline: Dereth's moving authored sun-and-moon shadows
|
||||
|
||||
The [measured renderer state](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md#5-renderer-state-relevant-to-atmospheric-work)
|
||||
already supplies retail's single directional colour/energy channel from
|
||||
`SkyStateProvider`. Tier 2 augments it with the visible authored celestial
|
||||
positions documented in the
|
||||
[Dereth celestial shadow-source research](../research/2026-08-22-dereth-celestial-shadow-sources.md).
|
||||
The camera-relative cascaded map selects, in order, the visible above-horizon
|
||||
sun (`0x01001348`), dominant haloed moon (`0x01001F6A`), or secondary moon
|
||||
(`0x01001F67`). The selected mesh's exact rendered transform supplies shadow
|
||||
direction; retail's one interpolated `DirColor * DirBright` channel supplies
|
||||
colour/energy. Moon texture brightness and mesh luminosity never manufacture a
|
||||
second world light. As those authored bodies move, tree branches, monsters,
|
||||
players, houses, and other eligible world geometry cast correspondingly moving
|
||||
shadows.
|
||||
|
||||
This source selection is an explicit opt-in pack enhancement. It is not a
|
||||
claim that the retail executable rendered real-time moon shadows, and it does
|
||||
not alter acdream's default retail-faithful scene lighting. Screen-space sun
|
||||
rays and volumetric shafts continue to use only the authored sun; they do not
|
||||
switch to either moon.
|
||||
|
||||
The required behavior is:
|
||||
|
||||
- Terrain and opaque world geometry receive shadows. Terrain, buildings,
|
||||
statics, procedural scenery, the local player, remote players, and creatures
|
||||
cast them when resident and visible to the main outdoor world.
|
||||
- Foliage and other cutout materials use an alpha-sampling shadow fragment
|
||||
shader. An empty depth fragment shader would turn each tree plane into a
|
||||
solid rectangular shadow.
|
||||
- Animated casters reuse the exact per-part transforms already published in
|
||||
the N.5 SSBO. The shadow pass must not create a second animation pose or
|
||||
gameplay entity projection.
|
||||
- Cascades follow the camera and are texel-stabilized. Their reach is clamped
|
||||
to the resident two-tier streaming window; the pack does not extend world
|
||||
streaming or issue speculative loads.
|
||||
- The celestial directional-shadow pass is outdoor-only. Dungeon and EnvCell lighting remains
|
||||
authored per-cell lighting. Entering an interior retires or idles outdoor
|
||||
shadow work without leaving stale maps on screen.
|
||||
- Shadow direction follows the selected visible above-horizon sun, dominant
|
||||
moon, or secondary moon. The authored directional colour/energy remains
|
||||
`DirColor * DirBright`; active day/weather pack policy may soften or reduce
|
||||
it without inventing a second celestial clock, light-energy channel, or
|
||||
weather system. A time with no eligible above-horizon body has no
|
||||
directional shadow; night is not itself a disable condition.
|
||||
- Transparent blend materials do not cast an opaque silhouette by default.
|
||||
Only existing opaque and cutout classifications participate until a
|
||||
material-specific transparent-shadow contract is designed.
|
||||
|
||||
The pass reuses the retained resident scene. It must not run PView, portal
|
||||
traversal, or per-object CPU visibility classification once per cascade.
|
||||
Initially, each cascade draws the bounded resident caster set through the
|
||||
existing batched/MDI ownership. If that is too expensive, the next permitted
|
||||
step is GPU culling—not repeated CPU culling or per-object submissions.
|
||||
|
||||
## Pack API surface sketch
|
||||
|
||||
The public declarations belong in the BCL-only
|
||||
`AcDream.Plugin.Abstractions` assembly. The graphical App supplies the
|
||||
implementation and translates the declarations to the Vulkan RHI. Headless
|
||||
hosts expose no render-pack registry and never load pack assets.
|
||||
|
||||
This is an API shape, not code committed by this design:
|
||||
|
||||
```csharp
|
||||
public interface IRenderPackPlugin
|
||||
{
|
||||
void Register(IRenderPackRegistry registry);
|
||||
}
|
||||
|
||||
public interface IRenderPackRegistry
|
||||
{
|
||||
IDisposable Register(RenderPackDescriptor descriptor, IRenderPackAssets assets);
|
||||
}
|
||||
|
||||
public interface IRenderPackAssets
|
||||
{
|
||||
Stream OpenRead(string assetKey);
|
||||
}
|
||||
|
||||
public sealed record RenderPackDescriptor(
|
||||
string Id,
|
||||
string DisplayName,
|
||||
Version PackVersion,
|
||||
int PackApiVersion,
|
||||
RenderPackTier HighestTier,
|
||||
IReadOnlyList<RenderCapability> RequiredCapabilities,
|
||||
IReadOnlyList<RenderCapability> OptionalCapabilities,
|
||||
IReadOnlyList<RenderResourceDeclaration> Resources,
|
||||
IReadOnlyList<RenderPassDeclaration> Passes,
|
||||
IReadOnlyList<SceneReplayDeclaration> SceneReplays,
|
||||
IReadOnlyList<PipelineVariantDeclaration> PipelineVariants,
|
||||
IReadOnlyList<RenderQualityPreset> QualityPresets,
|
||||
IReadOnlyList<RenderSettingDeclaration> Settings,
|
||||
AtmospherePolicyDeclaration? AtmospherePolicy);
|
||||
```
|
||||
|
||||
A pack declares:
|
||||
|
||||
- a stable ID, display name, pack version, and pack-API version;
|
||||
- its highest tier and a human-readable feature summary;
|
||||
- mandatory and optional GPU capabilities and per-preset limits;
|
||||
- shader assets and fixed renderer semantic inputs, including world colour,
|
||||
scene depth, optional normals, selected celestial shadow direction/energy,
|
||||
sun direction/screen position for sun-only effects, active weather, camera
|
||||
matrices, shadow-caster transforms, and frame time;
|
||||
- intermediate images/buffers by relative or absolute extent, format class,
|
||||
usage, lifetime, and estimated bytes;
|
||||
- passes at renderer-owned hooks such as `ShadowDepthBeforeWorld`,
|
||||
`AtmosphereBeforeToneMap`, `ToneMap`, and
|
||||
`AfterToneMapBeforePrivateViewports`;
|
||||
- renderer-owned scene replays such as `OutdoorDirectionalShadowCasters`, with
|
||||
requested cascade views and existing caster/material classes
|
||||
(`Terrain`, `OpaqueWorld`, `AlphaCutoutWorld`, `AnimatedOpaque`, and
|
||||
`AnimatedAlphaCutout`); the renderer resolves those classes from its
|
||||
retained scene and records their existing batched draws;
|
||||
- fixed pipeline variants for shadow-caster depth and main-world shadow
|
||||
receivers. A variant names its base semantic (`Terrain`, `WorldMesh`, or
|
||||
`EnvCell`), shader asset, compatible material classes, and declared inputs
|
||||
such as cascade matrices, directional depth maps, and sampler state; it does
|
||||
not replace visibility, batching, mesh ownership, or draw submission code;
|
||||
- quality presets, user-visible settings with bounded ranges, and declared
|
||||
incremental GPU/VRAM budgets; and
|
||||
- an atmosphere policy: explicit directional-source/sun-elevation response
|
||||
curves and a mapping from AC's categorical `activeDayGroup` values to effect
|
||||
multipliers. These values live in the visible pack declaration, not as hidden
|
||||
renderer constants; AC remains the owner of celestial position, directional
|
||||
energy, and weather state.
|
||||
|
||||
The renderer—not the pack—defines descriptor layouts, validates SPIR-V and
|
||||
resource declarations, resolves semantic scene-replay and pipeline-variant
|
||||
requests, builds pipelines, schedules barriers, owns frame-flight and teardown,
|
||||
and supplies immutable frame inputs. Packs cannot add arbitrary draw callbacks,
|
||||
read gameplay owners, submit command buffers, retain borrowed frame views, or
|
||||
address resources outside their registration. The built-in pack's Tier-2
|
||||
caster pass and receiver shaders must be expressible entirely through these
|
||||
same public declarations.
|
||||
|
||||
### Selection and fail-safe lifecycle
|
||||
|
||||
1. Discover manifests and descriptors without creating GPU objects.
|
||||
2. Show compatibility and estimated cost in Display settings. Unsupported
|
||||
packs remain visible with the exact missing capability; they cannot be
|
||||
selected.
|
||||
3. On explicit selection, validate the whole descriptor, all assets, resource
|
||||
ceilings, hooks, and shader interfaces; then build a complete candidate
|
||||
pipeline set off to the side.
|
||||
4. Atomically activate the candidate only after every required object exists.
|
||||
Until then acdream's default path continues rendering.
|
||||
5. Persist `pack id + pack version + preset`, never a positional index. If the
|
||||
pack disappears or becomes incompatible, select `acdream default` and
|
||||
retain the failure notice.
|
||||
6. On runtime validation or post-recreation candidate failure, withdraw all
|
||||
pack passes/resources at a frame boundary and resume acdream's default
|
||||
renderer. Do not repeatedly retry a failing pack during the session.
|
||||
7. Unload and reconnect use the normal render-generation and GPU-flight
|
||||
retirement rules. No pack object may retain a world generation, scene
|
||||
entity, or collectible plugin load context.
|
||||
|
||||
In this campaign, **device recreation** means disposing the complete old
|
||||
renderer, Vulkan context, and device, then constructing a fresh context/device,
|
||||
re-probing capabilities, and validating selection again with retail active
|
||||
until the candidate is complete. It does **not** mean live, in-process recovery
|
||||
from `VK_ERROR_DEVICE_LOST`; device loss remains terminal to that renderer and
|
||||
device lifetime.
|
||||
|
||||
The built-in Atmospheric Rendering pack should be the first consumer of this
|
||||
same API. It must not receive private renderer shortcuts that third-party packs
|
||||
cannot express.
|
||||
|
||||
The public v1 authoring surface, manifest schema, shader semantic bindings,
|
||||
failure guidance, validator command, and external no-op sample are indexed by
|
||||
the [render-pack SDK](../render-packs/README.md).
|
||||
|
||||
## Frame-graph placement
|
||||
|
||||
With the pack off, the frozen retail graph is unchanged. With a pack selected,
|
||||
the renderer builds a separate enhancement graph:
|
||||
|
||||
1. Update the existing immutable world frame, including authored sky objects,
|
||||
the retail directional colour/energy channel, and weather.
|
||||
2. Outdoors, select the visible above-horizon sun/dominant moon/secondary moon
|
||||
direction and render Tier-2 cascaded shadow depth from the resident caster
|
||||
set.
|
||||
3. Render the main world to the pack's world-colour intermediate, using
|
||||
pack-selected pipeline variants to sample the shadow map where requested.
|
||||
4. Preserve the established PView, punch/seal depth discipline, shared-alpha
|
||||
ordering, particle ordering, and world transparency boundaries.
|
||||
5. Generate screen-space sun occlusion/rays or, only while the shadow source is
|
||||
the sun, shadow-map volumetrics.
|
||||
6. Composite rays/shafts **before tonemapping**, so bloom sees them and the
|
||||
filmic curve rolls them off instead of clipping them.
|
||||
7. Apply tonemap, colour grade, and vignette to the main world image.
|
||||
8. Continue with private portal/paperdoll/appraisal viewports and retained UI
|
||||
on their existing path. They are not accidentally post-processed with the
|
||||
main world.
|
||||
|
||||
## Delivery slices and acceptance
|
||||
|
||||
The slice labels below are local to this document. They are not phase IDs.
|
||||
|
||||
### Pre-moon checkpoint, Stage 1 acceptance, and Stage 2 start — 2026-08-22
|
||||
|
||||
Before the approved moon extension, all seven local slices (0–6) had production
|
||||
implementations in the current worktree. A source-identical isolated clean
|
||||
snapshot closed that sun-only reference-GPU physical matrix, and one physical
|
||||
integrated-AMD Auto safe-fallback row was also present. Those artifacts remain
|
||||
valid evidence for the exact binaries and sun-only scope they measured; they
|
||||
are not moon-alignment, source-transition, current-worktree, or final user-
|
||||
acceptance evidence.
|
||||
|
||||
Stage 1's automated implementation and validation are complete. The authored
|
||||
sun/dominant-moon/secondary-moon resolver and its direction-versus-energy
|
||||
handoff are present and covered without inferring physical quality from unit
|
||||
tests, screenshots, or historical sun-only rows. The subsequent live ACE round
|
||||
covered the owner-reported shadow visibility/configuration, temporal
|
||||
pixelation/shimmer, frame-pacing/desktop responsiveness, selection/fullscreen
|
||||
regressions, and matched indoor/outdoor exposure. After the exposure correction
|
||||
the owner accepted the live result. The exact evidence and limits are recorded
|
||||
in the [Stage 1 live-gate report](../research/2026-08-22-atmospheric-stage1-live-gate.md).
|
||||
|
||||
Current authored-celestial Stage 1 automated gate (2026-08-22):
|
||||
|
||||
- The shader compiler reports **24/24** Vulkan shader pairs ready. Incremental
|
||||
regeneration expands only pack includes and preserves all 18 pre-campaign
|
||||
retail SPIR-V artifacts byte-for-byte; the exact SHA-256 oracle and complete
|
||||
source-manifest checks pass. All selected-celestial binding-6 modules expose
|
||||
the 336-byte ABI v1 layout, including source kind at offset 320.
|
||||
- Focused Release validation passes **344/344** App renderer tests, **30/30**
|
||||
standalone SDK/pack-validator tests, **14/14** Core sky-loader tests, and
|
||||
**48/48** MossTank tests. Both external Tier-2 samples embed and validate the
|
||||
current selected-celestial shader ABI without App or Vulkan dependencies.
|
||||
- The repository's forced locked restore passes. The complete Release solution,
|
||||
including all source, tests, tools, and SDK samples, builds with **0 warnings
|
||||
and 0 errors** after that restore.
|
||||
- The repository-owned fresh-process hermetic gate passes
|
||||
**14,928/14,928** tests with zero skips or failures across 14 assemblies;
|
||||
`AcDream.App.Tests` contributes **5,823/5,823**. Evidence is under
|
||||
`artifacts/atmospheric-rendering/stage1-moon-release-gate/`.
|
||||
- The App total includes the 9,500-caster 256-frame zero-managed-allocation
|
||||
steady-state fixture, warmed CPU/GPU sampling allocation gates, the complete
|
||||
12-cycle Low/Medium/High/retail/resize/failure/recovery/frame-flight/
|
||||
generation convergence fixture, and independent renderer/context/device
|
||||
recreation. These prove the non-physical performance and lifetime contracts;
|
||||
they do not claim physical frame pacing or image quality.
|
||||
- A final path audit finds no source changes under `src/AcDream.Runtime`, no
|
||||
physics or collision changes, and no changes to the retail GLSL sources or
|
||||
tracked retail SPIR-V binaries. Pack-off production integration remains the
|
||||
strict authoritative-path oracle.
|
||||
|
||||
The command-level record and evidence boundary are in the
|
||||
[Stage 1 automated gate report](../research/2026-08-22-atmospheric-stage1-automated-gate.md).
|
||||
|
||||
Recorded pre-moon automated checkpoint (not a current moon-scope completion
|
||||
claim):
|
||||
|
||||
- The repository-owned fresh-process Release test stage passes
|
||||
**14,880/14,880** tests with zero skips or failures across 14 assemblies.
|
||||
`AcDream.App.Tests` contributes **5,783/5,783**; campaign-focused App cases
|
||||
cover descriptor/asset/SPIR-V
|
||||
validation, pack-off/no-op invariants, atomic asynchronous candidate swaps,
|
||||
runtime fallback, declared settings, Tier-1/Tier-2/Tier-2+ graph execution,
|
||||
all headline caster classes, topology caching, exact animated transforms,
|
||||
Low/Medium/High/Auto policy, diagnostics, and the pack UI.
|
||||
- Headless plugin-session tests pass **6/6**, including rejection of a
|
||||
render-pack-only request before its DLL is loaded.
|
||||
- The SDK validator suite passes **26/26** and builds/validates the external
|
||||
`AcDream.RenderPacks.NoOp`, `AcDream.RenderPacks.AtmosphericTier2`, and
|
||||
`AcDream.RenderPacks.ShadowsOnlyTier2` samples without App or Vulkan
|
||||
references.
|
||||
- The production catalog is revisioned rather than frozen at startup. The same
|
||||
composed controller/UI observes external registration, withdrawal, and
|
||||
corrected re-registration; an active withdrawn pack retires at the next
|
||||
frame boundary, persists retail fallback, releases its asset/context owners,
|
||||
and does not retry the removed registration. Runtime package admission now
|
||||
matches the SDK: exactly one public constructible render-pack entry point and
|
||||
at least one live registration, with transactional rollback for malformed,
|
||||
multiple, internal, zero-registration, and partially failing packages.
|
||||
- The retained 9,500-caster warmed-frame fixture performs no second-frame
|
||||
scene-index copy, topology rebuild, sort, or classification and allocates
|
||||
zero managed bytes. Animated-static, live-dynamic, and equipped-child root
|
||||
and part transforms refresh through cached IDs/slots with exact float bits.
|
||||
- Render and screenshot diagnostics now publish exact accepted counts for
|
||||
terrain commands, outdoor statics, buildings, animated statics, local
|
||||
players, remote players, non-player creatures, other live dynamics, and
|
||||
equipped children. These labels stop at the authoritative evidence boundary:
|
||||
static DAT publication does not distinguish a tree from other outdoor
|
||||
scenery, and create-object render metadata does not distinguish a hostile
|
||||
monster from a non-hostile NPC creature. Diagnostics therefore report
|
||||
`OutdoorStatics` and `NonPlayerCreatures`; they never infer tree or monster
|
||||
identity from a mesh or ID.
|
||||
- The recording-RHI long-cycle gate repeatedly crosses Low, Medium, High, and
|
||||
retail selection; resize; injected candidate failure and explicit recovery;
|
||||
both frame-flight slots; render-generation replacement; and final renderer
|
||||
disposal. Pack resources, pipeline-format leases, texture slots, retained
|
||||
transforms, receiver candidates, and registrations converge exactly. A
|
||||
second fixture proves that device recreation is old-renderer/context/device
|
||||
teardown followed by an independent fresh device and activation generation.
|
||||
- Therefore the deterministic lifecycle implementation, recording-RHI
|
||||
convergence, and fresh-device recreation definition are locally closed. The
|
||||
executable connected route and its contract assertions are implemented for
|
||||
select/disable/re-enable, exact resize, authored time/weather changes, and
|
||||
fresh-process recreation, but a contract-tested route is not connected-world
|
||||
evidence; its ACE-backed execution and artifacts remain open below.
|
||||
- The complete Release solution, including all three SDK samples, the
|
||||
validator, shader compiler/generated manifest, and repository tools, builds
|
||||
with **0 warnings and 0 errors**. The current managed workspace could not
|
||||
repeat the gate's locked-restore stage because it denies NuGet access to the
|
||||
user-profile `NuGet.Config`; the explicit no-restore build and complete test
|
||||
stage above are current, while locked-restore verification remains a
|
||||
closeout-environment requirement rather than being reported as green here.
|
||||
|
||||
The corresponding durable source/test entry points are:
|
||||
|
||||
- public contracts and SDK:
|
||||
`src/AcDream.Plugin.Abstractions/Rendering/`, `docs/render-packs/`,
|
||||
`tools/RenderPackValidator/`, `samples/AcDream.RenderPacks.*`, and
|
||||
`tests/AcDream.RenderPackValidator.Tests/`;
|
||||
- activation, compatibility, Auto, diagnostics, and built-in graph:
|
||||
`src/AcDream.App/Rendering/Packs/` and
|
||||
`tests/AcDream.App.Tests/Rendering/Packs/`;
|
||||
- moving authored-celestial cascades, casters, receivers, and retained topology:
|
||||
`src/AcDream.App/Rendering/DirectionalShadow*.cs`,
|
||||
`src/AcDream.App/Rendering/Packs/AuthoredCelestialShadowSource.cs`,
|
||||
`src/AcDream.App/Rendering/Scene/DirectionalShadowCasterFrame.cs`,
|
||||
`src/AcDream.App/Rendering/Wb/WbDrawDispatcher.DirectionalShadows.cs`, and
|
||||
`tests/AcDream.App.Tests/Rendering/DirectionalShadow*` plus
|
||||
`tests/AcDream.App.Tests/Rendering/Packs/AuthoredCelestialShadowSourceResolverTests.cs`;
|
||||
- retained Display UI and headless boundary:
|
||||
`src/AcDream.App/UI/Layout/ConfigOptionsPageController.cs`,
|
||||
`tests/AcDream.App.Tests/UI/Layout/ConfigOptionsPageControllerTests.cs`, and
|
||||
`tests/AcDream.Headless.Tests/HeadlessPluginSessionTests.cs`.
|
||||
|
||||
Machine-local offline/physical evidence currently present under
|
||||
`artifacts/atmospheric-rendering/` is gate evidence, but it is not a substitute
|
||||
for connected-world or project-owner acceptance:
|
||||
|
||||
- `smoke-retail-720p/` records the pack-off `retail/off` path with zero pack
|
||||
resources, casters, cascades, or classification calls;
|
||||
- `accept-shadow-morning-200m/`, `accept-shadow-afternoon-200m/`, and
|
||||
`accept-shadow-morning-close/` contain fixed-camera moving-sun shadow
|
||||
screenshots plus metadata for 9,498 resident casters and three cascades;
|
||||
these pre-moon captures do not prove moon alignment or source transitions;
|
||||
- `volumetric-valid-camera-500m/` contains a High-preset volumetric diagnostic
|
||||
capture; it is not a performance acceptance row;
|
||||
- `matrix-clean-snapshot-dense-linear-v20/` is the current complete AMD Radeon
|
||||
RX 9070 XT physical matrix: **30/30 rows pass** across
|
||||
retail/Low/Medium/High/Auto, 1080p/1440p/4K, and capped/uncapped pacing. The
|
||||
actual worktree's 4,397 source files were copied and hash-verified into an
|
||||
isolated clean snapshot at commit `4876c970`; the Release App product version
|
||||
names that exact commit, source and binary identities match, and tracked
|
||||
status is empty. Its 18 active enhanced rows each own exact 2,048-sample
|
||||
CPU/receiver/GPU windows, 9,498 casters, and zero warmed classification calls.
|
||||
Six rows are retail and six 4K Low/Medium/Auto rows are accepted
|
||||
resource-unavailable fail-safe outcomes;
|
||||
- `matrix-final-v16-exact-auto/` and the two `current-low-1080p-*-v20/`
|
||||
captures retain the optimization's predecessor/reference trail; the clean
|
||||
snapshot matrix above supersedes them as current reference-adapter evidence;
|
||||
- all six retail rows record zero pack resources, passes, casters, cascades,
|
||||
draws, or dispatches. The six unavailable 4K Low/Medium/Auto rows likewise
|
||||
record zero pack work and pass their strict paired-default framebuffer
|
||||
comparisons instead of rendering a half-enabled graph;
|
||||
- `igpu-auto-safe-fallback-v7-paired/` records physical Auto behavior on the
|
||||
integrated **AMD Radeon(TM) Graphics** adapter (Vulkan 1.4.315, driver
|
||||
2.0.353). After 180 stable Low samples, Auto failed safe with the exact reason
|
||||
`GPU p99 19.308 ms (budget 3.000 ms), CPU p99 0.534 ms (budget 0.500 ms),
|
||||
resident GPU bytes 41648404 (budget 67108864)`. The published state is
|
||||
`retail/off`, has zero pack resources or work, and retains that visible
|
||||
reason. Its comparison against the paired time-matched retail artifact
|
||||
`igpu-retail-current-v6-time-matched/`, using `sky-mask.png`, differs in only
|
||||
**56 / 1,536,000 compared pixels**, a **0.003645833% (0.00365%) sky-masked
|
||||
pixel difference**, below the 0.1% gate. This closes physical weak-adapter
|
||||
safe fallback, not active Low performance on that adapter or the remaining
|
||||
GPU matrix;
|
||||
- `compare-retained-transform.json` records the retained-transform image
|
||||
comparison used by the 9,500-caster steady-state gate.
|
||||
|
||||
Stage 1 project-owner gate:
|
||||
|
||||
- **PASS — accepted by the project owner on 2026-08-22.** The acceptance closes
|
||||
Stage 1's physical-display and desktop-performance stop. It authorizes Stage
|
||||
2; it is not a substitute for Stage 2's connected scenario, long-lifetime,
|
||||
package lifecycle, additional physical-GPU, or final pack-off/pack-on rows.
|
||||
|
||||
Stage 2 and closeout gates, now active:
|
||||
|
||||
1. Execute the already-implemented connected graphical route and complete its
|
||||
remaining matrix: moving local/remote players, known monster encounters
|
||||
reported under the authoritative `NonPlayerCreatures` category, and equipped
|
||||
children; landblock publication/demotion; clear, overcast and rain;
|
||||
outdoor/interior/dungeon transitions; portal travel and reconnect. Capture
|
||||
the implemented select/disable/re-enable, resize, authored sun/moon source
|
||||
transitions and weather, and
|
||||
fresh-process renderer/context/device-recreation assertions against a real
|
||||
ACE session, proving exact resource convergence and no stale maps/owners.
|
||||
The local recording/device-recreation semantics are closed; this connected
|
||||
execution and its user-visible evidence are not.
|
||||
2. Repeat the now-current clean-source RX 9070 XT
|
||||
pack-off/Low/Medium/High/Auto × 1080p/1440p/4K × capped/uncapped matrix on
|
||||
every other supported physical GPU class. The integrated-AMD Auto-to-retail
|
||||
artifact proves weak-adapter safe fallback only; it does not prove active Low
|
||||
or the complete matrix on that adapter. Automated weak-GPU fixtures, one
|
||||
fallback row, and one high-end reference adapter do not prove the remaining
|
||||
physical rows.
|
||||
3. Complete the visual matrix for Tier-1 neutral values and private-view/UI
|
||||
isolation; rays at dawn/noon/dusk, behind-camera and occluded states;
|
||||
foliage cutouts; moving animated shadows under sun and moon; indoor gating;
|
||||
source-transition continuity; temporal pixelation/shimmer; bias/cascade seam
|
||||
review; sun-only volumetric weather/occluder behavior; and pack-off
|
||||
restoration.
|
||||
4. Exercise external install/select/update/remove/fail/recover flows in the
|
||||
graphical host, then complete the long lifetime run. Automated SDK and
|
||||
lifecycle fixtures do not replace this connected evidence.
|
||||
5. Obtain explicit project-owner acceptance of the final pack-off and pack-on
|
||||
visual/performance matrix before changing this document to shipped.
|
||||
|
||||
### Slice 0 — Contract, capability probe, and acdream-default no-op
|
||||
|
||||
**Implementation:** complete. The BCL-only v1 ABI, live revisioned plugin
|
||||
discovery/catalog, retained Display selection, compatibility/cost summaries,
|
||||
strict SDK-equivalent entry admission, asynchronous candidate preparation,
|
||||
frame-boundary activation/withdrawal/fallback, registration-scoped no-retry,
|
||||
stable diagnostics, no-op sample, and headless exclusion are present and
|
||||
automated. The checked-in default-path oracle and RX 9070 XT physical pack-off
|
||||
rows pass; connected lifetime evidence and the remaining supported physical GPU
|
||||
classes stay open.
|
||||
|
||||
Define the versioned BCL-only descriptor/registry, manifest fields, semantic
|
||||
bindings, pack discovery, Display selection, diagnostics, and atomic
|
||||
activation/fallback transaction. Implement a no-op conformance pack only.
|
||||
|
||||
**Acceptance:** `acdream default (retail-faithful)` remains selected on clean and upgraded
|
||||
installs; pack discovery allocates no GPU resources; the disabled run has the
|
||||
same pass list, pipeline set, draw/dispatch counts, deterministic framebuffer
|
||||
digests, and resource ledger as the pre-campaign baseline; malformed,
|
||||
unsupported, missing, and shader-invalid fixtures all report one precise
|
||||
reason and render acdream's default path without partial resources or retry loops; headless
|
||||
hosts load no render assemblies or pack assets.
|
||||
|
||||
### Slice 1 — Tier-1 world-colour and filmic stack
|
||||
|
||||
**Implementation:** complete. The pack-owned main-world target, bloom chain,
|
||||
ACES filmic pass, colour grade, vignette, declared neutral settings, resize
|
||||
recreation, and private-viewport/UI placement are implemented and automated.
|
||||
The RX 9070 XT 1080p/1440p/4K physical budget rows pass. Project-owner visual
|
||||
acceptance and the remaining supported physical GPU classes stay open.
|
||||
|
||||
Add the main-world intermediate and implement bloom, ACES filmic tonemapping,
|
||||
colour grade, and vignette through the pack API. Supply half/quarter-resolution
|
||||
variants and preserve private viewports/UI.
|
||||
|
||||
**Acceptance:** every effect can be independently set to its neutral value;
|
||||
the preset is deterministic across resize/recreate; UI, paperdoll, portal, and
|
||||
appraisal surfaces retain their accepted colours; 1080p/1440p/4K captures show
|
||||
no clipping, haloing at the world/UI edge, stale frame, or resource leak; the
|
||||
slice meets its preset GPU/VRAM budget.
|
||||
|
||||
### Slice 2 — Tier-1 screen-space sun rays
|
||||
|
||||
**Implementation:** complete. Authored sun projection, the screen-space
|
||||
occlusion mask, declared sun/day-group/weather policy, pre-tonemap ray
|
||||
composition, and deterministic disabled gates are implemented and automated.
|
||||
The connected dawn/noon/dusk, weather, behind-camera, occlusion, and edge-
|
||||
flicker visual matrix remains open.
|
||||
|
||||
Project the existing authored sun position, build a screen-space occlusion
|
||||
mask, and composite weather-driven crepuscular rays before tonemapping. This
|
||||
slice deliberately has no shadow-map dependency.
|
||||
|
||||
**Acceptance:** clear dawn/dusk produces visible raking rays, noon makes them
|
||||
vanish, overcast/rain mutes them, the sun behind the camera or fully occluded
|
||||
produces none, and camera edges do not streak or flicker. The pack descriptor's
|
||||
sun-elevation and `activeDayGroup` policy deterministically produces those
|
||||
states without a second weather/clock owner. The exact same scene with Tier 1
|
||||
disabled returns to the Slice-0 digest from acdream's default
|
||||
retail-faithful renderer.
|
||||
|
||||
### Slice 3 — Tier-2 moving authored sun-and-moon dynamic shadows
|
||||
|
||||
**Implementation:** Stage 1 automated and project-owner live gates complete.
|
||||
Camera-relative stabilized cascades,
|
||||
outdoor gating, opaque and alpha-cutout casters, terrain/world receivers,
|
||||
headline caster membership, exact current animated transforms, bounded
|
||||
resident replay, GPU-flight ownership, and cached topology/dynamic-transform
|
||||
refresh were present at the sun-only checkpoint. The approved authored
|
||||
sun/dominant-moon/secondary-moon resolver and direction-versus-energy handoff
|
||||
are now present, and focused plus complete fresh-process automated validation
|
||||
pass. The project owner's 2026-08-22 live round accepted source alignment,
|
||||
temporal stability, desktop responsiveness, and the final exposure correction.
|
||||
Per-class diagnostics cover
|
||||
terrain commands, outdoor
|
||||
statics, buildings, animated statics, local/remote players, non-player
|
||||
creatures, other live dynamics, and equipped children without inventing tree
|
||||
or hostile-monster identity. Fixed-camera morning/afternoon artifacts are not
|
||||
moon evidence; the live-gate result and its exact boundary are recorded in the
|
||||
[Stage 1 live-gate report](../research/2026-08-22-atmospheric-stage1-live-gate.md).
|
||||
Stage 2 connected and closeout acceptance is active.
|
||||
|
||||
This is the campaign's headline slice. Add camera-relative cascades, opaque
|
||||
and alpha-cutout caster variants, animated SSBO transforms, shadow receivers,
|
||||
texel stabilization, outdoor gating, and weather/authored-directional-energy
|
||||
control. Select the exact rendered direction of the visible above-horizon sun,
|
||||
dominant haloed moon, or secondary moon according to the
|
||||
[celestial source contract](../research/2026-08-22-dereth-celestial-shadow-sources.md),
|
||||
while retaining retail's one `DirColor * DirBright` energy channel. Trees,
|
||||
monsters, players, houses/buildings, terrain, and ordinary outdoor statics
|
||||
participate through existing scene ownership.
|
||||
|
||||
**Acceptance:** in fixed-camera and live dawn/noon/dusk/night captures, shadows
|
||||
align with and change direction/length under Dereth's authored sun and selected
|
||||
dominant/secondary moon; overlap and no-source transitions are stable and do
|
||||
not snap to an unrelated body. Walking players and monsters cast and
|
||||
self-shadow from their current animated poses; foliage casts leaf/branch
|
||||
cutouts rather than rectangles; houses and procedural trees retain shadows
|
||||
through landblock publication/demotion without popping outside the chosen
|
||||
cascade transition tolerance; indoor/dungeon captures have no outdoor
|
||||
celestial directional shadow; portal/reconnect/device recreation leaves zero
|
||||
stale maps or owners. Acne, Peter-panning, cascade seams, distant depth-bias
|
||||
leaks, temporal pixelation/shimmer, and desktop performance pass the live user
|
||||
gate and subsequent Stage 2 matrix; the dense-Arwic CPU submission and GPU
|
||||
budgets pass.
|
||||
|
||||
### Slice 4 — Quality scaling and automatic compatibility
|
||||
|
||||
**Implementation:** complete. Low/Medium/High declarations, capability and
|
||||
memory admission, preset cost summaries, retained Display controls, the
|
||||
built-in Automatic checkbox, hysteretic Auto, diagnostics, asynchronous
|
||||
off-side candidate preparation, and atomic stable-boundary swaps are present
|
||||
and automated. The source-identical clean-snapshot RX 9070 XT matrix passes all
|
||||
30 current rows after the Low dense-pose CPU optimization. The integrated-AMD
|
||||
physical row proves that persistently over-budget Low returns Auto atomically to
|
||||
retail with a visible reason and a paired-retail framebuffer match. Active Low
|
||||
on that adapter and additional supported-adapter matrices remain open.
|
||||
|
||||
Land the Low/Medium/High presets, memory ceilings, capability-based preset
|
||||
availability, stable cascade fitting, resize handling, and optional hysteretic
|
||||
Auto selection. Auto may change resolution/range only at a stable frame
|
||||
boundary and must expose its current choice.
|
||||
|
||||
**Acceptance:** every supported preset retains all headline caster classes;
|
||||
weak-hardware fixtures select a valid lower preset or fail safely to acdream's
|
||||
default renderer;
|
||||
changing preset cannot leak, stall the render thread, invalidate streaming, or
|
||||
leave mixed-resolution resources; the quality/performance table is populated
|
||||
with measured physical-hardware results.
|
||||
|
||||
### Slice 5 — Tier-2+ volumetric shafts
|
||||
|
||||
**Implementation:** complete for the recorded sun-only scope. The declared
|
||||
volumetric pass reuses directional-shadow depth only when the selected source
|
||||
is the authored sun, consumes authored sun/weather/indoor inputs, composites
|
||||
before tonemapping, and has independent quality/step settings and automated
|
||||
failure gates. The reference-GPU low-sun 2,048-sample enabled/neutral A/B passes
|
||||
its incremental cost target. Moon selection does not enable moon shafts. The
|
||||
connected occluder/weather visual matrix and additional physical GPU classes
|
||||
remain open.
|
||||
|
||||
Reuse the directional shadow map for world-space light shafts only while its
|
||||
source is the authored sun. Drive density, strength, and colour from authored
|
||||
sun/weather inputs and composite before tonemapping. A selected moon produces
|
||||
directional shadows but no rays or volumetric shafts.
|
||||
|
||||
**Acceptance:** shafts respect terrain, trees, houses, and moving creatures;
|
||||
clear low sun is strongest, overcast and indoor scenes are muted/off; disabling
|
||||
shafts leaves Tier-2 shadow output unchanged; the incremental cost stays within
|
||||
the Tier-2+ budget.
|
||||
|
||||
### Slice 6 — Pack SDK and campaign closeout
|
||||
|
||||
**Implementation:** SDK deliverables complete and the Stage 1 project-owner
|
||||
gate passed on 2026-08-22. The v1
|
||||
manifest schema, semantic binding table, compatibility/failure guide,
|
||||
validator, built-in pack, and three buildable external samples are present and
|
||||
automated. Connected graphical package lifecycle, long-run convergence, the
|
||||
remaining physical GPU classes, and project-owner acceptance remain open; the
|
||||
30-row RX 9070 XT reference matrix is complete.
|
||||
|
||||
Publish the manifest/schema, semantic binding table, sample no-op pack,
|
||||
Atmospheric pack, compatibility diagnostics, authoring/validation tool, and
|
||||
failure-handling guidance. Run the full automated, connected, physical-display,
|
||||
performance, lifetime, portal, and screenshot matrix.
|
||||
|
||||
**Acceptance:** a clean external sample builds without App or Vulkan
|
||||
references; install/select/update/remove/fail/recover flows work; pack-off
|
||||
evidence from acdream's default retail-faithful renderer remains authoritative
|
||||
and unchanged; all resource ledgers
|
||||
converge after long play, reconnect, portal travel, pack disable, and device
|
||||
recreation; the project owner accepts the visual matrix before the campaign is
|
||||
declared shipped.
|
||||
|
||||
## Performance budget and measurement
|
||||
|
||||
The [measured pre-campaign baseline](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md#performance-baseline-and-the-binding-constraint)
|
||||
is **519.7 FPS with CPU/GPU p50 of 1.869/1.096 ms**, and dense towns are
|
||||
CPU-submission-bound. Fullscreen work may occupy currently idle GPU time, but
|
||||
it is not treated as free. Shadow cascades must protect the CPU submission
|
||||
path.
|
||||
|
||||
| Preset | Incremental GPU p50 / p99 at 1080p | Incremental render-CPU p50 / p99 | Pack-owned resident GPU memory |
|
||||
|---|---:|---:|---:|
|
||||
| Low | ≤ 2.0 / 3.0 ms | ≤ 0.15 / 0.50 ms | ≤ 64 MiB |
|
||||
| Medium | ≤ 3.25 / 4.50 ms | ≤ 0.25 / 0.75 ms | ≤ 128 MiB |
|
||||
| High | ≤ 4.50 / 6.00 ms | ≤ 0.35 / 1.00 ms | ≤ 256 MiB |
|
||||
|
||||
The current physical reference matrix is
|
||||
`artifacts/atmospheric-rendering/matrix-clean-snapshot-dense-linear-v20/` on an
|
||||
AMD Radeon RX 9070 XT (Vulkan 1.4.349, driver 2.0.395). The source-identical
|
||||
isolated commit and Release binary both identify `4876c970`; tracked source
|
||||
status is empty. All 30 rows pass. At capped 1080p, the exact 2,048-sample
|
||||
windows are:
|
||||
|
||||
| Selection | Incremental render-CPU p50 / p99 | Inclusive GPU p50 / p99 | Resident pack memory |
|
||||
|---|---:|---:|---:|
|
||||
| Low | 0.108 / 0.164 ms | 0.900 / 1.002 ms | 39.719 MiB |
|
||||
| Medium | 0.116 / 0.144 ms | 1.001 / 1.023 ms | 73.590 MiB |
|
||||
| High | 0.121 / 0.145 ms | 1.240 / 1.273 ms | 113.556 MiB |
|
||||
| Auto (settled High) | 0.122 / 0.146 ms | 1.211 / 1.222 ms | 113.556 MiB |
|
||||
|
||||
Every uncapped active row also passes. Across capped and uncapped active rows,
|
||||
the maximum measured p50/p99 and resident memory are: Low 0.108/0.164 ms CPU,
|
||||
1.070/1.080 ms GPU, 60.868 MiB; Medium 0.117/0.194 ms CPU, 1.152/1.163 ms GPU,
|
||||
103.583 MiB; High 0.121/0.159 ms CPU, 2.237/2.252 ms GPU, 238.141 MiB; and Auto
|
||||
0.122/0.166 ms CPU, 1.482/1.494 ms GPU, 145.856 MiB. All six retail rows record
|
||||
zero pack resources/work. At 4K, Low needs 131,302,400 bytes and Medium/initial
|
||||
Auto need 198,440,960 bytes, so those six capped/uncapped rows correctly report
|
||||
`ResourceUnavailable`, create no pack resources/work, and pass their strict
|
||||
paired-default framebuffer comparisons. This closes the current reference
|
||||
adapter, not the connected receiver A/B route or other supported/weak physical
|
||||
GPU classes.
|
||||
|
||||
Tier 2+'s separate low-sun physical A/B is recorded in
|
||||
`artifacts/atmospheric-rendering/volumetric-performance-ab-1080p.json`. Both
|
||||
runs pin High, 1080p uncapped, clear weather, 16.667° sun elevation, the same
|
||||
50 m / 180° / 10° camera, 9,498 casters, four cascades, and exact 2,048-sample
|
||||
windows. Enabling one volumetric draw over the neutral-strength run adds
|
||||
**0.189 ms GPU p50 / 0.219 ms p99**, **0.009 ms CPU p50 / 0.007 ms p99**, and
|
||||
4,147,200 resident bytes. The measured GPU p50 passes the Tier-2+ ≤0.40 ms
|
||||
reference target; connected weather/occluder behavior and other adapters still
|
||||
require their own rows.
|
||||
|
||||
acdream's default path has a stricter gate: zero new enhancement passes,
|
||||
images, buffers, submissions, or shader variants, with CPU/GPU deltas within
|
||||
the existing run-to-run noise envelope and deterministic reference captures
|
||||
unchanged. `NoOpRenderPackProductionIntegrationTests` pins the pre-campaign
|
||||
pass list, pipeline set, draw/dispatch tuple, framebuffer SHA-256, and complete
|
||||
resource ledger; the matrix's six physical retail rows independently record
|
||||
zero pack work at all three resolutions and both pacing modes.
|
||||
|
||||
Measurement protocol:
|
||||
|
||||
- Use existing asynchronous GPU timestamps and frame diagnostics. Never add a
|
||||
`glFinish`/device-idle-style measurement fence to the frame loop.
|
||||
- Runtime Auto compares the declared incremental CPU budget with pack-added
|
||||
target-preparation, shadow, post, and volumetric recording only. The complete
|
||||
enhanced main-world receiver recording is retained separately as an absolute
|
||||
CPU diagnostic; it is not itself an incremental delta. GPU accounting remains
|
||||
conservatively inclusive of the complete enhanced receiver pass and every
|
||||
resolved pack pass, exactly once after asynchronous resolution. Identical
|
||||
pack-off/on runs remain the authority for the final receiver CPU delta and
|
||||
the complete physical incremental A/B result.
|
||||
- Run capped and uncapped Release builds; record CPU/GPU p50, p95, and p99,
|
||||
FPS, draw/dispatch submissions, shadow-caster count, cascade draw count,
|
||||
transient/retained GPU bytes, and process working/private memory.
|
||||
- Compare pack off, Low, Medium, and High with identical camera paths, render
|
||||
resolution, active day group, authored celestial/time keyframe, entity set,
|
||||
and warmed residency.
|
||||
- Cover pinned dense Arwic, a foliage-heavy outdoor route, a building cluster,
|
||||
moving-player/monster combat, dawn/noon/dusk/night plus sun/moon/no-source
|
||||
transitions, clear/overcast/rain, a dungeon, portal travel, resize, reconnect,
|
||||
and a long lifetime run.
|
||||
- Measure 1920x1080, 2560x1440, and 3840x2160 on each supported physical GPU
|
||||
class. Report—not hide—unavailable presets.
|
||||
- No cascade may rerun CPU PView/portal traversal or issue per-object draws.
|
||||
The pass records CPU classification calls and submission counts so this is
|
||||
an enforced gate, not an architectural hope.
|
||||
- Pipeline creation and pack validation occur before atomic activation. Normal
|
||||
play may not hitch on first shadow, weather, caster, or quality use.
|
||||
|
||||
## Quality scaling for weak hardware
|
||||
|
||||
These are starting envelopes to validate, not asset or world guarantees.
|
||||
Distances are metres and are always clamped to current resident world data.
|
||||
|
||||
| Setting | Bloom/rays | Directional shadows | Volumetric shafts | Approx. depth-map memory at 32-bit depth |
|
||||
|---|---|---|---|---:|
|
||||
| Off / acdream default | Off | Off | Off | 0 MiB |
|
||||
| Low | Quarter resolution | 2 × 768² cascades, about 72 m maximum reach | Off by default | 4.5 MiB |
|
||||
| Medium | Half resolution | 3 × 1536² cascades, about 144 m maximum reach | Quarter resolution | 27 MiB |
|
||||
| High | Half/full as measured | 4 × 2048² cascades, about 240 m maximum reach | Half resolution | 64 MiB |
|
||||
|
||||
Additional scaling rules:
|
||||
|
||||
- Prefer reducing cascade count, shadow resolution, reach, bloom/ray
|
||||
resolution, and sample count before removing a feature's semantic
|
||||
correctness.
|
||||
- Preserve alpha-tested foliage and animated transforms at every shadow
|
||||
quality. A cheaper preset may look softer or end sooner; it may not turn a
|
||||
tree into a rectangle or freeze a monster's shadow.
|
||||
- Clamp resource dimensions and bytes before allocation. A capability probe
|
||||
that cannot support Low disables the pack and explains why.
|
||||
- Preset availability uses the selected Vulkan adapter's probed 2-D image and
|
||||
array-layer limits. Optional pack memory receives at most one eighth of its
|
||||
device-local heap, capped at 256 MiB resident and 512 MiB transient; Auto
|
||||
starts at Low when Medium is unavailable and acdream's default remains the fallback if
|
||||
Low cannot fit.
|
||||
- Optional Auto quality uses long hysteresis and stable frame-boundary swaps;
|
||||
it never oscillates cascade layouts frame to frame. If Low stays over its
|
||||
declared runtime GPU/CPU/resident budgets for 180 stable samples, Auto
|
||||
retires the complete pack and returns to acdream's default with the measured
|
||||
and declared limits in the visible failure reason.
|
||||
- 4K defaults may choose lower post-process resolution because Tier 1 pays
|
||||
approximately four times the 1080p pixel workload.
|
||||
|
||||
## Constraints and traps
|
||||
|
||||
This list carries forward every item in the findings' measured
|
||||
[shadow-specific constraints](../research/2026-08-21-terrain-and-atmospheric-rendering-findings.md#shadow-specific-constraints)
|
||||
and adds the current renderer's ownership and lifecycle boundaries.
|
||||
|
||||
1. **The renderer is the shipped pass-based Vulkan RHI.** Design against
|
||||
`IGpuDevice` / `IGpuFrame` / `IGpuPassEncoder` and explicit pass/pipeline
|
||||
descriptions. Do not revive an OpenGL backend or build a parallel renderer.
|
||||
2. **The current PView graph is authoritative.** Shadow and atmosphere passes
|
||||
consume its retained scene; they do not introduce a competing visibility
|
||||
owner or change punch/seal, shared-alpha, particle, or private-viewport
|
||||
ordering.
|
||||
3. **CPU submission is the limiting dimension.** Reusing the full bounded
|
||||
resident caster set is preferable to CPU-reculling it per cascade. GPU
|
||||
culling is the only planned escalation.
|
||||
4. **Alpha-tested foliage needs sampling and discard.** Reusing the existing
|
||||
empty `portal_depth` fragment shader would cast solid tree rectangles.
|
||||
5. **Animated casters use the existing N.5 SSBO transforms.** A second pose,
|
||||
animation tick, or entity owner is forbidden.
|
||||
6. **Indoors has no outdoor celestial directional shadow.** Dungeon/EnvCell
|
||||
authored ambient and local lighting wins; outdoor sun/moon directional
|
||||
shadows, sun rays, and sun shafts are gated off.
|
||||
7. **Cascades are camera-relative and streaming-bounded.** They may not use a
|
||||
fixed Dereth-wide extent, request landblocks, retain retired generations, or
|
||||
draw stale portal destinations.
|
||||
8. **Depth bias is specified in meaningful eye/world units.** A constant NDC
|
||||
bias spans approximately `b*d²/near` metres of eye depth at distance and can
|
||||
recreate issue #129's door-shaped holes through hills. Bias, normal offset,
|
||||
cascade projection, near/far fitting, and reversed-depth conventions must be
|
||||
tested together at near and far ranges.
|
||||
9. **“Shadow” is an overloaded project term.** Existing `shadow_objects` and
|
||||
`CPhysicsObj::add_shadows_to_cells` are collision registration, not light
|
||||
shadows. New names use `DirectionalShadowMap`, `ShadowCaster`, or
|
||||
`CelestialDirectionalShadow`; never generic `ShadowObject`.
|
||||
10. **Authored celestial position, directional energy, and weather are
|
||||
inputs.** Do not invent another celestial clock, light-energy channel,
|
||||
weather state, or hard-coded dawn/noon schedule. The directional map uses
|
||||
the visible above-horizon sun/dominant moon/secondary moon's exact rendered
|
||||
direction but retail's single interpolated `DirColor * DirBright` colour/
|
||||
energy channel, per the
|
||||
[celestial source research](../research/2026-08-22-dereth-celestial-shadow-sources.md).
|
||||
Rays and shafts remain sun-only and use the pack's declared sun-elevation
|
||||
curve and categorical `activeDayGroup` mapping. The decomp evidence proves
|
||||
the category reaches the frame, not an authored numeric ray intensity, so
|
||||
the enhancement mapping must remain explicit pack policy.
|
||||
11. **Atmosphere ordering is deliberate.** Rays/shafts composite before
|
||||
tonemapping; retained UI and private viewports remain outside main-world
|
||||
post-processing.
|
||||
12. **Transparency remains ordered.** The pack cannot flatten the retail
|
||||
world-alpha queue into an unordered shadow/post pass. Truly translucent
|
||||
surfaces cast no opaque shadow until separately designed.
|
||||
13. **Generation and GPU-flight lifetimes remain exact.** Pack images,
|
||||
descriptors, and pipelines retire through existing fences and converge on
|
||||
disable, resize, portal, reconnect, reset, failure, and device recreation.
|
||||
14. **4K is a distinct performance row.** Tier-1 effects scale with pixels;
|
||||
passing at 1080p is not evidence for 4K.
|
||||
15. **Do not repeat closed investigations.** High-res DAT precedence is not
|
||||
dropping overrides, AC detail textures are colour/alpha rather than normal
|
||||
maps, and the engine's historical DOT3 capability does not turn those
|
||||
assets into PBR inputs; these points are already falsified in the findings.
|
||||
16. **Caster evidence must not exceed source identity.** Diagnostics separately
|
||||
count terrain commands, outdoor statics, buildings, animated statics,
|
||||
local/remote players, non-player creatures, other live dynamics, and
|
||||
equipped children. Outdoor statics include trees but have no authoritative
|
||||
tree discriminator; non-player creatures include monsters but have no
|
||||
render-only hostile-monster-versus-NPC discriminator. Visual/connected
|
||||
acceptance must name those limits instead of fabricating narrower counts.
|
||||
|
||||
## What this does NOT do
|
||||
|
||||
- It does **not** change acdream's default retail-faithful rendering path, its
|
||||
expected output, or its authority in fidelity tests.
|
||||
- The Atmospheric shader pack does **not** own or depend on #226 detail
|
||||
texturing or the terrain-normal parity correction. Those remain separate
|
||||
Track A ports even though the project owner authorized their implementation
|
||||
in the same worktree.
|
||||
- It does **not** add PBR or fabricate normal, roughness, metalness, or material
|
||||
maps that AC's assets do not contain.
|
||||
- It does **not** change terrain vertices, collision triangles, walkability,
|
||||
slope response, physics shadow lists, movement, projectiles, or any Runtime
|
||||
physics/collision owner.
|
||||
- It does **not** change gameplay rules/state, network messages or ordering, or
|
||||
any Runtime gameplay/network owner.
|
||||
- It does **not** extend view distance, streaming radius, landblock residency,
|
||||
or PView visibility to find more shadow casters.
|
||||
- It does **not** add an indoor sun or replace authored EnvCell/local lights.
|
||||
- It does **not** turn moon texture brightness or mesh luminosity into another
|
||||
world-light energy channel, and it does not produce moon rays or moon shafts.
|
||||
- It does **not** post-process retained UI or silently restyle private
|
||||
paperdoll, appraisal, or portal viewports.
|
||||
- It does **not** promise that every pack or quality preset runs on unsupported
|
||||
hardware; compatibility failure is explicit and safely returns to acdream's default renderer.
|
||||
- Campaign AR does **not** outrank active M4 gameplay work. The project owner's
|
||||
explicit reprioritization authorizes this campaign without changing M4's
|
||||
milestone priority; #268 + TS-8 are already complete and retired.
|
||||
|
||||
## Completion gate
|
||||
|
||||
The design, Stage 1 implementation, automated validation, and project-owner
|
||||
live gate are complete. Stage 2 and closeout are active. The campaign becomes
|
||||
**shipped** only after the subsequent
|
||||
connected, full physical-hardware, lifetime, and visual gates above pass and
|
||||
the project owner accepts both sides of the final matrix:
|
||||
|
||||
- **pack off:** unchanged output, performance, ownership, and lifecycle from
|
||||
acdream's authoritative default retail-faithful renderer; and
|
||||
- **pack on:** moving authored sun-and-moon directional shadows from trees,
|
||||
monsters, players, and buildings; sun-only rays/shafts; scalable atmosphere,
|
||||
safe compatibility fallback, measured budgets, and clean long-lived resource
|
||||
convergence.
|
||||
|
|
@ -14,9 +14,9 @@ in a fresh Release process. It does not retry failures. Tests carrying an
|
|||
explicit non-hermetic `Lane` trait (`InstalledDat`, `PreparedPackage`, `Live`,
|
||||
`Manual`, `Timing`, `Windows`, `Linux`, or `SystemFont`), `Purpose=Diagnostic`, or
|
||||
`Status=KnownFailure` are excluded from the hermetic total and run through
|
||||
their owned lane instead. The graph currently contains 44 projects,
|
||||
including all 13 maintained .NET tools; data-dependent tools are built but are
|
||||
not executed as tests.
|
||||
their owned lane instead. The graph currently contains 54 projects,
|
||||
including all 17 maintained .NET tools and three render-pack SDK samples;
|
||||
data-dependent tools and SDK samples are built but are not executed as tests.
|
||||
|
||||
Build and dependency policy is repository-owned:
|
||||
|
||||
|
|
|
|||
227
docs/render-packs/README.md
Normal file
227
docs/render-packs/README.md
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
# Render-pack SDK v1
|
||||
|
||||
**Campaign:** Atmospheric Rendering / Shader Packs
|
||||
**Phase id:** **Campaign AR**
|
||||
**Contract version:** `RenderPackApi.Current == 1`
|
||||
|
||||
Render packs are opt-in, declarative graphics extensions. acdream's current
|
||||
retail-faithful renderer is always installed, remains the default and
|
||||
authoritative comparison path, and is restored as one complete transaction
|
||||
when a selected pack cannot run. A
|
||||
pack cannot access Vulkan, renderer internals, gameplay state, world streaming,
|
||||
or physics.
|
||||
|
||||
The public dependency is only
|
||||
`AcDream.Plugin.Abstractions`. Do not reference `AcDream.App`, Silk.NET, or a
|
||||
Vulkan binding. Three buildable external samples cover the API:
|
||||
|
||||
- [`AcDream.RenderPacks.NoOp`](../../samples/AcDream.RenderPacks.NoOp/) is the
|
||||
smallest discovery and activation conformance pack.
|
||||
- [`AcDream.RenderPacks.AtmosphericTier2`](../../samples/AcDream.RenderPacks.AtmosphericTier2/)
|
||||
declares the complete semantic atmospheric executor with deliberately
|
||||
renamed pack-owned IDs, embeds all referenced SPIR-V, and demonstrates
|
||||
moving authored sun-and-moon shadows for terrain, trees, buildings, players, and monsters.
|
||||
- [`AcDream.RenderPacks.ShadowsOnlyTier2`](../../samples/AcDream.RenderPacks.ShadowsOnlyTier2/)
|
||||
demonstrates that Tier 2 is composable: it requests the same selected-celestial
|
||||
caster/receiver semantics without Tier-1 post-processing or volumetric
|
||||
shafts.
|
||||
|
||||
## Quick start
|
||||
|
||||
1. Target `.NET 10` and reference `AcDream.Plugin.Abstractions` with runtime
|
||||
copy disabled. The acdream host supplies that assembly.
|
||||
2. Add [`plugin.json`](plugin-manifest-v1.schema.json), include
|
||||
`"kinds": ["renderPack"]`, and copy it beside the built entry DLL.
|
||||
3. Expose exactly one public, parameterless `IRenderPackPlugin` entry point.
|
||||
4. Construct immutable `RenderPackDescriptor` values and register them from
|
||||
`Register`. Registration must only publish declarations; do not open assets,
|
||||
compile shaders, start threads, or allocate native/GPU resources.
|
||||
5. Supply shader bytes lazily through `IRenderPackAssets.OpenRead`. Asset keys
|
||||
are forward-slash relative logical paths: never rooted, backslash-based, or
|
||||
`.`/`..` traversals.
|
||||
6. Build and run the SDK validator:
|
||||
|
||||
```powershell
|
||||
dotnet build samples/AcDream.RenderPacks.NoOp/AcDream.RenderPacks.NoOp.csproj -c Release
|
||||
dotnet run --project tools/RenderPackValidator/AcDream.Tools.RenderPackValidator.csproj -c Release -- samples/AcDream.RenderPacks.NoOp/bin/Release/net10.0
|
||||
```
|
||||
|
||||
Substitute `AcDream.RenderPacks.AtmosphericTier2` in both paths to validate
|
||||
the complete Tier 2/Tier 2+ example and its embedded shader interfaces.
|
||||
|
||||
The validator executes the managed registration entry point. Use it only on a
|
||||
pack you trust. It loads no App, RHI, or Vulkan assembly and creates no GPU
|
||||
objects. It validates the manifest, v1 declarations, referenced asset keys,
|
||||
SPIR-V stage/entry point and complete v1 binary interface, managed
|
||||
registration, and duplicate pack IDs. Hardware and driver compatibility remain
|
||||
client-side activation checks.
|
||||
|
||||
To launch a visible offline preview of acdream with the built-in Atmospheric
|
||||
pack and a disposable settings profile:
|
||||
|
||||
```powershell
|
||||
.\tools\launch-atmospheric-preview.ps1 -Preset High
|
||||
```
|
||||
|
||||
The preview starts `AcDream.App` with audio disabled, clears inherited
|
||||
`ACDREAM_*` live/automation/diagnostic state for that child, and leaves the
|
||||
user's normal acdream settings untouched. It records the binary identity,
|
||||
selected audio mode, and separate stdout/stderr logs beside the disposable
|
||||
profile. To exercise OpenAL explicitly, add `-EnableAudio`.
|
||||
|
||||
Vertex and fragment asset keys are independent opaque keys; they do not need
|
||||
matching basenames or a host shader-directory stem. On explicit selection the
|
||||
client opens each declared stream, validates it, copies the bytes into the
|
||||
isolated candidate, and creates shader modules from those immutable blobs.
|
||||
The pipeline retains neither the stream nor a path into the plugin directory.
|
||||
Each stage must be little-endian, word-aligned SPIR-V no larger than 16 MiB.
|
||||
|
||||
Shader-visible pack settings are deliberately capped at 64 declarations. The
|
||||
public `PackSettings` binding and value encoding are documented in the
|
||||
[`semantic binding table`](semantic-bindings-v1.md#packsettings-set-3-binding-8-256-bytes).
|
||||
A persisted user override wins the selected preset override, which wins the
|
||||
declaration default. Overrides are stored by stable pack ID plus setting ID;
|
||||
the host validates the selected descriptor's kind, invariant numeric grammar,
|
||||
range, step, and choice list before supplying the resolved scalars. No renderer
|
||||
object is exposed to managed code.
|
||||
|
||||
When a pack is selected, the retained Config page appends its declared
|
||||
Boolean, bounded Float/Integer, and Choice controls under **Graphics
|
||||
Enhancements**. Changing packs replaces only that optional tail; retail's 39
|
||||
authored Config rows remain unchanged. Numeric controls snap to declared bounds
|
||||
and steps, preset changes retain explicit user overrides, and a pack change
|
||||
starts with an empty valid override map for the new stable pack identity.
|
||||
|
||||
## Manifest
|
||||
|
||||
The authoritative machine-readable schema is
|
||||
[`plugin-manifest-v1.schema.json`](plugin-manifest-v1.schema.json).
|
||||
|
||||
| Field | Meaning |
|
||||
|---|---|
|
||||
| `id` | Stable lowercase logical plugin ID. It is persisted and must not be localized or reused. |
|
||||
| `displayName` | User-visible plugin name. |
|
||||
| `version` | Dotted `System.Version`-compatible package version. |
|
||||
| `entryDll` | Safe path beneath the plugin directory to the managed entry DLL. |
|
||||
| `apiVersion` | General `PluginApi` version. v1 is `1`; this is distinct from `RenderPackApi`. |
|
||||
| `dependencies` | Optional plugin IDs that must load first. |
|
||||
| `kinds` | Entry-point kinds. Include `renderPack`; omission means legacy `gameplay` only. A hybrid lists both. |
|
||||
|
||||
Install one plugin directory containing this manifest, the entry DLL, its
|
||||
private managed dependencies, and declared shader assets. Do not redistribute
|
||||
`AcDream.Plugin.Abstractions.dll` in that directory: type identity is shared
|
||||
from the host.
|
||||
|
||||
## Declaration schema
|
||||
|
||||
The C# records in `AcDream.Plugin.Abstractions.Rendering` are the public v1
|
||||
declaration schema. `RenderPackShaderAbi` publishes the corresponding numeric
|
||||
SPIR-V set, binding, block-size, and capacity constants. They are intentionally
|
||||
BCL-only and expose no Vulkan handle.
|
||||
|
||||
| Declaration | What the pack supplies | What the host owns |
|
||||
|---|---|---|
|
||||
| `RenderPackDescriptor` | Identity/version, highest tier, capabilities, resources, passes, replays, variants, presets, settings, atmosphere policy | Validation, candidate creation, activation and fallback |
|
||||
| `RenderResourceDeclaration` | Logical ID, portable format, extent, usage, lifetime, estimated bytes | Images/buffers, allocation, barriers, frame-flight retirement |
|
||||
| `RenderPassDeclaration` | Fixed hook, shader asset keys, semantic inputs, logical resource reads/writes | Render graph order, descriptor layout, pipeline, command recording |
|
||||
| `SceneReplayDeclaration` | One supported replay semantic, caster flags, 1–4 views | Resident caster selection and existing batched submissions |
|
||||
| `PipelineVariantDeclaration` | Base pipeline semantic, shader assets, compatible material flags, inputs | Visibility, mesh/material ownership, fixed renderer state |
|
||||
| `RenderQualityPreset` | Capability requirements, resource/setting overrides, optional execution hints, and p50/p99 CPU/GPU/VRAM ceilings | Availability, explicit selection and stable-boundary swaps |
|
||||
| `RenderSettingDeclaration` | Stable ID, kind, default, bounds/choices | Persistence and conditional Display UI |
|
||||
| `AtmospherePolicyDeclaration` | Ordered sun and selected-light elevation curves plus explicit `activeDayGroup` multipliers | Authored Dereth clock, celestial source, day group, weather and indoor state |
|
||||
|
||||
IDs use `^[a-z][a-z0-9._-]*$`, are case-insensitively unique within each
|
||||
declaration kind, and remain stable across updates. A pack must declare at
|
||||
least one quality preset and at most 64 settings. The SDK ceiling is 256 MiB pack-owned resident GPU
|
||||
memory, 16 MiB per SPIR-V asset, 16,384 pixels per absolute image dimension,
|
||||
256 image layers, and four scene-replay views. A physical device may expose a
|
||||
lower ceiling or reject a preset whose mandatory capabilities are absent.
|
||||
For API v1 the host admits optional pack memory from one eighth of the selected
|
||||
adapter's probed device-local heaps, capped at 256 MiB resident and 512 MiB
|
||||
transient multisample storage. Presets remain listed with exact limit reasons.
|
||||
Auto requires asynchronous timestamps and uses Low when Medium cannot fit. At
|
||||
runtime, Auto alone watches the selected preset's declared inclusive-GPU p99,
|
||||
pack-added CPU p99, and resident-GPU budgets. If Low remains over any of those
|
||||
budgets for 180 stable samples, the whole pack fails safely to acdream's
|
||||
default renderer with the measured and declared limits in the failure reason.
|
||||
Explicit Low remains selectable and is not silently disabled by the Auto
|
||||
performance policy.
|
||||
|
||||
A Tier-2 directional-shadow elevation curve must resolve to exactly zero at
|
||||
and below the authored 0-degree horizon. Every declared non-positive point
|
||||
must therefore have multiplier `0`; if the curve omits an exact 0-degree
|
||||
point, its first positive point must also be `0` so endpoint clamping or
|
||||
interpolation cannot manufacture a below-horizon directional shadow. The host still
|
||||
owns the independent no-selected-light-energy and indoor gates.
|
||||
|
||||
The built-in Atmospheric Low preset preserves the complete directional-shadow
|
||||
caster set (terrain, opaque and alpha-cutout world geometry, and both animated
|
||||
classes). It reduces cost with two 768 x 768 shadow maps and the ordinary
|
||||
six-pass, quarter-resolution separable post chain: sun occlusion, sun rays,
|
||||
bloom downsample, horizontal blur, vertical blur, and filmic composition. It
|
||||
does not remove a caster class or use the fused post-process hint.
|
||||
|
||||
`FusedAtmosphericPostProcess` is an optional external-pack Low-preset execution
|
||||
hint for the standard atmospheric graph; it is not built-in Low behavior. An
|
||||
opting-in shader pack implements the PackPass ABI below: the host feeds scene
|
||||
depth directly to sun rays and asks filmic to evaluate the declared bloom
|
||||
extraction and separable filter while composing the final image. This reduces
|
||||
command recording without disabling rays, bloom, or filmic composition. The
|
||||
host never infers the hint from pack identity; unknown hints, non-Low use, and
|
||||
incomplete standard graphs fail validation.
|
||||
|
||||
`MultiviewDirectionalShadowCascades` is a separate explicit Low-preset
|
||||
execution hint. The opting-in pack must implement three multiview caster
|
||||
variants. The host records one layered directional-depth
|
||||
pass with view mask `0b11`; `gl_ViewIndex` selects the exact two declared Low
|
||||
cascade matrices. Terrain, opaque, and alpha-cutout commands retain their
|
||||
ordinary pipeline, transform, cull, and cutout semantics. The preset must require
|
||||
`MultiviewDirectionalShadowCascades`; unsupported hardware makes that Low preset
|
||||
unavailable before allocation. A zero hint retains ordinary per-cascade passes.
|
||||
|
||||
Resources are declared in execution order: a pass cannot read a pack resource
|
||||
before an earlier pass writes it, and one pass cannot read and write the same
|
||||
resource. `WorldColor`, `SceneDepth`, and other renderer semantics are not pack
|
||||
resources and are named in `SemanticInputs` instead. API v1 exposes four
|
||||
sampled pass-input slots; buffers, `StructuredData`, and storage resources are
|
||||
reserved enum values and are rejected until a public binding contract exists.
|
||||
Colour image arrays are likewise reserved; v1 arrays are directional-depth
|
||||
maps. One declared pass writes at most one attachment. Only `ToneMap` and
|
||||
`AfterToneMapBeforePrivateViewports` may write directly to the host surface
|
||||
without naming a pack resource.
|
||||
|
||||
Every semantic input implies its capability and the descriptor must list that
|
||||
capability as required: world colour, scene depth/normals, authored sun/selected-
|
||||
celestial/day/weather facts, animation transforms, and directional maps cannot
|
||||
be treated as
|
||||
optional after a pass unconditionally declares them.
|
||||
|
||||
## Lifecycle and versioning
|
||||
|
||||
- Discovery calls `Register` but does not open assets or allocate GPU objects.
|
||||
- Installing a pack never selects it. The user selects a pack ID, version, and
|
||||
preset; `acdream default (retail-faithful)` is always available.
|
||||
- The client validates every declaration and selected asset, builds the full
|
||||
candidate beside the active retail graph, then swaps at a frame boundary.
|
||||
- Dispose the registration handle to withdraw the descriptor. The host also
|
||||
withdraws every handle before unloading its collectible plugin context.
|
||||
- `PluginApi` versions the general managed plugin ABI. `RenderPackApi` versions
|
||||
these graphics declarations. Additive enum/record support stays compatible;
|
||||
a breaking contract requires a new render-pack API version and explicit
|
||||
compatibility path.
|
||||
- Persisted identity is pack ID + pack version + preset ID, never list index.
|
||||
User-authored setting strings are keyed by the same stable pack identity and
|
||||
stable setting ID, never declaration or menu index.
|
||||
|
||||
"Device recreation" in the v1 SDK means full teardown of the old renderer,
|
||||
graphics context, and device, followed by construction and capability probing
|
||||
of a fresh context/device. Retail is authoritative until a fresh pack candidate
|
||||
validates and activates. The SDK does not promise live recovery of a pack or
|
||||
renderer after `VK_ERROR_DEVICE_LOST`; that error is terminal to the old device
|
||||
lifetime.
|
||||
|
||||
The complete campaign contract, budgets, and non-goals remain in
|
||||
[`2026-08-21-atmospheric-rendering.md`](../plans/2026-08-21-atmospheric-rendering.md).
|
||||
The public shader-facing contracts are the
|
||||
[`semantic binding table`](semantic-bindings-v1.md) and
|
||||
[`compatibility/failure guide`](compatibility-and-failure-v1.md).
|
||||
134
docs/render-packs/compatibility-and-failure-v1.md
Normal file
134
docs/render-packs/compatibility-and-failure-v1.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# 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 block does not match v1` | Recompile against the v1 binding table |
|
||||
| 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.
|
||||
68
docs/render-packs/plugin-manifest-v1.schema.json
Normal file
68
docs/render-packs/plugin-manifest-v1.schema.json
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "urn:acdream:render-pack:plugin-manifest:v1",
|
||||
"title": "acdream plugin manifest v1",
|
||||
"description": "Manifest shared by gameplay plugins and declarative render packs. A render pack includes renderPack in kinds.",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"displayName",
|
||||
"version",
|
||||
"entryDll",
|
||||
"apiVersion"
|
||||
],
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[a-z][a-z0-9._-]*$",
|
||||
"description": "Stable plugin identity. It is persisted; do not reuse or localize it."
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]+(?:\\.[0-9]+){1,3}$",
|
||||
"description": "Dotted System.Version-compatible package version."
|
||||
},
|
||||
"entryDll": {
|
||||
"type": "string",
|
||||
"minLength": 5,
|
||||
"maxLength": 512,
|
||||
"pattern": "^(?![A-Za-z]:)(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))[^\\\\]+\\.[dD][lL][lL]$",
|
||||
"description": "Safe forward-slash relative path to the managed entry assembly."
|
||||
},
|
||||
"apiVersion": {
|
||||
"type": "integer",
|
||||
"const": 1,
|
||||
"description": "AcDream.Plugin.Abstractions PluginApi version, not RenderPackApi."
|
||||
},
|
||||
"dependencies": {
|
||||
"type": "array",
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9._-]*$"
|
||||
},
|
||||
"default": []
|
||||
},
|
||||
"kinds": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["gameplay", "renderPack"]
|
||||
},
|
||||
"default": ["gameplay"],
|
||||
"description": "Omitting kinds preserves legacy gameplay-plugin behavior. A render pack must explicitly include renderPack."
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
381
docs/render-packs/semantic-bindings-v1.md
Normal file
381
docs/render-packs/semantic-bindings-v1.md
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
# Render-pack shader ABI and semantic bindings v1
|
||||
|
||||
**Campaign phase id:** **Campaign AR**
|
||||
**Render-pack API:** `1`
|
||||
|
||||
This is a SPIR-V binary contract over renderer-owned descriptors. It does not
|
||||
expose Vulkan descriptor sets, descriptor handles, images, buffers, samplers,
|
||||
command buffers, devices, queues, or fences to managed pack code. A pack only
|
||||
declares semantic inputs and supplies SPIR-V; the renderer validates the
|
||||
interface and binds immutable frame data.
|
||||
|
||||
## Semantic execution and stable identity
|
||||
|
||||
`RenderResourceSemantic`, `RenderPassSemantic`,
|
||||
`RenderPipelineVariantSemantic`, `RenderQualitySemantic`, and
|
||||
`RenderSettingSemantic` select renderer-owned execution roles. Pack-owned IDs
|
||||
remain stable persistence, UI, graph-edge, and diagnostic keys; the executor
|
||||
never recognizes a role by comparing an ID or asset-name string. Custom
|
||||
fullscreen declarations retain `Custom` semantics and are executed from their
|
||||
declared hooks and edges.
|
||||
|
||||
Every non-custom semantic is unique within its declaration kind. The complete
|
||||
Tier-2+ atmospheric executor requires its exact v1 pass/resource/pipeline-
|
||||
variant roles, hook order, scene replay, and graph edges. A Tier-2 pack may
|
||||
instead declare the directional-shadow component plus the technical custom
|
||||
`WorldColor` tone-map copy needed to present the HDR world. That profile still
|
||||
requires the exact shadow depth resource/pass, five caster/receiver variants,
|
||||
headline-caster replay, capabilities, settings, and elevation policy; it does
|
||||
not require bloom, rays, grading, vignette, or volumetric shafts. Any other
|
||||
partial or malformed semantic graph fails validation even when all pack-owned
|
||||
IDs remain syntactically valid.
|
||||
|
||||
Directional-shadow declarations use
|
||||
`SelectedCelestialDirectionalLight` together with the required
|
||||
`AuthoredCelestialDirectionalLight` capability and the descriptor's
|
||||
`DirectionalShadowLightElevationResponse`. `SunDirection`,
|
||||
`SunElevationResponse`, and `VolumetricShaftSunElevationResponse` remain the
|
||||
separate sun-specific atmosphere contract for rays and shafts.
|
||||
The public v1 point record remains named `SunElevationResponsePoint` for ABI
|
||||
compatibility; points stored in `DirectionalShadowLightElevationResponse` are
|
||||
interpreted against the selected celestial light's elevation.
|
||||
|
||||
## Fixed descriptor ownership
|
||||
|
||||
| Set | Binding | Shader declaration | Owner and use |
|
||||
|---:|---:|---|---|
|
||||
| 3 | 5 | `AtmosphericFrame` std140 uniform block | Renderer-owned camera/reconstruction, authored sun/day/weather and frame facts |
|
||||
| 3 | 6 | `DirectionalShadow` std140 uniform block | Renderer-owned cascade matrices, splits, shadow texture slot, shadow policy, and selected authored celestial direction |
|
||||
| 3 | 7 | `PackPass` std140 uniform block | Renderer-resolved pass resource slots, output facts and pass-local parameters |
|
||||
| 3 | 8 | `PackSettings` std140 uniform block | Renderer-resolved declaration-order scalar values for the selected preset |
|
||||
| 2 | 0 | `sampler2DArray uTextures[]` combined-image-sampler array | Existing global sampled-texture table; index with host-supplied slot IDs and `nonuniformEXT` |
|
||||
|
||||
Set 0 remains the renderer's existing storage-buffer set. A declared base
|
||||
pipeline variant inherits the exact renderer pipeline ABI it specializes; it
|
||||
does not gain arbitrary set-0 storage access. In particular,
|
||||
`ShadowCasterTransforms` reuses the renderer's existing per-instance transform
|
||||
publication rather than publishing a second animation pose.
|
||||
|
||||
Set 1 remains the current retail uniform layout at bindings 1–4. Pack shaders
|
||||
must not redeclare or alias it. Set 3 is strictly opt-in: retail pipeline
|
||||
layouts contain only sets 0–2, and the host creates no set-3 Vulkan object
|
||||
until a validated pack pipeline is activated. Bindings other than those in the
|
||||
table are reserved and validation rejects them.
|
||||
|
||||
## SPIR-V interface validation
|
||||
|
||||
Candidate activation and the standalone authoring validator inspect the
|
||||
actual SPIR-V binary before any shader module or pipeline is created. Each
|
||||
asset must expose exactly the declared vertex or fragment stage with entry
|
||||
point `main`. A fullscreen pass may declare only the sampled table at set 2,
|
||||
binding 0 when its declaration supplies a sampled semantic/resource input,
|
||||
plus the role-appropriate set-3 blocks. It may not access renderer-private set
|
||||
0 or retail set 1. A retained-scene pipeline variant may use only the base
|
||||
set-0/set-1 bindings documented for that exact semantic role, plus its allowed
|
||||
set-2/set-3 bindings.
|
||||
|
||||
Validation checks descriptor type and count, all set-3 uniform-block member
|
||||
types, offsets, strides, and total shapes, and any declared push block against
|
||||
the exact 96-byte retail layout below. Renderer storage buffers inherited by a
|
||||
variant must be read-only; storage images, arbitrary storage descriptors, and
|
||||
`OpImageWrite` are forbidden. An absent, malformed, aliased, writable, or
|
||||
undeclared interface rejects the whole candidate atomically to retail with a
|
||||
specific reason. Validation never exposes or accepts a Vulkan handle.
|
||||
|
||||
The binary member layout is validated against matching host structs. The
|
||||
checked-in shared render-pack GLSL includes are the byte-offset SSOT; authors
|
||||
include those definitions rather than maintaining a private copy. The tables
|
||||
below state the same values for review and tool diagnostics.
|
||||
|
||||
### `AtmosphericFrame` — set 3, binding 5, 160 bytes
|
||||
|
||||
```glsl
|
||||
layout(std140, set = 3, binding = 5) uniform AtmosphericFrame {
|
||||
vec4 uAtmosphereSunScreen; // @0: uv.xy, resolved ray strength, elevation degrees
|
||||
vec4 uAtmosphereSunColor; // @16: linear rgb, combined ray-policy multiplier
|
||||
vec4 uAtmosphereViewport; // @32: width, height, 1/width, 1/height
|
||||
vec4 uAtmosphereWeather; // @48: WeatherKind numeric, intensity, delta seconds, outdoor 0/1
|
||||
vec4 uAtmosphereSunDirection; // @64: surface-to-sun xyz, authored direction brightness
|
||||
vec4 uAtmospherePolicy; // @80: day group, group factor, shadow factor, shaft factor
|
||||
mat4 uAtmosphereInverseViewProjection; // @96
|
||||
};
|
||||
```
|
||||
|
||||
`uAtmosphereSunScreen.xy` uses normalized main-world viewport coordinates. The two
|
||||
strength fields are host-evaluated authored/policy facts; they do not create a
|
||||
second sky or weather owner. `uAtmosphereWeather.x` is numerically integral and must be
|
||||
interpreted with this v1 table, not guessed from colour or time:
|
||||
|
||||
| Numeric value | Weather kind |
|
||||
|---:|---|
|
||||
| 0 | Clear |
|
||||
| 1 | Overcast |
|
||||
| 2 | Rain |
|
||||
| 3 | Snow |
|
||||
| 4 | Storm |
|
||||
|
||||
All other values are reserved. `uAtmosphereWeather.y` is the transition
|
||||
intensity in the inclusive range 0–1.
|
||||
|
||||
`uAtmosphereSunDirection.xyz` is normalized and points from a lit surface
|
||||
toward the authored sun. `uAtmosphereSunScreen.z` is the resolved visible ray
|
||||
strength; `uAtmosphereSunColor.w` is the combined ray elevation/day-group/
|
||||
weather policy multiplier before a pass's own declared setting. In
|
||||
`uAtmospherePolicy`, `.x` is the numerically integral active day group, `.y` is
|
||||
that group's declared multiplier, `.z` is the declared directional-shadow
|
||||
elevation factor, and `.w` is the declared volumetric-shaft elevation factor.
|
||||
Shadow curves interpolate in sine-of-elevation space; shaft curves use
|
||||
smoothstep interpolation in elevation-degree space. These are exact values
|
||||
from the selected pack's `AtmospherePolicyDeclaration`, not built-in fallback
|
||||
curves. An accepted directional-shadow curve resolves to exactly zero at and
|
||||
below the authored 0-degree horizon; non-positive points must be zero, and a
|
||||
curve without an exact 0-degree point must make its first positive point zero.
|
||||
`uAtmosphereInverseViewProjection` reconstructs main-world positions
|
||||
from scene depth and the normalized viewport coordinates. Matrix convention
|
||||
and depth range match the shared push-block `viewProjection`.
|
||||
|
||||
### `PackPass` — set 3, binding 7, 64 bytes
|
||||
|
||||
```glsl
|
||||
layout(std140, set = 3, binding = 7) uniform PackPass {
|
||||
vec4 uPackParams0; // @0
|
||||
vec4 uPackParams1; // @16
|
||||
vec4 uPackParams2; // @32
|
||||
vec4 uPackParams3; // @48
|
||||
};
|
||||
```
|
||||
|
||||
The active semantic pass defines these sixteen scalar meanings. Unused values
|
||||
are zero. A pass cannot reinterpret values owned by a different pass. Sampled
|
||||
pass inputs use logical `textureIndexA` through `textureIndexD` in the shared push block;
|
||||
binding 7 carries scalar/vector policy and filter parameters, not descriptors.
|
||||
|
||||
The optional external-pack Low-preset `FusedAtmosphericPostProcess` execution
|
||||
hint uses these fixed values. The built-in Low preset does not declare it:
|
||||
|
||||
| Semantic pass | Values |
|
||||
|---|---|
|
||||
| `SunRays` | `uPackParams1 = (1, logicalMaskWidth, logicalMaskHeight, 0)`; input A is scene depth and the shader reconstructs the declared RGBA8 sun mask before radial integration |
|
||||
| `BloomDownsample` / both `BloomBlur` passes | Declared for the standard graph but not recorded for this preset; their threshold, knee, strength, offsets, and weights remain authoritative inputs to filmic |
|
||||
| `FilmicComposite` | `uPackParams1.z = 1`; `uPackParams2 = (bloomStrength, threshold, knee, hasVolumetric)`, `uPackParams3.xy = logicalBloomTexelStep`; inputs A/B/C are world color, sun rays, and optional volumetric shafts, and filmic evaluates the full separable bloom kernel before composition |
|
||||
|
||||
Zero flags retain the ordinary six-pass atmospheric graph. The built-in Low
|
||||
preset uses that zero-flag path; Medium, High, and external packs that do not
|
||||
opt in never use this fused ABI.
|
||||
|
||||
### Multiview directional-shadow cascades
|
||||
|
||||
The optional Low-preset `MultiviewDirectionalShadowCascades` execution
|
||||
hint requires the three `*MultiviewDirectionalShadowCaster` variants and the
|
||||
matching capability in the Low preset. The host begins one layered depth pass
|
||||
with `viewMask = 0b11`; each vertex shader indexes `uShadowWorldToClip` with
|
||||
`gl_ViewIndex`. Commands retain exact order, `BaseInstance`, the shared N.5
|
||||
world-transform arena, texture index/layer, fixed-function culling, alpha cutoff
|
||||
`0.05`, and both fitted cascade matrices. With no hint the host records the
|
||||
ordinary one-pass-per-cascade path. Unsupported hardware makes the hinted preset
|
||||
unavailable rather than silently selecting an over-budget execution form.
|
||||
|
||||
### `DirectionalShadow` — set 3, binding 6, 336 bytes
|
||||
|
||||
```glsl
|
||||
layout(std140, set = 3, binding = 6) uniform DirectionalShadow {
|
||||
mat4 uShadowWorldToClip[4]; // @0, @64, @128, @192
|
||||
vec4 uShadowSplitFarMeters; // @256
|
||||
vec4 uShadowControl; // @272
|
||||
vec4 uShadowBiasMeters; // @288
|
||||
uvec4 uShadowTextureAndFlags; // @304
|
||||
vec4 uShadowLightDirectionAndSource; // @320
|
||||
} directionalShadow;
|
||||
```
|
||||
|
||||
Field meanings are fixed:
|
||||
|
||||
| Field/component | Meaning |
|
||||
|---|---|
|
||||
| `uShadowWorldToClip[0..3]` | Texel-stabilized world-to-shadow-clip matrices; only the first `cascadeCount` entries are active |
|
||||
| `uShadowSplitFarMeters` | Far distance of cascades 0–3 in camera-eye metres |
|
||||
| `uShadowControl.x` | Directional shadow strength |
|
||||
| `uShadowControl.y` | Filter softness |
|
||||
| `uShadowControl.z` | Maximum shadow reach in metres, clamped to resident data |
|
||||
| `uShadowControl.w` | Cascade blend width in metres |
|
||||
| `uShadowBiasMeters.x` | Constant receiver/caster bias in world metres |
|
||||
| `uShadowBiasMeters.y` | Slope-scaled bias in world metres |
|
||||
| `uShadowBiasMeters.z` | Normal offset in world metres |
|
||||
| `uShadowBiasMeters.w` | Caster depth padding in world metres |
|
||||
| `uShadowTextureAndFlags.x` | Directional-depth array slot in set 2 |
|
||||
| `uShadowTextureAndFlags.y` | Active cascade count, 1–4 |
|
||||
| `uShadowTextureAndFlags.z` | Square shadow-map resolution in pixels |
|
||||
| `uShadowTextureAndFlags.w` | Flags; bit 0 means directional shadows are valid/enabled; bits 8–11 carry the fixed receiver PCF radius; remaining v1 bits are reserved and zero |
|
||||
| `uShadowLightDirectionAndSource.xyz` | Normalized direction from a lit surface toward the one authored celestial body selected for this shadow frame |
|
||||
| `uShadowLightDirectionAndSource.w` | Numerically integral selected-source kind from the table below |
|
||||
|
||||
Selected-source kinds are stable ABI values:
|
||||
|
||||
| Numeric value | Selected celestial source |
|
||||
|---:|---|
|
||||
| 0 | None / unavailable; the enabled flag must be clear |
|
||||
| 1 | Authored sun |
|
||||
| 2 | Dominant authored Dereth moon |
|
||||
| 3 | Secondary authored Dereth moon |
|
||||
|
||||
All other values are reserved. The selected source is a renderer-owned fact
|
||||
resolved from the current immutable Dereth sky frame. A pack does not identify
|
||||
sky objects by private index or create a second celestial clock. The host still
|
||||
publishes only one directional-depth array: sun and moons are alternative
|
||||
sources for the same bounded cascade work, not simultaneous shadow maps.
|
||||
|
||||
When producing a cascade, its zero-based cascade index uses the existing
|
||||
`uRenderPass` push-constant member. Consumer shaders choose a cascade from the
|
||||
eye-space distance and split values. No available selected celestial source, a
|
||||
selected source at or below its accepted horizon, no authored directional
|
||||
energy, indoors, and portal/login cover clear the enabled bit; shaders must not
|
||||
sample stale maps when it is zero. Binding 5 remains sun-specific for sun rays
|
||||
and volumetric shafts. Such passes must not substitute the selected moon
|
||||
direction for `uAtmosphereSunDirection`; when binding 6 selects a moon they
|
||||
treat its shadow map as unrelated to sun-shaft occlusion.
|
||||
|
||||
## Shared push constants
|
||||
|
||||
Every pipeline retains retail's exact shared 96-byte push-constant range. API
|
||||
v1 does not enlarge it:
|
||||
|
||||
```glsl
|
||||
layout(push_constant) uniform AcdreamPushBlock {
|
||||
mat4 viewProjection; // byte 0
|
||||
int drawIdOffset; // byte 64
|
||||
int lightingMode; // byte 68
|
||||
int renderPass; // byte 72
|
||||
int lightDebug; // byte 76
|
||||
uint textureIndexA; // byte 80
|
||||
uint textureIndexB; // byte 84
|
||||
float paramA; // byte 88
|
||||
float paramB; // byte 92
|
||||
} acdreamPush;
|
||||
```
|
||||
|
||||
Pack shader source may use the logical aliases `uTextureIndexC` and
|
||||
`uTextureIndexD`. The host stores their uint slot bits in the existing
|
||||
`paramA` and `paramB` words, and the shared Vulkan preamble exposes them as
|
||||
`floatBitsToUint(acdreamPush.paramA)` and
|
||||
`floatBitsToUint(acdreamPush.paramB)`. This is an exact bit reinterpretation,
|
||||
not numeric float conversion. Pack pass scalar/vector parameters belong in
|
||||
binding 7, so the two spare retail words are available for these input slots.
|
||||
|
||||
Do not reshape existing fields. A future additive growth requires matching
|
||||
host/shader layout tests and must remain inside the 128-byte Vulkan guarantee.
|
||||
A pack pass receives scalar/vector values through binding 7. Base pipeline
|
||||
variants use `viewProjection`, draw offset, texture slots and existing mode
|
||||
fields according to that base pipeline's contract.
|
||||
|
||||
## PackSettings (set 3, binding 8, 256 bytes)
|
||||
|
||||
`RenderSettingDeclaration` values use one fixed declaration-order block:
|
||||
|
||||
```glsl
|
||||
layout(std140, set = 3, binding = 8) uniform PackSettings {
|
||||
vec4 uPackSettings[16];
|
||||
};
|
||||
```
|
||||
|
||||
The descriptor may declare at most 64 settings, which the authoring validator
|
||||
and graphical host both enforce. Setting index `i` is its zero-based position in
|
||||
`RenderPackDescriptor.Settings`; it maps to
|
||||
`uPackSettings[i / 4][i % 4]`. Declaration order is therefore shader ABI and
|
||||
must remain stable within a compatible pack version. The resolved value is one
|
||||
IEEE-754 float: Boolean is `0.0` or `1.0`, Choice is its zero-based index in
|
||||
`Choices`, and Integer/Float parse with invariant culture before float
|
||||
conversion. Integer values are limited to the exactly representable inclusive
|
||||
range -16,777,216..16,777,216; Float values must remain finite in float32. A
|
||||
matching persisted user override wins a selected-preset `SettingOverride`,
|
||||
which wins the declaration default. User values remain invariant strings keyed
|
||||
by stable pack ID plus setting ID; before candidate activation the host rejects
|
||||
unknown IDs and values that fail kind, range, step, or choice validation. That
|
||||
failure retires the complete candidate to retail with an exact reason. The host
|
||||
zero-initializes the complete block, so unused slots and any value that fails
|
||||
defensive parsing are `0.0`; ordinary descriptor/selection validation prevents
|
||||
invalid values from reaching the bind.
|
||||
|
||||
Binding 7 remains pass-local host dynamics and filter parameters. It must not
|
||||
be overloaded with pack settings: doing so would make the same setting occupy
|
||||
different components in different passes and would prevent one stable public
|
||||
mapping. This fixed block is the complete v1 contract because it adds no
|
||||
descriptor handles, storage buffers, per-frame managed callbacks, or
|
||||
pass-specific setting schemas.
|
||||
|
||||
## Logical semantic table
|
||||
|
||||
The descriptor must list every semantic the shader reads. Listing a semantic
|
||||
does not guarantee device support; the corresponding `RenderCapability` must
|
||||
also be required when the table says so.
|
||||
|
||||
| `RenderSemanticInput` | Logical shader value | Source / lifetime | Capability prerequisite |
|
||||
|---|---|---|---|
|
||||
| `WorldColor` | Sampled main-world colour slot, excluding retained UI and private viewports | `textureIndexA-D` into set 2; current main-world frame | `MainWorldColorIntermediate` |
|
||||
| `SceneDepth` | Sampled main-world depth slot plus reconstruction matrix | `textureIndexA-D` and binding 5; current main-world frame | `SceneDepthSampling` |
|
||||
| `SceneNormals` | Sampled main-world normal slot | `textureIndexA-D` into set 2; current main-world frame | `SceneNormalSampling` |
|
||||
| `SunDirection` | Normalized authored surface-to-sun direction; no second clock | Binding 5 `uAtmosphereSunDirection`; current immutable world frame | `AuthoredSunDirection` |
|
||||
| `SelectedCelestialDirectionalLight` | Normalized direction and stable source kind for the one authored sun/moon selected to cast this frame's directional shadows | Binding 6 `uShadowLightDirectionAndSource`; current immutable world/sky frame | `AuthoredCelestialDirectionalLight` |
|
||||
| `SunScreenPosition` | Authored sun projected for the main-world viewport, plus valid/in-front state | Binding 5; current camera/world frame | `AuthoredSunScreenPosition` |
|
||||
| `ActiveDayGroup` | AC's categorical group plus descriptor-declared group/elevation multipliers | Binding 5 `uAtmospherePolicy`; current Runtime environment frame | `AuthoredWeather` |
|
||||
| `Weather` | Numeric `WeatherKind`, intensity and outdoor state | Binding 5 `uAtmosphereWeather`; current Runtime environment frame | `AuthoredWeather` |
|
||||
| `CameraMatrices` | Main-world view-projection and inverse, or directional cascade transforms required by the hook | Push `viewProjection` + binding 5 inverse; binding 6 for cascades | None beyond the hook's feature capability |
|
||||
| `ShadowCasterTransforms` | Exact existing per-instance/per-part transforms for retained eligible casters | Inherited base-pipeline set-0 ABI; current retained scene | `AnimatedCasterTransforms` |
|
||||
| `DirectionalShadowMaps` | Directional-depth table slot, active cascade count, matrices, splits and valid state | Binding 6 plus set 2; current outdoor shadow frame | `DirectionalShadowMaps` |
|
||||
| `FrameTime` | Monotonic frame delta in seconds; never a gameplay clock | Binding 5 `uAtmosphereWeather.z`; current frame | None |
|
||||
|
||||
Pack-declared `ResourceReads` are resolved deterministically to the pass input
|
||||
slots supplied by the host; v1 exposes up to four sampled inputs through
|
||||
`textureIndexA-D`. `DirectionalShadowMaps` uses the binding-6 texture slot and
|
||||
does not consume A-D. Slot assignment first walks sampled-image entries in
|
||||
`SemanticInputs` declaration order (`WorldColor`, `SceneDepth`, and
|
||||
`SceneNormals` when present), then sampled `ResourceReads` declaration order.
|
||||
The first input receives A, then B, C, and D. Duplicate inputs are invalid. The
|
||||
pack never chooses a global texture-table index. Resource IDs describe graph
|
||||
edges, not binding numbers. `ResourceWrites` are render targets chosen by the
|
||||
host and are not simultaneously sampled by the same pass.
|
||||
|
||||
`RenderResourceKind.Buffer`, `RenderFormatClass.StructuredData`, and
|
||||
`RenderResourceUsage.Storage` are reserved for an additive future contract.
|
||||
They have no public v1 descriptor binding and the v1 authoring validator
|
||||
rejects them instead of accepting an unbindable graph. V1 image arrays are
|
||||
reserved for `DirectionalDepth`; ordinary colour intermediates are `Image2D`.
|
||||
Each pass writes at most one declared attachment. A zero-write pass is valid
|
||||
only at `ToneMap` or `AfterToneMapBeforePrivateViewports`, where the host-owned
|
||||
main-world target is implicit.
|
||||
|
||||
## Texture-table sampling
|
||||
|
||||
Vulkan pack SPIR-V targets the same global table as retail shaders:
|
||||
|
||||
```glsl
|
||||
#extension GL_EXT_nonuniform_qualifier : require
|
||||
layout(set = 2, binding = 0) uniform sampler2DArray uTextures[];
|
||||
|
||||
vec4 sample2D(uint slot, vec2 uv) {
|
||||
return texture(uTextures[nonuniformEXT(slot)], vec3(uv, 0.0));
|
||||
}
|
||||
```
|
||||
|
||||
An ordinary 2-D texture is a one-layer array at layer zero. Array resources use
|
||||
their declared layer. Directional depth may be sampled as ordinary depth and
|
||||
compared/filtered in shader according to the declared shadow policy; the pack
|
||||
does not create a private sampler or descriptor. `0xFFFFFFFFu` is the
|
||||
unassigned texture-slot sentinel and must be checked before sampling an
|
||||
optional input.
|
||||
|
||||
## Hooks and availability
|
||||
|
||||
| `RenderPassHook` | Inputs valid at the hook | Output boundary |
|
||||
|---|---|---|
|
||||
| `ShadowDepthBeforeWorld` | Camera/selected celestial source/environment, cascade block, retained caster transforms | Declared directional-depth resources only; outdoor gating applies |
|
||||
| `AtmosphereBeforeToneMap` | HDR world colour when required, depth/normals when required, authored atmosphere and earlier declared resources | HDR pack intermediates; rays/shafts composite here |
|
||||
| `ToneMap` | HDR world colour and earlier atmosphere resources | Main-world display colour |
|
||||
| `AfterToneMapBeforePrivateViewports` | Tonemapped main-world colour and declared resources | Main world only; private viewports and retained UI remain outside |
|
||||
|
||||
Pass order is the descriptor order within a hook and never moves backward
|
||||
through this table. Discovery does not bind any of these blocks. Bindings exist
|
||||
only in the fully validated candidate and retire through normal frame-flight
|
||||
fences on fallback, resize, portal, reconnect, unload, or the teardown phase of
|
||||
device recreation. Recreation means a complete renderer/context/device
|
||||
teardown followed by a fresh context/device; it is not live recovery from
|
||||
`VK_ERROR_DEVICE_LOST`.
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
# Retail building and environment detail texturing — #226 port note
|
||||
|
||||
**Date:** 2026-08-21
|
||||
**Status:** IMPLEMENTED + CONNECTED-VISUAL-VERIFIED
|
||||
|
||||
This note is the implementation handoff requested by #226. The measurements
|
||||
below come from the already-completed
|
||||
[`2026-08-21 terrain and atmospheric rendering findings`](2026-08-21-terrain-and-atmospheric-rendering-findings.md),
|
||||
especially §§1–2. They are cited here rather than re-derived. The reachable
|
||||
preference/caller chain is also recorded in
|
||||
[`2026-07-10 detail texturing`](2026-07-10-detail-texturing.md).
|
||||
The A2 terrain-normal verdict and A3 subdivision disposition are recorded in
|
||||
the companion [`Terrain fidelity Track A report`](2026-08-21-terrain-fidelity-track-a-report.md).
|
||||
|
||||
## User-visible target and reachable caller trace
|
||||
|
||||
The issue title used to say “landscape,” but the Sept-2013 retail client does
|
||||
not expose live landscape detail through this option:
|
||||
|
||||
1. The Options checkbox writes `RenderPrefs.EnvironmentDetailTextures`.
|
||||
2. `Render::UpdateFromPreferences` (`0x0054d850`) explicitly changes
|
||||
`Current_Render_LandscapeDetailTextures` to `0` and calls
|
||||
`SmartBox::SetDetailTexturing(smartbox, 0, environmentEnabled)` at
|
||||
`0x0054d9f3`.
|
||||
3. `SmartBox::SetDetailTexturing` (`0x00451df0`) forwards
|
||||
`LScape::SetDetailTexturing(lscape, landscape, enabled, enabled, 0)`.
|
||||
4. `LScape::ChangeRegion` (`0x00506cb0`) independently installs the same
|
||||
category state: `(0, EnvDetail, EnvDetail, 0)`.
|
||||
|
||||
The four positions are landscape (0), building (1), environment/EnvCell (2),
|
||||
and ordinary object (3). The only reachable named-retail preference caller
|
||||
forces categories 0 and 3 off. `DrawPartCell` also clears ordinary-object
|
||||
detail. Therefore #226's scene target is **building shells and interior
|
||||
EnvCell geometry**, not outdoor terrain, scenery, creatures, or players. This
|
||||
also explains why acdream's existing checkbox is labelled “Building Detail
|
||||
Textures.”
|
||||
|
||||
## Authored source, size, and sampling
|
||||
|
||||
Detail data is reached through
|
||||
`Region(0x13000000).TerrainInfo.LandSurfaces.TexMerge.TerrainDesc[category]`:
|
||||
|
||||
```text
|
||||
SurfaceTextureId = TerrainDesc[category].TerrainTex.DetailTextureId
|
||||
tiling = TerrainDesc[category].TerrainTex.DetailTexTiling
|
||||
RenderSurfaceId = SurfaceTexture(SurfaceTextureId).Textures[0]
|
||||
rgba = decode(RenderSurface(RenderSurfaceId), level 0)
|
||||
```
|
||||
|
||||
For Dereth, enabled categories 1 and 2 both resolve
|
||||
`0x05001787 -> 0x06006D58`, a **256 x 256 A8R8G8B8** texture, with tiling
|
||||
**4**. The complete measured Dereth population is three textures across 33
|
||||
entries: `0x050012AF -> 0x060037D2` (64 x 64, 29 entries),
|
||||
`0x05001786 -> 0x06006D57` (256 x 256, two), and the enabled-category texture
|
||||
above (256 x 256, two). See the findings §2 table.
|
||||
|
||||
Retail uses wrap addressing in U and V and linear minification,
|
||||
magnification, and mip filtering. Detail UV is `baseUv * tiling`. The port
|
||||
therefore uploads each live category as a one-layer RGBA8 texture array with a
|
||||
full mip chain and the existing repeat/linear world sampler.
|
||||
|
||||
## Exact two-pass pseudocode
|
||||
|
||||
Retail has both a single-pass multitexture route and a two-pass fallback. The
|
||||
Vulkan port uses the fallback because it preserves the already-accepted base
|
||||
pass byte-for-byte and expresses the retail framebuffer blend directly.
|
||||
|
||||
```text
|
||||
enabled = DisplaySettings.BuildingDetailTextures // existing setting; no new option
|
||||
|
||||
buildingDetail = load_category(TerrainDesc[1])
|
||||
environmentDetail = load_category(TerrainDesc[2])
|
||||
|
||||
for each retail built-mesh material subset:
|
||||
draw_existing_base_subset_unchanged()
|
||||
|
||||
if enabled and subset belongs to a building or EnvCell:
|
||||
draw the same subset with its category detail texture
|
||||
// transparent/additive/inverse-alpha: detail follows its base
|
||||
// immediately, before the next delayed-alpha subset
|
||||
|
||||
for each replayed fragment:
|
||||
reject ordinary objects / landscape / scenery
|
||||
accept opaque, ClipMap, alpha, additive and inverse-alpha subsets
|
||||
|
||||
zMetres = positive_view_space_depth_in_metres
|
||||
fade = clamp((50 m - zMetres) / (50 m - 10 m), 0, 1)
|
||||
// full through 10 m; linear 10–50 m; exactly zero at/after 50 m
|
||||
|
||||
detail = sample(categoryTexture, baseUv * categoryTiling)
|
||||
src.rgb = detail.rgb * fade
|
||||
src.a = detail.a * fade
|
||||
|
||||
depth test = EQUAL opaque; LESS_OR_EQUAL transparent
|
||||
depth write = preserve base class // ON opaque; OFF transparent
|
||||
alpha-to-coverage = OFF // detail alpha is blend input
|
||||
blend op = ADD
|
||||
source = DEST_COLOR
|
||||
destination = ONE_MINUS_SRC_ALPHA
|
||||
```
|
||||
|
||||
Scaling **both** RGB and alpha by the fade is load-bearing. The resulting
|
||||
framebuffer multiplier is:
|
||||
|
||||
```text
|
||||
factor = 1 + fade * (detail.rgb - detail.a)
|
||||
```
|
||||
|
||||
Thus fade zero is an exact no-op and the full-strength neutral point is
|
||||
`detail.rgb == detail.a` channel-by-channel. It is not 0.5 gray.
|
||||
|
||||
### Built-mesh material coverage and order
|
||||
|
||||
The land-polygon `SurfaceType & 4` exclusion does **not** narrow this built-mesh
|
||||
port. Named-retail `RenderDeviceD3D::DrawEnvCell` (`0x0059f170`) and
|
||||
`DrawBuilding` (`0x0059f2a0`) install `curr_detail_surface` before calling
|
||||
`D3DPolyRender::DrawMesh`. `DrawMesh` (`0x0059d4a0`) bypasses delayed-alpha
|
||||
queuing while that surface is installed and passes detail enabled to
|
||||
`RenderMeshSubset` (`0x0059ca10`) for each material subset. The fallback then
|
||||
redraws that exact subset with the detail surface before proceeding. Therefore
|
||||
ClipMap, straight-alpha, additive, and inverse-alpha built-mesh subsets are
|
||||
included alongside plain opaque ones.
|
||||
|
||||
The Vulkan port first filters the opaque object command stream to coalesced
|
||||
runs containing at least one category-1 building instance; nonbuilding-only
|
||||
commands never reach the detail pipeline. A mixed instanced command remains in
|
||||
the replay and `mesh_detail` rejects its ordinary instances individually. The
|
||||
accepted opaque path stays batched, while transparent subsets preserve
|
||||
immediate base/detail adjacency. Their separate detail pipeline
|
||||
keeps depth writes disabled, matching the base subset's accepted depth
|
||||
contract. This prevents another shell/object contribution from being
|
||||
composited between the base and its detail contribution.
|
||||
|
||||
Opaque detail uses depth compare **EQUAL** against the exact geometry just
|
||||
written by the base pass. Vulkan depth is per sample, so on MSAA ClipMap edges
|
||||
the detail affects only samples whose base alpha-to-coverage mask wrote depth.
|
||||
The detail pipeline itself deliberately keeps alpha-to-coverage off: detail
|
||||
alpha controls `ONE_MINUS_SRC_ALPHA` in the retail blend and is not the base
|
||||
coverage mask. Transparent bases do not write depth, so their adjacent detail
|
||||
uses `LESS_OR_EQUAL` with depth writes still off.
|
||||
|
||||
One bounded ordering seam is explicit: retail bypasses its delayed-alpha queue
|
||||
while `curr_detail_surface` is installed, whereas acdream retains its already-
|
||||
authoritative shared alpha-queue order and inserts the detail draw immediately
|
||||
after the corresponding base draw. This does not narrow material coverage or
|
||||
change base coverage/blend/depth behavior; it avoids making the checkbox
|
||||
reorder the default transparent scene. The connected acceptance matrix must
|
||||
still exercise overlapping transparent building/EnvCell surfaces.
|
||||
|
||||
## Brightening decision
|
||||
|
||||
The port keeps retail's `DEST_COLOR + ONE_MINUS_SRC_ALPHA` verbatim. The
|
||||
findings measured factors **1.177**, **1.204**, and **1.033** for the three
|
||||
Dereth textures; the live Dereth building/environment category uses the
|
||||
1.033-factor texture. That slight brightening is intentional retail parity,
|
||||
not an acceptance failure.
|
||||
|
||||
Changing the destination factor to `ZERO` would be a visual correction rather
|
||||
than a port. Exposing both behaviors behind one retail checkbox would also
|
||||
make the option ambiguous. If a roughening-corrected material is wanted later,
|
||||
it belongs as an explicitly named opt-in enhancement/shader-pack policy with a
|
||||
registered divergence. It is not part of #226.
|
||||
|
||||
## What the reverted experiment got wrong
|
||||
|
||||
The experiment described by `c25d6186` was never committed as renderer code;
|
||||
it was reverted from the worktree with `git checkout`. Its useful failure
|
||||
record remains in that issue commit. It differed from the verified contract in
|
||||
five material ways:
|
||||
|
||||
- It targeted outdoor landscape, while the live setting enables building and
|
||||
environment categories and forces landscape off.
|
||||
- It built a per-terrain-type texture array, while retail selects one
|
||||
category-scoped surface and scalar tiling for each draw path.
|
||||
- It used `base * detail * 2` (`MODULATE2X`) instead of retail's framebuffer
|
||||
blend.
|
||||
- It assumed 128 gray was neutral; retail neutral is RGB equal to alpha.
|
||||
- Its acceptance prohibited an overall brightness change, although retail's
|
||||
measured blend intentionally brightens these textures.
|
||||
|
||||
The old OpenGL-specific array/bindless wiring is also not reusable in the
|
||||
current Vulkan-only RHI.
|
||||
|
||||
## Corrected acceptance
|
||||
|
||||
- With `BuildingDetailTextures=false`, no detail replay is submitted and the
|
||||
current base rendering remains unchanged.
|
||||
- With it `true`, toggling the existing Options checkbox **visibly changes
|
||||
buildings and interior/EnvCell surfaces** without a restart. The connected
|
||||
2026-08-21 Facility Hub A/B/A gate applied the real Config checkbox on ->
|
||||
off -> restored-on and captured the same nearby walls/floor after each
|
||||
transition. Static right-wall mean absolute RGB error was 2.132 for on/off
|
||||
versus 0.007 for original-on/restored-on; the floor row was 3.385 versus
|
||||
0.013. The persisted setting was observed false during B, restored true,
|
||||
and the session ended with ACE-confirmed graceful logout.
|
||||
- Outdoor terrain, ordinary scenery/objects, creatures, and players do not
|
||||
gain this overlay.
|
||||
- Every built building/EnvCell material subset is eligible: opaque, ClipMap,
|
||||
straight alpha, additive, and inverse alpha. Transparent base/detail draws
|
||||
remain adjacent in acdream's authoritative shared alpha order.
|
||||
- Opaque object replay submits only command runs containing a building; mixed
|
||||
commands are filtered per instance. Depth equality inherits the base pass's
|
||||
per-sample ClipMap coverage without applying A2C to detail alpha.
|
||||
- Detail is full through positive view depth 10 m, fades linearly over 10–50
|
||||
m, and is an exact no-op at and beyond 50 m.
|
||||
- Category source, 256 x 256 size, tiling 4, repeat addressing, and linear mip
|
||||
sampling match the measured Dereth data.
|
||||
- The retail 1.033 live-category brightening is expected. There is no
|
||||
`dst=ZERO` correction mode hidden behind the retail checkbox.
|
||||
- Physics, collision, walkability, and geometry are untouched.
|
||||
75
docs/research/2026-08-21-terrain-fidelity-track-a-report.md
Normal file
75
docs/research/2026-08-21-terrain-fidelity-track-a-report.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# Terrain fidelity Track A report
|
||||
|
||||
**Date:** 2026-08-21
|
||||
**Status:** REPORT ACCEPTED BY OWNER DIRECTION; A1/A2 IMPLEMENTED; A3 REJECTED
|
||||
|
||||
This report answers Track A from the measured
|
||||
[`terrain and atmospheric rendering findings`](2026-08-21-terrain-and-atmospheric-rendering-findings.md).
|
||||
It cites that evidence rather than repeating its measurements, and it does not
|
||||
reopen the findings' three refuted claims. The project owner subsequently
|
||||
authorized implementation. No physics or collision behavior changed.
|
||||
|
||||
## A1 — #226 detail-texture overlay
|
||||
|
||||
The complete source/size/tiling, blend, neutral point, distance units, setting
|
||||
gate, material-ordering contract, reverted-experiment analysis, and connected
|
||||
A/B/A evidence are in the
|
||||
[`#226 retail building/EnvCell detail-texturing port note`](2026-08-21-retail-building-detail-texturing-pseudocode.md).
|
||||
|
||||
The report conclusions are:
|
||||
|
||||
- The reachable user-visible target is **building shells and interior EnvCell
|
||||
geometry**, not outdoor terrain. The Options preference caller and
|
||||
`LScape::ChangeRegion` both install category state `(landscape=0,
|
||||
building=enabled, environment=enabled, ordinary=0)` through
|
||||
`SmartBox::SetDetailTexturing`.
|
||||
- The existing **Building Detail Textures** checkbox is the sole setting gate.
|
||||
No second option was added. Toggling it now visibly changes the connected
|
||||
Facility Hub scene without a restart.
|
||||
- The port keeps retail's `DEST_COLOR + ONE_MINUS_SRC_ALPHA` blend verbatim,
|
||||
including the measured slight brightening. `dst=ZERO` would be an opt-in
|
||||
visual correction, not parity; exposing both meanings behind the one retail
|
||||
checkbox would make that preference ambiguous.
|
||||
- The reverted experiment targeted landscape, built the wrong texture-array
|
||||
shape, used `base * detail * 2`, assumed 128 gray was neutral, and rejected
|
||||
the brightness change that the measured retail blend actually produces.
|
||||
|
||||
## A2 — terrain vertex normals
|
||||
|
||||
**Verdict: parity gap. Retail smooths shared terrain vertices.**
|
||||
|
||||
The decisive named-retail function is
|
||||
`CLandBlockStruct::calc_lighting` at `0x00531700` in
|
||||
[`acclient_2013_pseudo_c.txt`](named-retail/acclient_2013_pseudo_c.txt):
|
||||
|
||||
1. It zeroes one three-float accumulator for every shared landblock vertex.
|
||||
2. From `0x00531774` through `0x005317F6`, it walks every terrain polygon and
|
||||
adds that polygon's plane normal (`CPolygon + 0x20..0x28`) to the
|
||||
accumulator of each of its three vertex IDs.
|
||||
3. From `0x00531817` through `0x00531886`, it normalizes every accumulated
|
||||
vector, falling back to `(0, 0, 1)` only for a degenerate sum.
|
||||
4. The following sunlight/ambient loop dots those normalized shared-vertex
|
||||
normals with `LScape::sunlight` and writes per-vertex lighting.
|
||||
|
||||
That is incident-face normal averaging, not flat per-face shading. The
|
||||
WorldBuilder-derived `TerrainUtils.GetNormal` identified in the findings §4
|
||||
is therefore a simplified tool path and not the retail oracle.
|
||||
|
||||
The approved port is in `LandblockMesh.BuildRetailVertexNormals`. It uses the
|
||||
same split hash and exact emitted triangle topology, accumulates each
|
||||
normalized incident face normal at the shared 9 x 9 height-sample vertex, and
|
||||
normalizes the sum. Tests independently reconstruct the average from emitted
|
||||
positions/indices and prove every position and index is unchanged.
|
||||
|
||||
This is lighting-only parity: the 81 height samples, 128 triangles, split
|
||||
directions, terrain surface, collision triangles, walkability, and physics
|
||||
owners are byte-for-byte/topology-equivalent to the prior path.
|
||||
|
||||
## A3 — subdivision
|
||||
|
||||
**Agree: the standing “not worth doing” recommendation survives.** The
|
||||
findings §4 already establishes that the 9 x 9 samples are height-table
|
||||
quantized, so subdivision cannot recover missing terrain detail; changing the
|
||||
surface would create physics divergence, while coplanar subdivision would only
|
||||
interpolate a surface whose retail-correct shared-vertex smoothing is now
|
||||
already present. No subdivision work is scheduled.
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Campaign AR Stage 1 automated gate
|
||||
|
||||
**Date:** 2026-08-22
|
||||
**Verdict:** PASS — every Stage 1 gate that does not require physical visual or
|
||||
desktop-performance judgment is complete. Those judgments were deliberately
|
||||
outside this automated report and subsequently passed in the
|
||||
[Stage 1 live-gate report](2026-08-22-atmospheric-stage1-live-gate.md).
|
||||
|
||||
## Scope
|
||||
|
||||
This report covers the current authored-celestial implementation: the visible
|
||||
above-horizon sun, dominant haloed moon, secondary moon, and no-source states;
|
||||
the selected source's direction-versus-energy handoff; the 336-byte render-pack
|
||||
shadow ABI; the unchanged authoritative retail path; and local deterministic
|
||||
performance and lifetime contracts.
|
||||
|
||||
It does not itself claim that a physical display proves shadow alignment,
|
||||
source-transition continuity, temporal pixelation/shimmer quality, or desktop
|
||||
frame pacing. It did not itself begin Campaign AR Stage 2; the subsequent
|
||||
project-owner live approval did.
|
||||
|
||||
## Results
|
||||
|
||||
| Gate | Result |
|
||||
|---|---|
|
||||
| Shader compilation | 24/24 Vulkan shader pairs ready |
|
||||
| Retail shader preservation | all 18 pre-campaign SPIR-V SHA-256 oracles exact; no tracked retail SPIR-V change |
|
||||
| Focused App renderer validation | 344/344 passed |
|
||||
| Core sky loader | 14/14 passed |
|
||||
| SDK and standalone pack validator | 30/30 passed |
|
||||
| MossTank plugin regression | 48/48 passed |
|
||||
| Forced locked restore | passed for the complete solution graph |
|
||||
| Complete Release build after locked restore | passed, 0 warnings, 0 errors |
|
||||
| Fresh-process hermetic Release gate | 14,928/14,928 passed, 0 skipped, 0 failed, 14 assemblies |
|
||||
| App assembly inside the complete gate | 5,823/5,823 passed |
|
||||
|
||||
The release evidence bundle is
|
||||
[`artifacts/atmospheric-rendering/stage1-moon-release-gate/`](../../artifacts/atmospheric-rendering/stage1-moon-release-gate/).
|
||||
Its `release-gate-summary.json`, TRX files, logs, environment inventory, and
|
||||
`SHA256SUMS.txt` are the machine-readable authority for the fresh-process total.
|
||||
|
||||
## Performance and lifetime coverage
|
||||
|
||||
The complete App gate includes these deterministic contracts:
|
||||
|
||||
- `DirectionalShadowCasterFrameTests.WarmStableFrame_AllocatesZero` builds a
|
||||
9,500-static-caster scene, warms it, then performs 256 stable frames with
|
||||
zero managed bytes, no additional scene-index copy, no classification, and
|
||||
no topology rebuild.
|
||||
- `DirectionalShadowCasterFrameTests.WarmDenseChangedFrames_AllocateZeroAndReadNoSceneRecords`
|
||||
proves dense animated-transform refresh stays allocation-free and does not
|
||||
reread scene records.
|
||||
- `AtmosphericCpuStageProfilerTests.WarmedObservationAllocatesNothing` and
|
||||
`AtmosphericGpuTimerSamplingTests.WarmSamplingDecisionsAllocateZero` keep the
|
||||
measurement path allocation-free after warmup.
|
||||
- `RenderPackLongCycleConvergenceTests.RepeatedPackResizeFailureGenerationAndFlightCyclesConvergeExactly`
|
||||
repeatedly crosses Low, Medium, High, retail selection, resize, injected
|
||||
failure/recovery, both frame-flight slots, render-generation replacement,
|
||||
and terminal disposal for 12 cycles. Every pack resource, registration,
|
||||
receiver candidate, transform owner, texture slot, and pipeline-format lease
|
||||
returns to its exact baseline.
|
||||
- `RenderPackLongCycleConvergenceTests.DeviceRecreationIsFullRendererTeardownThenANewContextAndDevice`
|
||||
proves recreation is complete old-renderer/context/device teardown followed
|
||||
by an independent activation generation on a fresh device.
|
||||
|
||||
These are CPU-side and recording-RHI gates. The historical physical AMD rows
|
||||
remain valid for their exact pre-moon binaries and stated scope, but they are
|
||||
not reused as current sun-and-moon image-quality or desktop-performance proof.
|
||||
|
||||
## Authoritative-path and scope audit
|
||||
|
||||
- Shader regeneration expands includes and injects pack-only definitions only
|
||||
for pack shaders. Unchanged retail sources retain their existing committed
|
||||
binaries; the source manifest still forces a recompile after a real source
|
||||
edit.
|
||||
- The exact pre-campaign retail SPIR-V oracle passes after ordinary shader
|
||||
regeneration.
|
||||
- No source file under `src/AcDream.Runtime` changed for this campaign gate.
|
||||
- No physics or collision source changed.
|
||||
- No retail GLSL source changed.
|
||||
- Pack-off production integration continues to require zero enhancement passes,
|
||||
resources, casters, cascades, draws, or dispatches and its pinned framebuffer
|
||||
and resource ledger remain exact.
|
||||
|
||||
## Pending project-owner gate
|
||||
|
||||
When the desktop is healthy, launch the corrected Release client against ACE
|
||||
and stop for the project owner to judge:
|
||||
|
||||
- sun, dominant-moon, and secondary-moon shadow alignment;
|
||||
- sun-to-moon, moon-to-moon, and no-source transitions;
|
||||
- temporal pixelation/shimmer during camera and celestial motion; and
|
||||
- desktop smoothness, frame pacing, and FPS behavior.
|
||||
|
||||
Stage 2 and campaign closeout remain gated on that explicit approval.
|
||||
46
docs/research/2026-08-22-atmospheric-stage1-live-gate.md
Normal file
46
docs/research/2026-08-22-atmospheric-stage1-live-gate.md
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Campaign AR Stage 1 live gate
|
||||
|
||||
**Date:** 2026-08-22
|
||||
**Verdict:** PASS — project-owner accepted; Stage 2 authorized
|
||||
|
||||
## Scope
|
||||
|
||||
This is the physical-display and desktop-performance stop that followed the
|
||||
[Stage 1 automated gate](2026-08-22-atmospheric-stage1-automated-gate.md). It
|
||||
records the project owner's live acceptance of the opt-in Atmospheric pack; it
|
||||
is not final Campaign AR acceptance.
|
||||
|
||||
The owner exercised the Vulkan client against the local ACE server through the
|
||||
Stage 1 correction rounds: visible authored sun and moon shadows, selection and
|
||||
configuration persistence, temporal texture/shadow shimmer, frame pacing and
|
||||
desktop responsiveness, world selection, fullscreen, and final exposure. After
|
||||
the exposure correction the owner reported **“Looks good!”** and directed the
|
||||
campaign to synchronize with main and proceed autonomously through Stage 2.
|
||||
|
||||
The opt-in Atmospheric exposure changed from `1.00` to `0.80`. The retail
|
||||
renderer remains the default and authoritative path. Physics, collision,
|
||||
gameplay, and network behavior are unchanged.
|
||||
|
||||
## Matched exposure evidence
|
||||
|
||||
The final comparison pinned time, day group, sky, weather, MSAA, route, and
|
||||
camera framing. Its five screenshots and machine-readable metadata are under
|
||||
[`artifacts/atmospheric-rendering/live-exposure-comparison-exposure080-20260822-125033/`](../../artifacts/atmospheric-rendering/live-exposure-comparison-exposure080-20260822-125033/).
|
||||
|
||||
| Scene/preset | Retail mean luminance | Atmospheric mean luminance | Delta | Atmospheric p95 delta | Saturation delta | Clipped pixels |
|
||||
|---|---:|---:|---:|---:|---:|---:|
|
||||
| Outdoor / High | 0.1090 | 0.1003 | -8.0% | +6.8% | +29.8% | 0% |
|
||||
| Interior / High | 0.2711 | 0.2813 | +3.8% | -3.5% | +5.2% | 0% |
|
||||
| Outdoor / Low | 0.1090 | 0.1011 | -7.2% | +8.4% | +30.1% | 0% |
|
||||
|
||||
Before the correction, Atmospheric High measured +19.6% outdoors and +24.5%
|
||||
indoors. The `0.80` correction removes that overexposure without clipping.
|
||||
The final outdoor Atmospheric High capture reports 6,613 shadow casters, four
|
||||
cascades, 95,260,912 resident GPU bytes, and 285 performance samples.
|
||||
|
||||
## Acceptance boundary
|
||||
|
||||
This gate does not fabricate evidence for a second connected remote player,
|
||||
portal/reconnect or device-recreation lifecycle, external package flows,
|
||||
long-run convergence, or unavailable physical GPU classes. Those remain Stage
|
||||
2/closeout rows, followed by the final project-owner acceptance gate.
|
||||
200
docs/research/2026-08-22-dereth-celestial-shadow-sources.md
Normal file
200
docs/research/2026-08-22-dereth-celestial-shadow-sources.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# Dereth celestial shadow sources
|
||||
|
||||
**Date:** 2026-08-22
|
||||
**Status:** measured retail-DAT and named-retail finding; implementation input
|
||||
for Campaign AR
|
||||
**Scope:** identify the Dereth sun/moons and define the opt-in pack's dominant
|
||||
directional-shadow source. This note does not change the retail rendering path.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Dereth's Region `0x13000000` consistently authors three moving celestial
|
||||
meshes across all 20 day groups:
|
||||
|
||||
1. `0x01001348` is the sun disk.
|
||||
2. `0x01001F6A` is the large, haloed moon and is the dominant lunar source.
|
||||
3. `0x01001F67` is the smaller secondary moon.
|
||||
|
||||
Retail does **not** provide a separate lighting colour or intensity for each
|
||||
mesh. `SkyDesc::GetLighting` produces one interpolated directional vector,
|
||||
colour, and brightness from `SkyTimeOfDay.DirHeading`, `DirPitch`, `DirColor`,
|
||||
and `DirBright`. The opt-in atmospheric pack therefore uses the selected
|
||||
visible celestial mesh only for shadow **direction**. Colour and energy remain
|
||||
the single AC-authored directional-light values.
|
||||
|
||||
The deterministic priority is:
|
||||
|
||||
1. visible sun whose transformed centre is above the horizon;
|
||||
2. visible large/haloed moon whose transformed centre is above the horizon;
|
||||
3. visible secondary moon whose transformed centre is above the horizon;
|
||||
4. no directional shadow source.
|
||||
|
||||
This is a pack enhancement, not a claim that retail cast real-time moon
|
||||
shadows.
|
||||
|
||||
## Evidence and provenance
|
||||
|
||||
The investigation followed the project rendering inventory and used the
|
||||
already-loaded retail structures rather than inventing another sky model.
|
||||
Evidence came from:
|
||||
|
||||
- `artifacts/atmospheric-rendering/sky-heading-dump/client.log`, especially
|
||||
lines 40-88 for Sunny day group 0 and the corresponding repeated entries for
|
||||
all later day groups. The dump records the three IDs, visibility windows,
|
||||
angular sweeps, keyframe directional lighting, and the sun surface.
|
||||
- A read-only `DatCollection.Get<GfxObj>`/`Get<Surface>` probe against the
|
||||
installed Asheron's Call DATs, using the same inspection path implemented by
|
||||
`tools/SkyObjectInspect/Program.cs`, for all three `GfxObj` sort centres,
|
||||
polygon geometry, surfaces, and texture chains.
|
||||
- `tools/RainMeshProbe/Program.cs` lines 37-49, which names and audits the
|
||||
celestial surface set independently of the shadow implementation.
|
||||
- `docs/research/named-retail/acclient_2013_pseudo_c.txt`:
|
||||
`SkyDesc::GetLighting` at `0x00500a80` (around line 261291),
|
||||
`SkyDesc::GetSky` at `0x00501ec0` (around line 262761),
|
||||
`GameSky::CalcFrame` at `0x00506f80` (around line 268650), and
|
||||
`GameSky::UseTime` at `0x005075b0` (around line 269090).
|
||||
- `docs/research/2026-04-23-sky-retail-verbatim.md`, especially its recorded
|
||||
directional-light interpolation and `GameSky::UseTime` material updates.
|
||||
|
||||
No fresh decompilation was required. The named-retail corpus already answered
|
||||
the only question the current code and DAT dump could not answer on their own:
|
||||
whether a moon mesh contributes a second retail world light. It does not.
|
||||
|
||||
## Installed-DAT characterization
|
||||
|
||||
The following values were read from the installed Dereth Region and the three
|
||||
referenced `GfxObj`/surface/texture chains. The same three object IDs, windows,
|
||||
and sweeps occur in every one of the 20 day groups; only their object index
|
||||
changes between seven-object and weather-heavy groups.
|
||||
|
||||
| Role | GfxObj | Day window | Angular sweep | Authored `SortCenter` |
|
||||
|---|---:|---:|---:|---:|
|
||||
| Sun disk | `0x01001348` | `0.1600..0.9400` | `-23 deg..203 deg` | `(1050, 0, 0)` |
|
||||
| Secondary moon | `0x01001F67` | `0.0400..0.2100` | `-20 deg..190 deg` | `(1909.46, 1874.78, -0.0000157485)` |
|
||||
| Dominant moon + halo | `0x01001F6A` | `0.0000..0.2300` | `-20 deg..190 deg` | `(2066.82, 552.99, 0)` |
|
||||
|
||||
The asset chain establishes the visual identities and the dominant-moon
|
||||
choice:
|
||||
|
||||
| GfxObj | Surface | Surface flags | SurfaceTexture | RenderSurface | Image |
|
||||
|---:|---:|---|---:|---:|---|
|
||||
| `0x01001348` | `0x080000D1` | Base1Image, Alpha, Additive | `0x050014CD` | `0x0600388D` | 128x128 `PFID_R8G8B8` sun disk |
|
||||
| `0x01001F67` | `0x080000D2` | Base1ClipMap | `0x05001A6C` | `0x06003894` | 256x256 `PFID_INDEX16`, palette `0x0400103F` |
|
||||
| `0x01001F6A` | `0x080000D6` | Base1ClipMap | `0x05001A6D` | `0x06003898` | 256x256 `PFID_INDEX16`, palette `0x0400103F` |
|
||||
| `0x01001F6A` | `0x080000D7` | Base1Image, Alpha, Additive | `0x05001A6E` | `0x06003899` | 128x128 `PFID_R8G8B8` halo |
|
||||
|
||||
Every listed surface has authored `Luminosity=1`, `Diffuse=1`, and
|
||||
`Translucency=0`. The large moon's primary quad has roughly 2.3 times the
|
||||
polygon area of the secondary moon before its still larger additive halo is
|
||||
counted. That makes `0x01001F6A` the unambiguous dominant lunar visual when
|
||||
both moons are above the horizon.
|
||||
|
||||
These installed-DAT facts are characterization evidence, not an ordinary test
|
||||
dependency. Unit tests use hand-built `DayGroupData` so clean CI and machines
|
||||
without retail DATs remain deterministic.
|
||||
|
||||
## Direction and visibility contract
|
||||
|
||||
`SkyObjectData.IsVisible(dayFraction)` owns the normal, always-visible, and
|
||||
midnight-wrapping window cases. `CurrentAngle(dayFraction)` owns the authored
|
||||
arc interpolation, including progress through a wrapping window.
|
||||
|
||||
The selected direction must match the sky renderer exactly:
|
||||
|
||||
```text
|
||||
heading = active SkyObjectReplace.Rotate
|
||||
arc = SkyObjectData.CurrentAngle(dayFraction)
|
||||
model = RotationZ(-heading) * RotationY(-arc)
|
||||
anchor = effective GfxObj.SortCenter
|
||||
direction = normalize(TransformNormal(anchor, model))
|
||||
```
|
||||
|
||||
“Effective” means that an active non-zero replacement `GfxObjId` also supplies
|
||||
its own `SortCenter`. A replacement with `Transparent >= 1` makes the object
|
||||
ineligible. The replacement lookup follows the renderer's discrete active
|
||||
keyframe rule; it does not interpolate replacement fields. A zero, non-finite,
|
||||
or below/on-horizon transformed direction is ineligible.
|
||||
|
||||
This deliberately does not substitute `SkyTimeOfDay.DirHeading/DirPitch` for
|
||||
moon direction. Those values are the one retail world-light direction. The
|
||||
moon meshes have separate authored arcs, and the enhancement is specifically
|
||||
intended to align moon shadows with the moon the player can see.
|
||||
|
||||
## Authored light contribution
|
||||
|
||||
Named retail `SkyDesc::GetLighting` interpolates the two surrounding
|
||||
`SkyTimeOfDay` records and produces:
|
||||
|
||||
```text
|
||||
sunVector = DirBright * (
|
||||
cos(DirPitch) * sin(DirHeading),
|
||||
cos(DirPitch) * cos(DirHeading),
|
||||
sin(DirPitch))
|
||||
directionalColor = DirColor * length(sunVector)
|
||||
```
|
||||
|
||||
`length(sunVector)` is `DirBright`. acdream exposes the resulting colour as
|
||||
`SkyKeyframe.SunColor`. The pack's scalar authored energy is therefore
|
||||
`clamp(max(SunColor.r, SunColor.g, SunColor.b), 0, 1)`.
|
||||
|
||||
By contrast, named retail `GameSky::UseTime` sends a celestial replacement's
|
||||
`Luminosity`, `MaxBright`, and `Transparent` to the mesh material through
|
||||
`SetLuminosity`, `SetDiffusion`, and `SetTranslucency`. It does not install a
|
||||
second directional light. Texture brightness and moon surface luminosity must
|
||||
not manufacture extra world-light energy.
|
||||
|
||||
Weather/day-group reductions, softness, and elevation ramps remain explicit
|
||||
render-pack policy. They are not mislabelled as measured retail intensities.
|
||||
|
||||
## Parity and safety registration
|
||||
|
||||
### Retail behavior
|
||||
|
||||
- One interpolated directional world-light channel comes from
|
||||
`SkyTimeOfDay.Dir*`.
|
||||
- Celestial meshes follow their own visibility windows and transformed arcs.
|
||||
- Replacement luminosity/diffusion/transparency changes mesh material state,
|
||||
not the number of world-directional lights.
|
||||
- Retail does not render the Campaign AR cascaded real-time object shadows.
|
||||
|
||||
### Opt-in pack enhancement
|
||||
|
||||
- The pack chooses the visible sun or dominant visible moon direction for its
|
||||
directional shadow map.
|
||||
- Moon direction follows the rendered moon; energy remains the single
|
||||
AC-authored directional channel.
|
||||
- Sun wins any overlap when its transformed centre is above the horizon;
|
||||
otherwise the haloed moon wins before the secondary moon.
|
||||
- This deviation belongs in the atmospheric render-pack entry of
|
||||
`docs/architecture/retail-divergence-register.md`.
|
||||
|
||||
### Unchanged boundaries
|
||||
|
||||
- The retail rendering path remains the default and authoritative output.
|
||||
- Pack-off frames do not resolve or render celestial shadow work.
|
||||
- Existing retail scene lighting remains driven by `SkyStateProvider`; this
|
||||
policy does not replace it.
|
||||
- Physics, collision, containment, selection, movement, and DAT geometry are
|
||||
untouched. The selected source is an immutable one-frame rendering fact.
|
||||
|
||||
## Deterministic acceptance coverage
|
||||
|
||||
`tests/AcDream.App.Tests/Rendering/Packs/AuthoredCelestialShadowSourceResolverTests.cs`
|
||||
locks:
|
||||
|
||||
- the three verified IDs and priority independent of object-list order;
|
||||
- sun overlap, dominant-moon fallback, and secondary-moon fallback;
|
||||
- fully transparent and effective replacement behavior;
|
||||
- replacement rotation and the exact renderer transform direction;
|
||||
- no-visible/no-above-horizon suppression;
|
||||
- midnight-wrapping visibility and angle progress; and
|
||||
- directional colour-times-brightness energy, including preservation when no
|
||||
celestial source is available.
|
||||
|
||||
The test fixture is entirely hand-built. It neither requires nor silently
|
||||
substitutes installed retail DAT content.
|
||||
|
||||
The complete non-physical verification result, including shader ABI, exact
|
||||
retail-binary preservation, performance/lifetime fixtures, locked restore,
|
||||
Release build, and fresh-process totals, is recorded in the
|
||||
[Campaign AR Stage 1 automated gate report](2026-08-22-atmospheric-stage1-automated-gate.md).
|
||||
Loading…
Add table
Add a link
Reference in a new issue