Iteration on the sky rendering pipeline to restore stars/moon visibility
at night and fix washed-out grey daytime clouds. Key fixes:
* sky.frag: disable fog-mix on sky meshes. Retail's keyframe FogEnd
(0..400m at midnight, up to 2400m during day) is calibrated for
terrain; sky meshes are authored at radii 1050-14271m which sits
past FogEnd universally, causing every sky pixel to saturate to
fogColor (dark navy). Stars, moon, dome texture all got
obliterated. The horizon-glow trade-off is noted in the shader
comment; research item to find retail's sky-specific fog range
later.
* SkyRenderer + sky.frag: promote rep.Luminosity into uEmissive so the
vertex lighting saturates properly for bright keyframes. Retail's
FUN_0059da60 non-luminous path writes rep.Luminosity into
material.Emissive via the cache +0x3c slot; we were instead using
it as a post-fragment multiply which could only dim, never brighten.
Net effect: daytime clouds now render saturated white, dome dims
correctly at night (rep.Luminosity=0.11 → Emissive=0.11), stars
and moon unchanged.
* terrain.vert: MIN_FACTOR 0.08 -> 0.0 per retail FUN_00532440 decompile
(DAT_00796344 ambient-floor = 0.0). Back-lit terrain now falls to
pure ambient rather than getting an 8% sun floor.
New research / tooling (no runtime impact):
* docs/research/2026-04-24-lambert-brightness-split.md — retail's
ambient-brightness formula pinned from PE .rdata read + live
RetailTimeProbe capture: effAmbBright = AmbBright + |sunDir| * 0.2
where scale constant 0x0079a1e8 = 0.2f exactly.
* docs/research/2026-04-23-lightning-real.md — research note on the
dat-baked PhysicsScript-driven lightning path (Rainy DayGroup has
explicit PES-triggered flash SkyObjects with 5ms time windows).
* Corrections stapled to sky-decompile-hunt-{B,C}.md: DAT_00842778 is
DirColor, DAT_0084277c is AmbColor (the hunt docs had the swap
backwards).
* tools/RetailTimeProbe/Program.cs: extended with pid=NNNN selector,
sky global probe (DirColor/AmbColor/AmbBright/sunDir/cache.amb),
and the 0x0079a1e8 scale-factor readout.
* tools/SkyObjectInspect/: throwaway dat-inspector built by the Opus
deep-dive agent. Identified GfxObj 0x010015EF as the stars layer
(A8R8G8B8 128x128 texture, 4% bright-pixel ratio).
* src/AcDream.App/Rendering/TextureCache.cs: per-texture alpha
histogram dump under ACDREAM_DUMP_SKY=1 for diagnosing "are the
clouds decoded with proper alpha" type questions.
README: rewrite to reflect current state (playable pre-alpha rendering
Dereth with animated characters, day-night cycle, weather, etc.)
instead of the stale "Phase 0 dat inventory only" description.
All 742 tests green.
439 lines
19 KiB
Markdown
439 lines
19 KiB
Markdown
# Sky Decompile Hunt B — D3D Render-State Signature Trace
|
||
|
||
**Date:** 2026-04-23
|
||
**Hunter:** Hunt Agent B (render-state signatures)
|
||
**Status:** SIGNIFICANT FINDINGS — but NOT a "celestial-body iteration draw loop"
|
||
|
||
> **⚠ 2026-04-24 correction:** Any occurrences in this doc that call
|
||
> `DAT_00842778` the "ambient" colour are backwards. `DAT_00842778` =
|
||
> **DirColor** (directional / sun), `DAT_0084277c` = **AmbColor**,
|
||
> `DAT_00842780` = **AmbBright**. Cross-verified against
|
||
> `SkyTimeOfDay.Unpack` and `FUN_00501600`'s output mapping. Full
|
||
> re-analysis: `docs/research/2026-04-24-lambert-brightness-split.md`.
|
||
|
||
## TL;DR
|
||
|
||
The retail acclient does NOT appear to have a classical "sky dome + iterate
|
||
celestial meshes with per-mesh blend swaps" render function. Instead, sky
|
||
is implemented as:
|
||
|
||
1. **A per-frame keyframe sampler** (`FUN_005062e0`) that interpolates the
|
||
current RegionDesc keyframe (sun angle, sun color, ambient color, fog
|
||
color/near/far) and stashes results into globals.
|
||
2. **Per-mesh ambient color** pushed via `_DAT_008682bc/c0/c4` (RGB floats,
|
||
no D3DRS_AMBIENT call).
|
||
3. **D3D fog state** (D3DRS_FOGCOLOR/FOGSTART/FOGEND) set via
|
||
`FUN_005a41b0` — this is the ONLY sky-keyframe → D3D state write path.
|
||
4. **A "pre-world" pass** (`FUN_00507a50(0)`) that disables Zwrite and
|
||
iterates weather volume objects (fog/rain/snow, NOT sun/moon/stars).
|
||
|
||
No "celestial body draw loop" found. No D3DRS_AMBIENT call found. No
|
||
matrix-translation-zero (camera-anchor) found. No lightning RNG found.
|
||
No huge far-plane constant found at the sky call site. This strongly
|
||
suggests retail AC is far simpler than modern sky demos assume — the
|
||
sun/moon/stars are either baked into the RegionDesc scene geometry
|
||
(treated as regular objects that happen to be positioned far away) or
|
||
do not exist as discrete draw calls at all, with "sun color" being
|
||
expressed purely via per-vertex lighting on world meshes.
|
||
|
||
## D3D Vtable Map
|
||
|
||
All state writes go through `pIDirect3DDevice` at `(param_1 + 0x468)`,
|
||
vtable offset `0xe4` = `SetRenderState`, offset `0xd4` = `SetTextureStageState`,
|
||
offset `0xc4` = `SetMaterial`, offset `0x1c` (of a PARENT wrapper object at
|
||
`DAT_0086734c`) = `SetPerspective/SetFrustum`.
|
||
|
||
Discovered D3D state wrappers in `chunk_005A0000.c`:
|
||
|
||
| Wrapper | Addr | D3DRS | Meaning | Cache offset |
|
||
|---|---|---|---|---|
|
||
| FUN_005a3ba0 | 0x005A3BA0 | 0x1b=27 | ALPHABLENDENABLE | +0x475 |
|
||
| FUN_005a3be0 | 0x005A3BE0 | 0x0f=15 | ALPHATESTENABLE | +0x476 |
|
||
| FUN_005a3c20 | 0x005A3C20 | 0x19=25 | ALPHAFUNC | +0x478 |
|
||
| FUN_005a3c60 | 0x005A3C60 | 0x18=24 | ALPHAREF | +0x47c |
|
||
| FUN_005a3ca0 | 0x005A3CA0 | 0x13=19 src, 0x14=20 dst, 0xab=171 op | blend state | +0x4f0/+0x49c |
|
||
| FUN_005a3d80 | 0x005A3D80 | 0x16=22 | CULLMODE | +0x488 |
|
||
| FUN_005a3dc0 | 0x005A3DC0 | 0xaf=175 | (fog-gated) | +0x48c |
|
||
| FUN_005a3e00 | 0x005A3E00 | 7 | ZENABLE | none (stateless) |
|
||
| FUN_005a3e20 | 0x005A3E20 | 0x17=23 (ZFUNC) + 0xe=14 (ZWRITE) | combined ZFunc+ZWrite | +0x494/+0x498 |
|
||
| FUN_005a3eb0 | 0x005A3EB0 | 0x8b=139 | **AMBIENT** | +0x4a0 |
|
||
| FUN_005a3ef0 | 0x005A3EF0 | 0x91=145 | COLORVERTEX | +0x4a8 |
|
||
| FUN_005a3f40 | 0x005A3F40 | 0x93=147 | ? | +0x4a4 |
|
||
| FUN_005a3f90 | 0x005A3F90 | 0x1c=28 | **FOGENABLE** | +0x4bc |
|
||
| FUN_005a4080 | 0x005A4080 | 0x22=34 (FOGCOLOR), 0x24=36 (FOGSTART), 0x25=37 (FOGEND) | fog triple | +0x4ac/+0x4c0/+0x4c4 |
|
||
| FUN_005a41b0 | 0x005A41B0 | wrapper → FUN_005a4080 | **SetFog(color, start, end)** | — |
|
||
| FUN_005a41f0 | 0x005A41F0 | 0x89=137 | **LIGHTING** | +0x4c8 |
|
||
| FUN_005a4280 | 0x005A4280 | 0x92=146 | LOCALVIEWER | +0x4d8 |
|
||
| FUN_005a4350 | 0x005A4350 | 0x3c=60 | ? | +0x4e0 |
|
||
| FUN_005a4390 | 0x005A4390 | 0x08 | SHADEMODE | +0x4e4 |
|
||
| FUN_005a4420 | 0x005A4420 | stages | SetTextureStageState blend args | — |
|
||
| FUN_005a4010 | 0x005A4010 | internal flag at +0x4be → re-calls FUN_005a3f90 | **fog-master-enable gate** | +0x4be |
|
||
|
||
**Device-reset-to-default** function: `FUN_005a10f0` at 0x005A10F0, lines
|
||
687-740 of chunk_005A0000.c. This is NOT the sky render; it is called
|
||
once on device init and full-state rebuild.
|
||
|
||
## AMBIENT (D3DRS=0x8b=139) — The Key Negative Finding
|
||
|
||
I searched every call to vtable `0xe4` with state `0x8b` across the
|
||
entire 688K-line decompile. Results:
|
||
|
||
- chunk_005A0000.c:3388 — `...0x8b,0` — inside default-init reset
|
||
- chunk_005A0000.c:2772 — `...0x8b,param_2` — inside wrapper FUN_005a3eb0
|
||
|
||
External callers of `FUN_005a3eb0`:
|
||
|
||
```
|
||
chunk_005A0000.c:704: FUN_005a3eb0(0); // default-reset, ambient=black
|
||
```
|
||
|
||
**ONLY ONE CALLER.** The sky keyframe sampler (`FUN_005062e0`) does NOT
|
||
call `FUN_005a3eb0`. D3DRS_AMBIENT is set to 0 once and never changed.
|
||
**This falsifies the previous research hypothesis** (2026-04-22 doc) that
|
||
clouds are tinted by a per-keyframe D3DRS_AMBIENT write.
|
||
|
||
Sky color instead lives in `_DAT_008682bc/c0/c4` (three floats, RGB, set
|
||
from `DAT_00842950/54/58`). See below.
|
||
|
||
## Sky Keyframe Sampler — `FUN_005062e0` @ 0x005062E0
|
||
|
||
chunk_00500000.c, lines 6213-6333. Called each frame from the scene
|
||
renderer (`FUN_00455a50` path, around chunk_00450000.c:4341, 4468).
|
||
|
||
Stripped pseudocode:
|
||
|
||
```c
|
||
void FUN_005062e0(int worldRenderer) {
|
||
if (worldRenderer.layerBits != 0 && worldRenderer.skyEnabled != 0)
|
||
{
|
||
FUN_00508010(); // advance sky timer
|
||
double now = _DAT_008379a8; // game-time seconds
|
||
if (_DAT_00842798 <= now && currentRegionDesc != 0) {
|
||
_DAT_00842798 = regionDesc[+0x50].nextKeyTime + now; // reschedule
|
||
|
||
float turbidity = (weatherSys ? weatherSys[+0x48] : _DAT_00795610);
|
||
if (_DAT_008427a0 < now) {
|
||
// ---- BLOCK 1: sample ambient/sun ----
|
||
int ok = FUN_004ff440(turbidity,
|
||
&fogDensity, // out float
|
||
&sunColor, // out ARGB
|
||
sunDirVec, // out float[3]
|
||
&ambientColor); // out ARGB
|
||
if (ok) {
|
||
if (fogDensity < DAT_0084295c) fogDensity = DAT_0084295c;
|
||
// ---- WEATHER FOG BLEND (if weather active) ----
|
||
if (DAT_008427a9 /* weatherFogEnabled */) {
|
||
if (1.0f <= _DAT_008427b8 /* blendProgress */) {
|
||
// blend complete; snap to weather values
|
||
fogDensity = DAT_008427ac; // weather density
|
||
sunColor = DAT_00842788; // weather fog color
|
||
} else {
|
||
// lerp currentColor → weatherColor per channel (bytes),
|
||
// lerp density toward DAT_008427ac
|
||
// advance _DAT_008427b8 by _DAT_007c7208 (blend rate)
|
||
}
|
||
}
|
||
FUN_00505f30(fogDensity, sunColor, sunDirVec, ambientColor);
|
||
}
|
||
_DAT_008427a0 = now + (currentRegionDesc ?
|
||
regionDesc[+0x50].keyInterval : _DAT_007c7200);
|
||
}
|
||
|
||
// ---- BLOCK 2: sample fog near/far/color ----
|
||
FUN_005a4010(!DAT_0081dbf8); // disable fog if master fog flag clear
|
||
if (DAT_0081dbf8) {
|
||
FUN_005a3f90(DAT_0081dbf8); // FOGENABLE = true
|
||
int ok2 = FUN_004ff480(turbidity,
|
||
&fogNear, // out float
|
||
&fogFar, // out float
|
||
&fogColor);// out ARGB
|
||
if (ok2) {
|
||
if (DAT_008427a9 /* weatherFogEnabled */) {
|
||
// blend fogNear, fogFar, fogColor toward weather values
|
||
// identical lerp structure
|
||
}
|
||
FUN_005a41b0(&fogColor, fogNear, fogFar);
|
||
// → SetRenderState(FOGCOLOR, ...), (FOGSTART, ...), (FOGEND, ...)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
This is the ONLY per-frame writer of fog D3D state. Called by
|
||
`FUN_00455a50` (scene entry) via `FUN_00506270` etc.
|
||
|
||
## Block-1 Keyframe Layout — `FUN_00501600` @ 0x00501600
|
||
|
||
chunk_00500000.c, lines 1151-1232. Decoded keyframe struct:
|
||
|
||
| Offset | Field | Type | Notes |
|
||
|---|---|---|---|
|
||
| +0x04 | SunMagnitude | float | lerp → param_4 (radius) |
|
||
| +0x08 | SunYAngle | float | degrees; `* (π/180)` gives radians |
|
||
| +0x0c | SunZAngle | float | degrees; `* (π/180)` gives radians |
|
||
| +0x10–0x12 | AmbientColor | byte[3] | R,G,B (packed via byte-shuffles) |
|
||
| +0x14 | FogDensity | float | lerp → param_2 |
|
||
| +0x18–0x1a | SunColor | byte[3] | R,G,B |
|
||
|
||
From chunk_00500000.c:1190-1216:
|
||
|
||
```c
|
||
// out sunDirVec (param_4):
|
||
fVar9 = lerp(nextKey[+0x04], prevKey[+0x04], param_1); // magnitude
|
||
float sinAngY = lerp(nextKey[+0x08], prevKey[+0x08]) * DEG2RAD;
|
||
float sinAngZ = lerp(nextKey[+0x0c], prevKey[+0x0c]) * DEG2RAD;
|
||
float cosY = cos(sinAngY);
|
||
float sinY = sin(sinAngY);
|
||
float cosZ = cos(sinAngZ);
|
||
float sinZ = sin(sinAngZ);
|
||
param_4[0] = fVar9 * sinZ * cosY;
|
||
param_4[1] = fVar9 * cosZ * cosY;
|
||
param_4[2] = fVar9 * sinY;
|
||
|
||
// out sunColor ARGB (param_3): from bytes at +0x18,0x19,0x1a
|
||
// out ambientColor ARGB (param_5): from bytes at +0x10,0x11,0x12
|
||
```
|
||
|
||
(The repeated `FUN_005df4c4()` calls with no args are Ghidra's artifact
|
||
for a union/reinterpret-cast byte shuffle — they are NOT an RNG and NOT
|
||
a lightning modulator.)
|
||
|
||
## Block-2 Keyframe Layout — `FUN_00501860` @ 0x00501860
|
||
|
||
chunk_00500000.c, lines 1236-1268. Second block of the keyframe struct:
|
||
|
||
| Offset | Field | Type | Notes |
|
||
|---|---|---|---|
|
||
| +0x20 | FogNear | float | |
|
||
| +0x24 | FogFar | float | |
|
||
| +0x28–0x2a | FogColor | byte[3] | R,G,B |
|
||
|
||
## Apply-Block-1 — `FUN_00505f30` @ 0x00505F30
|
||
|
||
chunk_00500000.c, lines 6026-6092. Stores the sampled values into global
|
||
cache and into the `_DAT_008682bc/c0/c4` "per-vertex ambient" slot:
|
||
|
||
```c
|
||
DAT_00842780 = fogDensity; // param_2
|
||
DAT_0084277c = sunColor; // param_3 (ARGB)
|
||
DAT_00842950 = sunDir[0]; // param_4[0]
|
||
DAT_00842954 = sunDir[1];
|
||
DAT_00842958 = sunDir[2];
|
||
DAT_00842778 = ambientColor; // param_5 (ARGB)
|
||
|
||
_DAT_008682c8 = sunDir[0]; // duplicated
|
||
_DAT_008682d0 = sunDir[2];
|
||
_DAT_008682cc = sunDir[1];
|
||
FUN_00451a60(&_something, ambientColor); // unpacks ARGB → 3 floats
|
||
_DAT_008682bc = unpacked R; // RGB ambient floats for mesh lighting
|
||
_DAT_008682c0 = unpacked G;
|
||
_DAT_008682c4 = unpacked B;
|
||
DAT_008682d4 = 0; // ambient dirty flag
|
||
```
|
||
|
||
**`_DAT_008682bc/c0/c4` is the THING the sky tints the world with.**
|
||
It is consumed per-vertex by mesh-draw, not by a D3D state write.
|
||
|
||
## Candidate "Sky Pass" — `FUN_00507a50` @ 0x00507A50
|
||
|
||
chunk_00500000.c, lines 7250-7299.
|
||
|
||
```c
|
||
void FUN_00507a50(weatherMgr, phase) {
|
||
if ((FUN_00451ec0() || phase == 0)) { // skyEnabled or pass-0
|
||
weatherMgr.renderingFlag = 1;
|
||
char oldAlpha = FUN_005a1560();
|
||
FUN_005a3f90(DAT_008427a9 != '\0'); // FOGENABLE ← weatherFog flag
|
||
float oldFar = DAT_0081fc98;
|
||
FUN_0054bf30(DAT_0081fc98 * _DAT_007c6f14); // multiply far plane
|
||
FUN_005a3e20(8, 0); // ZFUNC=ALWAYS, ZWRITE=0
|
||
if (phase == 0) {
|
||
// iterate weather volume objects (rain/snow/fog cells)
|
||
for (uint i = 0; i < weatherMgr.count; ++i) {
|
||
int obj = *(int*)(weatherMgr.pArray + i*4);
|
||
uint flags = *(uint*)(weatherMgr.pFlags + i*4);
|
||
if (obj && !(flags & 1) &&
|
||
(DAT_0081dbf9 || !(flags & 4)) &&
|
||
(!DAT_008427a9 || !FUN_005a1560() || !(flags_byte & 2)))
|
||
{
|
||
FUN_00511720(obj); // scene-graph walk (update per-frame)
|
||
FUN_00511760(obj); // scene-graph walk (draw)
|
||
}
|
||
}
|
||
} else if (DAT_0081dbf9) {
|
||
// phase 1: just let the device callback at +0x64 run
|
||
(**(code **)(*DAT_00870340 + 0x64))(weatherMgr[+0x28]);
|
||
}
|
||
FUN_0054bf30(oldFar); // restore far plane
|
||
FUN_005a3e20(4, 1); // ZFUNC=LESSEQUAL, ZWRITE=1
|
||
FUN_005a3f90(oldAlpha); // restore FOGENABLE
|
||
weatherMgr.renderingFlag = 0;
|
||
}
|
||
}
|
||
```
|
||
|
||
Called by `FUN_00506d90` (chunk_00500000.c:6683):
|
||
|
||
- `FUN_00507a50(0)` = phase-0 (sky-ish volume draw, before terrain/entity)
|
||
- `FUN_00507a50(1)` = phase-1 (post-draw overlay, gated by weather flag)
|
||
|
||
The `DAT_0081fc98 * _DAT_007c6f14` line IS the far-plane multiplier —
|
||
however `_DAT_007c6f14` here is ambiguous, used elsewhere as a small
|
||
rotation coefficient (chunk_005E0000.c:258 etc). This deserves a
|
||
follow-up: check if that DAT is a configurable sky-far-plane multiplier
|
||
or a recycled constant.
|
||
|
||
**No matrix-translation-zero (camera-anchor) is present in this
|
||
function.** The scene graph objects iterated via `FUN_00511720/60` are
|
||
rain/snow/fog-shaft volumes — they are positioned in world space and
|
||
expected to follow the camera via normal scene-graph propagation.
|
||
|
||
**No per-mesh blend swap (alpha for clouds vs additive for sun) is
|
||
present.** Blend mode is set once for the whole pass.
|
||
|
||
## Fog Write Path — SUMMARY
|
||
|
||
The complete trail:
|
||
|
||
1. `FUN_00505f30` stores sun/ambient globals (no D3D call).
|
||
2. `FUN_005062e0` lerps current-keyframe fog → weather-fog if needed.
|
||
3. `FUN_005a41b0(&fogColor, fogNear, fogFar)` →
|
||
4. `FUN_005a4080(pFogColor_as_ARGB, near, far)` →
|
||
5. Writes D3DRS_FOGCOLOR=0x22, D3DRS_FOGSTART=0x24, D3DRS_FOGEND=0x25.
|
||
|
||
Fog ENABLE is wrapped by `FUN_005a4010` (master gate at +0x4be) +
|
||
`FUN_005a3f90` (actual D3DRS_FOGENABLE=0x1c write). Both driven by
|
||
the DAT_0081dbf8 master-fog flag.
|
||
|
||
**D3DRS_FOGTABLEMODE=0x23, FOGVERTEXMODE=0x8c, FOGDENSITY=0x26** —
|
||
these are only set once in the default-init (`FUN_005a10f0`) and never
|
||
per-frame. Retail uses linear fog (FOGSTART/FOGEND), not exponential
|
||
(FOGDENSITY).
|
||
|
||
## Lightning Flash — NOT FOUND
|
||
|
||
Searched for:
|
||
|
||
- `FUN_005df4c4` (the RNG/byte-shuffle) — all hits are byte-packing
|
||
for ARGB color interpolation, not random modulation.
|
||
- `_DAT_008427b8` — it's a weather-fog-blend progress (0 → 1 ramp),
|
||
not a per-frame random value.
|
||
- Time-based sine/cos on small periods — none found coupled to sky
|
||
color.
|
||
- Any RNG call near fog/ambient writes — none found.
|
||
|
||
**Lightning is likely a separate system not found in this hunt, OR
|
||
does not exist in retail and is purely decorative in modern ports.**
|
||
|
||
## Matrix-Anchor (Camera-Centered Sky) — NOT FOUND
|
||
|
||
Searched for patterns like:
|
||
|
||
- `*(undefined4*)(mat + 0x30) = 0;`
|
||
- `SetTransform(D3DTS_VIEW=2, ...)` via any vtable offset
|
||
- A matrix copy that zeroes three consecutive 4-byte fields
|
||
|
||
Nothing found anchored to the sky pass. The view matrix is NOT rewritten
|
||
with zero translation before the sky draw. This is consistent with the
|
||
conclusion that there is no discrete "sky dome" — the weather/fog volume
|
||
objects follow the camera by being placed in camera-relative world
|
||
position by their parent scene-graph node.
|
||
|
||
## Huge Far-Plane Constants — NOT FOUND
|
||
|
||
Searched for:
|
||
|
||
- 0x49742400 (1e6f) — no hits
|
||
- 0x47c35000 (1e5f) — no hits
|
||
- 0x4b189680 (1e7f) — no hits
|
||
|
||
The only far-plane modulator is `DAT_0081fc98 * _DAT_007c6f14` at
|
||
chunk_00500000.c:7272. Without knowing `_DAT_007c6f14`'s numeric value
|
||
from a debugger or disassembly of `.rdata`, we can't tell if this is
|
||
>>1 (sky-far-plane multiplier) or ~1 (no-op). A follow-up
|
||
disassembly of the address `0x007c6f14` should nail this down.
|
||
|
||
## D3D State Inventory — Sky-Relevant Per-Frame Writes
|
||
|
||
Only four D3D states are written per-frame in the sky-adjacent code path:
|
||
|
||
| State | Hex | Name | Writer | Source of value |
|
||
|---|---|---|---|---|
|
||
| 34 | 0x22 | FOGCOLOR | `FUN_005a4080` | current keyframe+weather lerp |
|
||
| 36 | 0x24 | FOGSTART | `FUN_005a4080` | current keyframe+weather lerp |
|
||
| 37 | 0x25 | FOGEND | `FUN_005a4080` | current keyframe+weather lerp |
|
||
| 28 | 0x1c | FOGENABLE | `FUN_005a3f90` | `DAT_0081dbf8` / weather flag |
|
||
|
||
Plus, inside `FUN_00507a50` (phase-0 weather-volume draw only):
|
||
|
||
| State | Hex | Name | Value |
|
||
|---|---|---|---|
|
||
| 23+14 | 0x17+0x0e | ZFUNC + ZWRITE | ALWAYS + OFF → restore LESSEQUAL + ON |
|
||
|
||
## Gaps / Unresolved
|
||
|
||
1. **The classical sky dome / celestial body draw** — NOT FOUND. Either
|
||
retail literally does not have one (sky is just a clear-color glimpse
|
||
plus fog between camera and terrain), or it's hidden inside the
|
||
RegionDesc.Scene entries and rendered as a regular object through the
|
||
normal scene-graph path.
|
||
2. **`_DAT_007c6f14`** — used as a far-plane multiplier in the sky pass,
|
||
but cross-referenced use in chunks 0x5E0000 suggests it's a unit-scale
|
||
float (< 10). Needs raw `.rdata` dump to confirm its value.
|
||
3. **Is `_DAT_008682bc/c0/c4` consumed by D3D material or per-vertex
|
||
color?** The writers are clear; the readers need a separate hunt
|
||
(likely in chunk_00590000.c or the mesh-draw path at `FUN_0054c9c0`).
|
||
4. **Lightning flash** — not found in the keyframe/fog paths. If it
|
||
exists, it's in a separate code path (maybe tied to weather presets
|
||
6 "thunderstorm" — DAT_008427a9=1 case in chunk_00550000.c:11886).
|
||
|
||
## Files / Addresses cited (all absolute paths)
|
||
|
||
- `C:\Users\erikn\source\repos\acdream\docs\research\decompiled\chunk_005A0000.c`
|
||
- lines 687-740: `FUN_005a10f0` device reset (AMBIENT=0 here, only caller)
|
||
- lines 2606-3035: D3D state wrapper zoo (all vtable-0xe4 writers)
|
||
- lines 2868-2907: `FUN_005a4080` = SetFog triple
|
||
- lines 2911-2921: `FUN_005a41b0` = SetFog wrapper
|
||
- lines 3346-3400: default state-init defaults
|
||
- lines 3860-3970: `FUN_005a5950` world-scene top-level renderer
|
||
- `C:\Users\erikn\source\repos\acdream\docs\research\decompiled\chunk_00500000.c`
|
||
- lines 1151-1232: `FUN_00501600` Block-1 keyframe sampler
|
||
- lines 1236-1268: `FUN_00501860` Block-2 (fog) keyframe sampler
|
||
- lines 6026-6092: `FUN_00505f30` apply-keyframe-1 to globals
|
||
- lines 6213-6333: `FUN_005062e0` per-frame sky+fog update (MAIN)
|
||
- lines 6683-6708: `FUN_00506d90` scene renderer (calls sky phase 0/1)
|
||
- lines 7250-7299: `FUN_00507a50` the phase-0 weather-volume draw
|
||
- `C:\Users\erikn\source\repos\acdream\docs\research\decompiled\chunk_00450000.c`
|
||
- lines 608-622: `FUN_00451a60` ARGB → 3-float unpack
|
||
- lines 4300-4479: per-landblock ambient setup (`_DAT_008682bc` writers)
|
||
- `C:\Users\erikn\source\repos\acdream\docs\research\decompiled\chunk_00550000.c`
|
||
- lines 11835-12016: `FUN_0055eb40` weather-preset-id → fog constants
|
||
(0=clear, 1=light rain, 2=medium, 3=heavy, 4=blizzard, 5=?, 6=thunderstorm)
|
||
- `C:\Users\erikn\source\repos\acdream\docs\research\decompiled\chunk_004F0000.c`
|
||
- lines 10692-10720: `FUN_004ff440` / `FUN_004ff480` keyframe trampolines
|
||
|
||
## Recommendation for acdream port
|
||
|
||
Given these findings, our C# SkyRenderer should:
|
||
|
||
1. **Stop expecting a celestial-body draw loop in the decompile** — Hunt A
|
||
will likely confirm via the Region-loader angle.
|
||
2. **Port the keyframe sampler faithfully** from `FUN_00501600` / 1860 —
|
||
the struct layout above is the ground truth for RegionDesc keyframe
|
||
blobs.
|
||
3. **Apply the sun direction and colors to world-mesh vertex lighting**,
|
||
not to a D3DRS_AMBIENT equivalent. The values live in
|
||
`_DAT_008682bc/c0/c4` and are consumed by the mesh pipeline.
|
||
4. **Set fog via SetFog(color, start, end) + FogEnable** per frame from
|
||
the Block-2 sample. This matches `FUN_005a41b0`.
|
||
5. **Do NOT add a matrix translation-zero** — retail does not anchor the
|
||
sky dome, because retail does not appear to HAVE a sky dome draw in
|
||
the D3D state sense. If acdream renders a dome mesh, it's a modern
|
||
addition.
|
||
6. **Tint clouds (if rendered) via the per-mesh ambient global**, not
|
||
via the current uniform-one approach. The previous research doc's
|
||
conclusion on "clouds multiply by ambient" is likely correct in
|
||
spirit — just know that "ambient" here means
|
||
`_DAT_008682bc/c0/c4`, not D3DRS_AMBIENT.
|