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
|
|
@ -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