acdream/docs/plans/2026-04-23-sky-weather-lightning-port.md
Erik d5e37694ed docs(sky): port plan for PhysicsScript/fog/lightning/crossfade
Captures where we stand after Phase 4b and lays out the remaining
retail-faithful port work across four phases (5-8):

- Phase 5: PhysicsScript loader + runtime + sky lifecycle. Replaces
  WeatherSystem's crude "DayGroup name contains Rainy → spawn rain"
  shortcut with retail's actual PES-driven particle emission.
- Phase 6: Fog on sky meshes. The sky frag currently ignores fog
  uniforms; retail's D3D fog applies to sky.
- Phase 7: Lightning flash trigger + thunder audio for storm keyframes.
- Phase 8: Weather / DayGroup crossfade (DAT_008427a9 / _DAT_008427b8
  lerp) + AdminEnvirons override → fog crossfade.

User observation 2026-04-23 during Phase 4b verification: "Now it is
raining when it should not be." Root cause traced to the
SetKindFromDayGroupName string match firing rain particles on a "Rainy"
DayGroup regardless of whether that DayGroup actually has a visible
rain-emitting SkyObject. Proper fix requires porting PhysicsScript.

Also commits the earlier research from agent Q1-Q6:
`docs/research/2026-04-23-sky-material-state.md`.

Four parallel decompile agents are in flight as of this commit:
- PhysicsScript dat + runtime
- Sky↔PES wiring + emitter lifecycle
- Lightning + weather crossfade
- Fog on sky + vertex distance

Phase 5 implementation starts once those land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:53:46 +02:00

136 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (830 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).