acdream/docs/research/2026-08-22-dereth-celestial-shadow-sources.md

9.3 KiB

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:

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:

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.