feat(sky): split SkyRenderer into pre-/post-scene passes + retail -120m weather Z offset

Bug A (foreground rain) from docs/research/2026-04-26-sky-investigation-handoff.md:
rain mesh was only visible at horizon, not in the air between camera and
character. Two retail mechanisms ported here:

1. **Render order split.** Retail's `LScape::draw` at 0x00506330 calls
   `GameSky::Draw(0)` BEFORE the landblock DrawBlock loop and
   `GameSky::Draw(1)` AFTER — i.e. weather meshes render after scene
   geometry so additive rain streaks paint on top of terrain and entities.
   Acdream was rendering both passes pre-scene, so terrain immediately
   painted over the rain.

   Refactored `SkyRenderer.Render` into `RenderSky` (filter !IsWeather)
   and `RenderWeather` (filter IsWeather) sharing a private `RenderPass`
   core that takes a `weatherPass` bool. Partition is per-SkyObject by
   `Properties & 0x04` (the WEATHER_BIT, mirroring tools/WeatherEnumerator).
   Added `SkyObjectData.IsWeather` getter for the partition.

   `GameWindow.OnRender` now calls `RenderSky` before terrain/static-mesh/
   particles (line ~4322) and `RenderWeather` after particles (line ~4368).

2. **Weather Z offset.** Retail `GameSky::UpdatePosition` at 0x00506dd0,
   lines 0x506e96..0x506e98:

       if (((eax_13 & 4) != 0 && (eax_13 & 8) == 0))
           int32_t var_4_1 = 0xc2f00000;   // 0xc2f00000 == -120.0f

   Weather objects (property bit 0x04 set, bit 0x08 unset) get their frame
   origin set to player_pos + (0, 0, -120m). The rain cylinder GfxObjs
   0x01004C42/0x01004C44 have local Z range 0.11..814.90 (815m tall, 113m
   radius). Without the offset the cylinder bottom sat just above the
   camera; with -120m the cylinder spans (camera-119.89)..(camera+694.90)
   so the camera is inside.

   `SkyRenderer.RenderPass` applies the -120m model translation when
   `weatherPass` is true (line ~253-254).

3. **Legacy camera-attached emitter gated.** `UpdateWeatherParticles` —
   the pre-research workaround that emitted camera-attached rain particles
   (broken alpha fade, fixed disk around camera) — is now gated behind
   `ACDREAM_FAKE_RAIN_PARTICLES=1`. Default off; the retail-faithful
   world-space mesh is the default path.

User-verified: rain is now visible in foreground from many perspectives,
but the cylinder's open-top rim is still visible when looking straight up.
That rim issue is a separate brightness-excess bug filed for follow-up
(Translucency float not plumbed to shader; surface.Translucency=0.5 ignored
so streaks render at 2× retail intensity).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-27 08:49:42 +02:00
parent 9567597814
commit 3e0da496e0
3 changed files with 130 additions and 5 deletions

View file

@ -36,6 +36,23 @@ public sealed class SkyObjectData
public uint GfxObjId;
public uint Properties;
/// <summary>
/// True when this SkyObject is flagged as weather (Properties bit
/// <c>0x04</c>). Per the named retail decomp,
/// <c>GameSky::CreateDeletePhysicsObjects</c> at <c>0x005073c0</c>
/// passes <c>Properties &amp; 0x04</c> as <c>arg5</c> of
/// <c>GameSky::MakeObject</c> (<c>0x00506ee0</c>) — when set, the
/// CPhysicsObj is added to <c>after_sky_cell</c> instead of
/// <c>before_sky_cell</c>, and <c>GameSky::Draw(arg2=1)</c> at
/// <c>0x00506ff0</c> draws that cell <i>after</i> the scene. acdream
/// uses this flag to split the sky pass: non-weather objects render
/// pre-scene (so terrain and entities z-test on top), weather meshes
/// (e.g. the 815m-tall rain cylinders <c>0x01004C42</c>/<c>0x01004C44</c>)
/// render post-scene with depth-test off so they overlay foreground
/// geometry — matching retail's volumetric foreground-rain look.
/// </summary>
public bool IsWeather => (Properties & 0x04u) != 0u;
/// <summary>Object is visible at day-fraction <paramref name="t"/>
/// by retail's begin/end semantics (r12 §2). Three cases:
/// <list type="bullet">