From d95a8d2a558841a69fa159cf661ed0f8b65ab4cf Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 27 Apr 2026 12:05:12 +0200 Subject: [PATCH] refactor(weather): delete legacy camera-attached rain/snow particle emitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-research workaround at GameWindow.UpdateWeatherParticles + BuildRainDesc + BuildSnowDesc was acdream's stand-in for retail's weather rendering. It emitted billboarded particles inside a 15m disk attached to the camera ('AttachLocal'), with a broken alpha fade (0.3 → 0 caused rain to vanish at exact ground level — Issue #1) and a fixed disk that visibly framed the player even at speed. Retail rain is the world-space mesh path (SkyRenderer.RenderWeather): GfxObj 0x01004C42 / 0x01004C44 — hollow octagonal cylinder, 113m radius, 815m tall, anchored at player_pos + (0, 0, -120m) per GameSky::UpdatePosition at 0x00506dd0 — drawn AFTER the landblock pass per LScape::draw at 0x00506330. Snow renders identically when a Snowy DayGroup is active: the partition by Properties&0x04 picks up snow weather meshes for free. The legacy emitter was gated behind ACDREAM_FAKE_RAIN_PARTICLES=1 in the previous commit (3e0da49) so the world-space path could be A/B-compared. Visual verification this session confirmed the world- space path is correct; deleting the legacy code removes ~120 LOC plus the env var, the gate, the _rainEmitterHandle / _snowEmitterHandle fields, and the _lastWeatherKind state machine. Files affected: GameWindow.cs: drop UpdateWeatherParticles, BuildRainDesc, BuildSnowDesc, emitter-handle fields, last-weather-kind state, and the gated call site. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 141 +++--------------------- 1 file changed, 13 insertions(+), 128 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 17ed773..66888b6 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -373,12 +373,6 @@ public sealed class GameWindow : IDisposable private long _loadedSkyDayIndex = long.MinValue; private AcDream.Core.World.DayGroupData? _activeDayGroup; - // Current rain/snow emitter handles — spawned on weather-kind change - // and stopped when the kind leaves Rain/Snow. Non-zero == active. - private int _rainEmitterHandle; - private int _snowEmitterHandle; - private AcDream.Core.World.WeatherKind _lastWeatherKind = - AcDream.Core.World.WeatherKind.Clear; private double _weatherAccum; // F7 / F10 debug-cycle steps for time + weather. Initialized out of @@ -4229,20 +4223,19 @@ public sealed class GameWindow : IDisposable Weather.Tick(nowSeconds: _weatherAccum, dayIndex: dayIndex, dtSeconds: (float)deltaSeconds); _weatherAccum += deltaSeconds; - // Update the rain/snow particle emitters when the weather kind - // changes. Keep the emitters fed by the ParticleSystem tick so - // visuals stay alive frame-over-frame. - // - // Bug A note (2026-04-26): retail rain is the world-space mesh - // 0x01004C42/0x01004C44 rendered in SkyRenderer.RenderWeather - // AFTER the scene — see docs/research/2026-04-26-sky-investigation-handoff.md. - // The camera-attached emitter path here is acdream's old - // pre-research workaround (broken alpha fade, fixed disk around - // camera). It's kept gated behind ACDREAM_FAKE_RAIN_PARTICLES=1 - // so the retail-faithful path can be A/B-tested against it - // without uninstalling the legacy code outright. - if (System.Environment.GetEnvironmentVariable("ACDREAM_FAKE_RAIN_PARTICLES") == "1") - UpdateWeatherParticles(atmo); + // (Pre-Bug-A code spawned camera-attached rain/snow particle + // emitters here as a workaround for missing weather-mesh + // rendering. Deleted 2026-04-26 once the retail-faithful world- + // space mesh path landed in SkyRenderer.RenderWeather. Retail + // rain is GfxObj 0x01004C42/0x01004C44 — a hollow octagonal + // cylinder anchored at player_pos + (0, 0, -120m) per + // GameSky::UpdatePosition at 0x00506dd0 — drawn after the + // landblock pass per LScape::draw at 0x00506330. There is no + // server-driven weather event and no camera-attached emitter + // in retail. Snow renders identically when a Snowy DayGroup is + // active in some other Region; the partition by Properties&0x04 + // and the SkyRenderer.RenderWeather pass both pick up snow + // weather meshes for free.) // Phase E.3: advance live particle emitters AFTER animation tick // so emitters spawned by hooks fired this frame get integrated. @@ -5241,114 +5234,6 @@ public sealed class GameWindow : IDisposable } } - /// - /// Keep the rain/snow camera-anchored emitters aligned with the - /// current weather state. Spawns on entry, stops on exit, with no - /// per-frame churn while the state is stable. Emitters are camera- - /// local () - /// so walking never leaves the rain volume (r12 §7). - /// - private void UpdateWeatherParticles(in AcDream.Core.World.AtmosphereSnapshot atmo) - { - if (_particleSystem is null) return; - - if (atmo.Kind == _lastWeatherKind) return; // no change - - // Stop any existing emitters first. - if (_rainEmitterHandle != 0) - { - _particleSystem.StopEmitter(_rainEmitterHandle, fadeOut: true); - _rainEmitterHandle = 0; - } - if (_snowEmitterHandle != 0) - { - _particleSystem.StopEmitter(_snowEmitterHandle, fadeOut: true); - _snowEmitterHandle = 0; - } - - // Anchor at camera world position; AttachLocal keeps it moving. - var anchor = System.Numerics.Vector3.Zero; - if (_cameraController is not null) - { - System.Numerics.Matrix4x4.Invert(_cameraController.Active.View, out var inv); - anchor = new System.Numerics.Vector3(inv.M41, inv.M42, inv.M43); - } - - switch (atmo.Kind) - { - case AcDream.Core.World.WeatherKind.Rain: - case AcDream.Core.World.WeatherKind.Storm: - _rainEmitterHandle = _particleSystem.SpawnEmitter( - BuildRainDesc(), anchor); - break; - case AcDream.Core.World.WeatherKind.Snow: - _snowEmitterHandle = _particleSystem.SpawnEmitter( - BuildSnowDesc(), anchor); - break; - } - - _lastWeatherKind = atmo.Kind; - } - - /// - /// Rain emitter tuned per r12 §7: streaks falling at ~50 m/s with - /// a slight wind bias, 500 drops/sec, 2000 max alive, 1.2s life so - /// drops cover the ~60m fall at terminal velocity. - /// - private static AcDream.Core.Vfx.EmitterDesc BuildRainDesc() => new() - { - DatId = 0xFFFF_0001u, // synthetic id - Type = AcDream.Core.Vfx.ParticleType.LocalVelocity, - Flags = AcDream.Core.Vfx.EmitterFlags.AttachLocal | - AcDream.Core.Vfx.EmitterFlags.Billboard, - EmitRate = 500f, - MaxParticles = 2000, - LifetimeMin = 1.0f, - LifetimeMax = 1.4f, - OffsetDir = new System.Numerics.Vector3(0, 0, 1), - MinOffset = 0f, - MaxOffset = 50f, - SpawnDiskRadius = 15f, - InitialVelocity = new System.Numerics.Vector3(0.5f, 0f, -50f), - VelocityJitter = 2f, - Gravity = System.Numerics.Vector3.Zero, - StartColorArgb = 0x40B0C0E0u, - EndColorArgb = 0x20B0C0E0u, - StartAlpha = 0.3f, - EndAlpha = 0f, - StartSize = 0.05f, - EndSize = 0.05f, - }; - - /// - /// Snow emitter tuned per r12 §8: slow fall at ~2 m/s, tumbling - /// sideways drift, small billboards, 100 flakes/sec, long lifespan. - /// - private static AcDream.Core.Vfx.EmitterDesc BuildSnowDesc() => new() - { - DatId = 0xFFFF_0002u, - Type = AcDream.Core.Vfx.ParticleType.LocalVelocity, - Flags = AcDream.Core.Vfx.EmitterFlags.AttachLocal | - AcDream.Core.Vfx.EmitterFlags.Billboard, - EmitRate = 100f, - MaxParticles = 1000, - LifetimeMin = 4f, - LifetimeMax = 8f, - OffsetDir = new System.Numerics.Vector3(0, 0, 1), - MinOffset = 0f, - MaxOffset = 30f, - SpawnDiskRadius = 15f, - InitialVelocity = new System.Numerics.Vector3(0.3f, 0.2f, -2f), - VelocityJitter = 0.8f, - Gravity = System.Numerics.Vector3.Zero, - StartColorArgb = 0xE0FFFFFFu, - EndColorArgb = 0x80FFFFFFu, - StartAlpha = 0.85f, - EndAlpha = 0.3f, - StartSize = 0.08f, - EndSize = 0.06f, - }; - // ── Phase I.2 — DebugPanel helpers ──────────────────────────────── // // The ImGui DebugPanel reads through DebugVM closures that ask