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:
parent
05a8a7209f
commit
034a684f02
2 changed files with 72 additions and 37 deletions
|
|
@ -107,21 +107,27 @@ public sealed unsafe class SkyRenderer : IDisposable
|
|||
float dayFraction,
|
||||
DayGroupData? group,
|
||||
SkyKeyframe keyframe)
|
||||
=> RenderPass(camera, cameraWorldPos, dayFraction, group, keyframe, weatherPass: false);
|
||||
=> RenderPass(camera, cameraWorldPos, dayFraction, group, keyframe, postScenePass: false);
|
||||
|
||||
/// <summary>
|
||||
/// Draw the WEATHER sky objects (the foreground rain mesh
|
||||
/// <c>0x01004C42</c>/<c>0x01004C44</c> on Rainy DayGroups, plus the
|
||||
/// per-storm 5cm flash dummies — every <c>SkyObject</c> with
|
||||
/// <c>Properties & 0x04 != 0</c>). Called AFTER the scene so the
|
||||
/// rain meshes paint on top of terrain and entities — that's the
|
||||
/// retail-faithful order from <c>LScape::draw</c> at
|
||||
/// <c>0x00506330</c>, where <c>GameSky::Draw(1)</c> fires after the
|
||||
/// <c>DrawBlock</c> loop. With depth-test disabled and additive blend
|
||||
/// (the rain Surface flag <c>0x080000C5</c> includes Additive), the
|
||||
/// 815m-tall rain cylinder's bright streak texels add over the scene
|
||||
/// — making rain appear in the air between camera and character
|
||||
/// instead of only at the horizon.
|
||||
/// Draw the POST-SCENE sky objects (the foreground rain mesh
|
||||
/// <c>0x01004C44</c> on Rainy DayGroups, plus any other SkyObject with
|
||||
/// <c>Properties & 0x01 != 0</c>). Called AFTER the scene so these
|
||||
/// meshes paint on top of terrain and entities — retail-faithful order
|
||||
/// from <c>LScape::draw</c> at <c>0x00506330</c>, where
|
||||
/// <c>GameSky::Draw(1)</c> fires after the <c>DrawBlock</c> loop and
|
||||
/// renders the <c>after_sky_cell</c> contents. With depth-test
|
||||
/// disabled and additive blend (the rain Surface flag includes
|
||||
/// Additive), the 815m-tall rain cylinder's bright streak texels add
|
||||
/// over the scene — making rain appear in the air between camera and
|
||||
/// character instead of only at the horizon.
|
||||
/// <para>
|
||||
/// Method name kept as <c>RenderWeather</c> for API stability; the
|
||||
/// pass actually partitions on <see cref="SkyObjectData.IsPostScene"/>
|
||||
/// (Properties bit <c>0x01</c>), not <see cref="SkyObjectData.IsWeather"/>
|
||||
/// (bit <c>0x04</c>). The two bits are independent in retail per
|
||||
/// <c>GameSky::CreateDeletePhysicsObjects</c> at <c>0x005073c0</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public void RenderWeather(
|
||||
ICamera camera,
|
||||
|
|
@ -129,14 +135,15 @@ public sealed unsafe class SkyRenderer : IDisposable
|
|||
float dayFraction,
|
||||
DayGroupData? group,
|
||||
SkyKeyframe keyframe)
|
||||
=> RenderPass(camera, cameraWorldPos, dayFraction, group, keyframe, weatherPass: true);
|
||||
=> RenderPass(camera, cameraWorldPos, dayFraction, group, keyframe, postScenePass: true);
|
||||
|
||||
/// <summary>
|
||||
/// Shared pass for <see cref="RenderSky"/> and <see cref="RenderWeather"/>.
|
||||
/// Sets up the same GL state for both (depth-test off, additive +
|
||||
/// alpha-blend per submesh, camera-anchored translation) and iterates
|
||||
/// only the SkyObjects matching the requested partition by
|
||||
/// <see cref="SkyObjectData.IsWeather"/>.
|
||||
/// <see cref="SkyObjectData.IsPostScene"/> — bit <c>0x01</c> per the
|
||||
/// retail decomp at <c>GameSky::MakeObject</c> (<c>0x00506ee0</c>).
|
||||
/// </summary>
|
||||
private void RenderPass(
|
||||
ICamera camera,
|
||||
|
|
@ -144,7 +151,7 @@ public sealed unsafe class SkyRenderer : IDisposable
|
|||
float dayFraction,
|
||||
DayGroupData? group,
|
||||
SkyKeyframe keyframe,
|
||||
bool weatherPass)
|
||||
bool postScenePass)
|
||||
{
|
||||
if (group is null || group.SkyObjects.Count == 0) return;
|
||||
|
||||
|
|
@ -197,14 +204,20 @@ public sealed unsafe class SkyRenderer : IDisposable
|
|||
for (int i = 0; i < group.SkyObjects.Count; i++)
|
||||
{
|
||||
var obj = group.SkyObjects[i];
|
||||
// Partition by weather flag — the caller chose either the
|
||||
// pre-scene sky pass (non-weather) or the post-scene weather
|
||||
// pass (weather only). Mirrors retail GameSky::Draw at
|
||||
// 0x00506ff0 where arg2==0 iterates non-weather sky_obj
|
||||
// entries (filtered by property bit 0x04 == 0 inside the
|
||||
// loop) and arg2==1 draws after_sky_cell which only contains
|
||||
// weather objects.
|
||||
if (obj.IsWeather != weatherPass) continue;
|
||||
// Partition by post-scene flag (Properties bit 0x01) — the
|
||||
// caller chose either the pre-scene sky pass (bit clear) or
|
||||
// the post-scene pass (bit set). Mirrors retail
|
||||
// GameSky::CreateDeletePhysicsObjects at 0x005073c0 / decomp
|
||||
// line 269036 which routes (Properties & 1) into
|
||||
// before_sky_cell vs after_sky_cell, and GameSky::Draw at
|
||||
// 0x00506ff0 which renders those cells in the two passes.
|
||||
// NOTE: bit 0x04 (IsWeather) is independent — it gates whether
|
||||
// the object is instantiated when weather_enabled is false.
|
||||
// Earlier acdream incorrectly used IsWeather for this
|
||||
// partition, putting the outer rain cylinder 0x01004C42
|
||||
// (Props=0x04, NO bit 0x01) into the post-scene pass with the
|
||||
// foreground rain — double-thick rain not matching retail.
|
||||
if (obj.IsPostScene != postScenePass) continue;
|
||||
if (!obj.IsVisible(dayFraction)) continue;
|
||||
|
||||
// Apply per-keyframe replace overrides.
|
||||
|
|
@ -267,7 +280,7 @@ public sealed unsafe class SkyRenderer : IDisposable
|
|||
// (camera-119.89)..(camera+694.90) in view space — camera
|
||||
// is inside, looking in any direction shows surrounding
|
||||
// walls — the volumetric foreground-rain look retail has.
|
||||
if (weatherPass)
|
||||
if (postScenePass)
|
||||
model = model * Matrix4x4.CreateTranslation(0f, 0f, -120f);
|
||||
|
||||
_shader.SetMatrix4("uModel", model);
|
||||
|
|
|
|||
|
|
@ -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 & 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 & 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 & 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">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue