# Phase 5+ Port Plan — Sky / Weather / Lightning, retail-verbatim **Date:** 2026-04-23 **Scope:** Port the remaining retail-accurate pieces of the sky/weather/lightning system so acdream visually matches a side-by-side retail client in all day/night + weather states (clear, cloudy, rainy, stormy). ## Where we are today (main, commit 2802fb2) Sky core, landed across Phases 1-4b: - Region-dat SkyDesc loader with GameTime offsets ✓ - Retail LCG DayGroup picker (seed = Year × DaysPerYear + DayOfYear, Phase 3g) ✓ - Calendar tick extraction with `GameTime.ZeroTimeOfYear = 3600` (Phase 3f) ✓ - Per-vertex D3D-fixed-function lighting formula (Phase 4, Phase 4b clamp) ✓ - Sky objects drawn with visibility, arc sweep, UV scroll ✓ - ACDREAM_DUMP_SKY diagnostic for retail-faithfulness verification ✓ - RetailTimeProbe tool for live memory comparison ✓ Left to do: 1. **PhysicsScript** — no loader, no runtime, no sky-side integration. User-visible: rain doesn't spawn when retail rolls a PES-carrying SkyObject. 2. **Fog on sky** — shader ignores fog uniforms; retail's D3D fog applies to sky. 3. **Lightning flash trigger** — storm timer + visual not ported. 4. **Weather / DayGroup crossfade** — retail's 10-second smooth blend between keyframe sets not ported. 5. **AdminEnvirons override** — packet handler exists as a stub on the wire side; not wired to our rendering. ## Phases (execute in order) ### Phase 5 — PhysicsScript loader + runtime + sky wiring Output of parallel research agents #1 + #2 (2026-04-23): - `2026-04-23-physicsscript.md` — dat schema + runtime interpreter - `2026-04-23-sky-pes-wiring.md` — sky → PES lifecycle Sub-phases: - **5a** Port `PhysicsScript` dat type + any nested types. Add to `AcDream.Core/Dat/`. - **5b** Port the runtime interpreter to C#. `AcDream.Core/Vfx/PhysicsScriptRunner.cs`. Wire into existing `ParticleSystem` as the spawner — we do NOT build a new emitter class, reuse what's there. - **5c** Hook into `SkyRenderer` → on per-frame sky-object iteration, for each visible SkyObject with non-zero `DefaultPesObjectId`, ensure its PES is running. Despawn on visibility loss or DayGroup change. - **5d** Replace `WeatherSystem.SetKindFromDayGroupName`'s crude `"Rainy" → WeatherKind.Rain` string match with PES-driven spawning. The `WeatherKind` enum becomes fog/tone info only; particle emission is 100% PES-gated. Tests: PhysicsScript parser conformance (golden bytes → expected struct), runtime determinism (same script + same seed → same particle stream). ### Phase 6 — Fog on sky meshes Output of research agent #4: `2026-04-23-sky-fog.md`. Sub-phases: - **6a** `sky.vert` computes fog factor per vertex. Formula from the agent's findings (expected: linear per-vertex based on eye-space Z). - **6b** `sky.frag` applies `mix(fragment, fogColor, fogFactor)` before the lightning-flash bump. - **6c** If sky meshes render at distances that saturate the keyframe's FOGEND (sky would be pure fog color), either: - Cap sky mesh eye-space Z at FOGEND - epsilon for fog purposes only, OR - Use a separate "sky fog" distance parameter per retail's behavior. Tests: render-golden at 4 canonical times (dawn/noon/dusk/midnight) + 3 DayGroups (Sunny / Cloudy / Stormy) — compare against retail screenshots. ### Phase 7 — Lightning flash trigger Output of research agent #3: `2026-04-23-lightning-crossfade.md` (shared with Phase 8 findings). Sub-phases: - **7a** Port retail's storm-keyframe lightning timer. - **7b** Wire to existing `uFogParams.z` lightning-flash uniform in the UBO (sky.frag already consumes it). - **7c** Wire thunder audio cue via `AdminEnvirons.Thunder1Sound..Thunder6Sound` or a local per-flash delay (retail uses speed-of-sound distance). ### Phase 8 — Weather / DayGroup crossfade Also from agent #3. Sub-phases: - **8a** Port `DAT_008427a9` flag + `_DAT_008427b8` progress mechanics into our SkyStateProvider or a new CrossfadeOrchestrator class. - **8b** Trigger a crossfade when: - DayGroup index changes (day rollover hits a new weather roll) — smooth swap of keyframe set over retail's step constant `_DAT_007c7208`. - `AdminEnvirons` override arrives — smooth fog transition to the override color. - **8c** AdminEnvirons wiring: the packet handler stub in `WeatherSystem.Override` already exists; wire it to the crossfade trigger + our renderer. ### Optional Phase 9 — Per-cell AdjustPlanes terrain relight From earlier research (`2026-04-23-sky-decompile-hunt-A.md` §1): retail reruns `FUN_00532440` on every terrain cell whenever the sky keyframe advances. We currently bake terrain vertex lighting once and don't refresh. Visible effect: terrain doesn't darken smoothly as the sun sets. Deferred because it's higher effort and lower payoff than 5-8. ## Success criteria 1. A `+Acdream` character stationary in outdoor Holtburg for 30 real minutes (about 15 Derethian minutes with our 1:1 tick rate) produces a sky that, side-by-side with retail, is visually indistinguishable within lighting equipment tolerances (color temperature, saturation). 2. Rolling a DayGroup that contains a rain-emitting SkyObject causes acdream to spawn rain particles MATCHING retail's rain cadence (drop rate, direction, lifetime). 3. During a Stormy DayGroup, acdream shows lightning flashes at the retail cadence (8–30 sec between strikes, flash rises in ~50ms, decays in ~200ms). 4. An `AdminEnvirons RedFog` packet arriving mid-play crossfades acdream's fog to the red tint within ~10 real seconds, same direction retail does. ## Non-goals (for this plan) - **PhysicsScript author tools** — we parse + run; we don't edit. - **Retail-accurate GPU particle rendering** — reuse our existing `ParticleSystem` backend. PhysicsScript drives IT, not a new emitter. - **Exotic EnvironChangeTypes** (the Thunder3Sound, DarkLaughSound, etc. non-fog variants) — those are admin-only and we can stub-log them. - **Per-landblock weather variation** — retail weather is Dereth-wide. ## Open risks / unknowns (to be resolved by agents) - Will our `ParticleSystem.SpawnEmitter` API be sufficient, or does retail's PhysicsScript need commands we don't expose? (agent #1). - Does sky mesh vertex data need a 1e6-far-plane fog distance rescaling, or is retail's FOGEND authored large enough to cover sky? (agent #4). - Does retail sync the lightning random timer across all clients (so everyone sees the same strike), or is it truly client-local? (agent #3).