fix(sky): partition sky pass on Properties bit 0x01, not bit 0x04

The pre/post-scene sky pass split was using SkyObjectData.IsWeather
(bit 0x04) — the wrong bit. Per the named retail decomp:

  GameSky::CreateDeletePhysicsObjects at 0x005073c0 / decomp 269036:
    MakeObject(this, gfx_id, &tex_velocity,
               (properties & 1),    // arg4: post-scene flag
               (properties & 4));   // arg5: weather gate

  GameSky::MakeObject at 0x00506ee0 / decomp 268656:
    if (arg4 != 0)
      AddObjectToSingleCell(result, after_sky_cell);   // post-scene
    else
      AddObjectToSingleCell(result, before_sky_cell);  // pre-scene

So bit 0x01 routes between before_sky_cell (rendered pre-scene by
GameSky::Draw(0)) and after_sky_cell (rendered post-scene by
GameSky::Draw(1)). Bit 0x04 is independent — it gates whether the
object is instantiated at all when LScape::weather_enabled is false.

In Dereth's Rainy DayGroup this matters for the rain cylinders:
  0x01004C42  Props=0x04 (bit 0x04 only)  → pre-scene + weather-gated
  0x01004C44  Props=0x05 (bits 0x01+0x04) → post-scene + weather-gated
  0x01004C35  Props=0x02 (bit 0x02 only)  → pre-scene (cloud, fog-hide)

Before this fix acdream put BOTH rain cylinders in the post-scene
pass (because both have bit 0x04). That double-rendered foreground
rain — explained why acdream's foreground rain looked thicker than
retail's. Now only 0x01004C44 is foreground; 0x01004C42 renders with
the sky dome.

Added SkyObjectData.IsPostScene (bit 0x01) with citations. Renamed
the internal RenderPass parameter weatherPass → postScenePass and
updated both the partition criterion and the -120m foreground-rain
Z offset to gate on it. Public RenderSky / RenderWeather entry
points kept their names for API stability; doc comments updated to
explain the bit semantics.

Independent confirmation from one of the user's external code-review
agents — the report's Setup-objects-silently-dropped finding is the
remaining defect in the same family (Setup IDs 0x020xxx aren't
loaded by EnsureMeshUploaded; deferred to a separate phase).

1227 tests pass.
This commit is contained in:
Erik 2026-04-27 22:43:14 +02:00
parent 05a8a7209f
commit 034a684f02
2 changed files with 72 additions and 37 deletions

View file

@ -37,22 +37,44 @@ public sealed class SkyObjectData
public uint Properties;
/// <summary>
/// True when this SkyObject is flagged as weather (Properties bit
/// <c>0x04</c>). Per the named retail decomp,
/// True when this SkyObject is gated on the weather system (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.
/// passes <c>Properties &amp; 4</c> as <c>arg5</c> of
/// <c>GameSky::MakeObject</c> (<c>0x00506ee0</c>); the inner
/// <c>(arg5 == 0 || LScape::weather_enabled != 0)</c> guard at decomp
/// line 268630 means weather-flagged objects only get instantiated when
/// the global weather flag is on. This bit does <b>not</b> control
/// pre/post-scene placement — that's <see cref="IsPostScene"/>.
/// acdream currently always renders weather-flagged objects (we don't
/// honor a weather_enabled toggle yet); when we add one, this flag is
/// the gate.
/// </summary>
public bool IsWeather => (Properties & 0x04u) != 0u;
/// <summary>
/// True when this SkyObject renders <i>after</i> the world scene
/// (Properties bit <c>0x01</c>) — i.e. as foreground over terrain and
/// entities. Per the named retail decomp,
/// <c>GameSky::CreateDeletePhysicsObjects</c> passes
/// <c>Properties &amp; 1</c> as <c>arg4</c> of
/// <c>GameSky::MakeObject</c> (decomp line 269036); MakeObject at
/// decomp 268656 routes <c>arg4 != 0</c> objects into
/// <c>after_sky_cell</c> instead of <c>before_sky_cell</c>, and
/// <c>GameSky::Draw(arg2=1)</c> at <c>0x00506ff0</c> draws
/// <c>after_sky_cell</c> as a separate post-scene pass.
/// <para>
/// In Dereth's Rainy DayGroup this distinguishes the two rain
/// cylinders: <c>0x01004C44</c> (Props=0x05) is foreground rain
/// rendered after terrain; <c>0x01004C42</c> (Props=0x04 alone) is
/// background rain rendered <i>with</i> the sky dome. Earlier
/// versions of acdream incorrectly split on <see cref="IsWeather"/>
/// (bit 0x04) so both rain meshes ended up in the post-scene pass,
/// double-rendering rain in the foreground.
/// </para>
/// </summary>
public bool IsPostScene => (Properties & 0x01u) != 0u;
/// <summary>Object is visible at day-fraction <paramref name="t"/>
/// by retail's begin/end semantics (r12 §2). Three cases:
/// <list type="bullet">