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