The aurora was never missing data — it was a missing mechanism plus a misread. New decompile evidence closes the April-2026 contradiction: retail plays the sky carriers' PES through the Setup's own DefaultScript (GameSky::MakeObject @0x00506EE0 -> CPhysicsObj::makeObject @0x00513970 sets state|=0x80000; animate_static_object @0x00513DF0 ticks ScriptManager + ParticleManager). The pes_id column stays dead — that half of the April finding stands; the ids are byte-equal mirrors. - SkyPesFrameController is now the production owner (ACDREAM_ENABLE_SKY_PES deleted): script ids resolve from the Setup DefaultScript (SkyObjectData.DefaultScriptId; the pes_id column is a one-time-logged cross-check), slots persist by (index, gfx id, properties) per CreateDeletePhysicsObjects @0x005073C0 — a day-group swap keeping the carrier no longer restarts its emitters — and stale slots stop before replacements claim the slot-derived owner id. - RetailParticleFacing ports calc_draw_frame @0x0050DFA0: degrade mode 2 faces the viewer roll-free (set_vector_heading) instead of the camera plane; modes 3/4/5 spin the authored frame around one local axis (rotate_around_axis_to_vector) — Dereth authors 54 mode-5 emitters that previously got no facing at all; 1,583 mode-2 emitters get the exact law; authored/mode-1 paths are unchanged. - The 2026-08-23 'whole-sky tint' was the Rainy-group lightning/thunder PES playing at the debug anchor inside their 0.03-0.19 window, not the aurora: the aurora is nine faint viewer-facing glows pulsing on 6.7/15/55-minute rebirth cycles, in every day group, all day. Research: docs/research/2026-08-23-sky-default-script-port.md. Register: AD-112 filed (camera-anchored synthetic owners vs sky-cell physics objects). ISSUES #2 corrected (the playback ban is lifted by the new evidence); #28 fix landed pending the connected night gate. Tests: RetailParticleFacingTests (16), SkyPesFrameControllerTests (6); hermetic suites App 6,076/0, Core 4,905/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
119 lines
6.5 KiB
Markdown
119 lines
6.5 KiB
Markdown
# Sky default-script (aurora/lightning/thunder) — the retail mechanism, proven
|
||
|
||
**Date:** 2026-08-23 · **Issues:** #28 (aurora), #2 (lightning, partial), #29 (clouds — not addressed here) · **Phase:** C.1.5c
|
||
|
||
## The April contradiction, resolved
|
||
|
||
Two prior research passes (`2026-04-23-sky-pes-wiring.md`, `2026-04-28-pes-pseudocode.md`)
|
||
correctly proved `GameSky` never **reads** `CelestialPosition.pes_id`
|
||
(`SkyDesc::GetSky @0x00501EC0` writes it at 0x00501FC9; no reader exists), and
|
||
issue #2 therefore banned per-SkyObject PES playback "without new decompile
|
||
evidence". This document is that evidence. Both were right and both missed the
|
||
actual route:
|
||
|
||
**The sky PES ids ride the sky Setups' own `DefaultScript`, and retail plays
|
||
them through the ordinary object default-script machinery — the `pes_id`
|
||
column is a dead mirror of the same ids.**
|
||
|
||
Verified in the installed Dereth DAT: Setup `0x02000714` (aurora carrier,
|
||
parts `0x010001EC`) has `DefaultScript = 0x330007DB` — byte-equal to its
|
||
SkyObject's `PesObjectId`. Same holds for `0x02000589→0x3300042C` (thunder
|
||
ping-pong), `0x02000588→0x33000428` (thunder variant), `0x02000BA6→0x33000453`
|
||
(lightning flash).
|
||
|
||
## The retail chain (named decomp, every link read this session)
|
||
|
||
1. `GameSky::UseTime @0x005075B0` (30 Hz) → `CRegionDesc::GetSky` →
|
||
`SkyDesc::GetSky @0x00501EC0` rebuilds the `CelestialPosition` list.
|
||
Per-object visibility: **if `begin_time == end_time` the object is always
|
||
included**; otherwise `begin <= t <= end` gates the gfx id (out-of-window →
|
||
`INVALID_DID`). The aurora object has `begin=end=0.00` in **all 20 day
|
||
groups** → always present, all day.
|
||
2. `GameSky::CreateDeletePhysicsObjects @0x005073C0`: an existing sky object
|
||
is **kept** when its current DataID equals the wanted gfx id AND the
|
||
properties word is unchanged AND (`props & 4` → `LScape::weather_enabled`
|
||
did not flip). Only a mismatch destroys/recreates. ⇒ the aurora object —
|
||
same id in every group — **persists across day-group changes**, and its
|
||
emitters keep their particle population.
|
||
3. `GameSky::MakeObject @0x00506EE0`: `props & 4` objects are only created
|
||
when `LScape::weather_enabled != 0`; `props & 1` selects `after_sky_cell`
|
||
(post-scene) vs `before_sky_cell`. Creation is
|
||
`CPhysicsObj::makeObject(gfx_id, 0, 0)`.
|
||
4. `CPhysicsObj::makeObject @0x00513970` → `InitPartArrayObject`: a Setup
|
||
with `default_script_id != 0` sets `state |= 0x80000` and registers via
|
||
`CPhysics::AddStaticAnimatingObject @0x00509AF0`.
|
||
5. `CPhysicsObj::animate_static_object @0x00513DF0` (per tick): state 0x80000
|
||
→ `ScriptManager::UpdateScripts` (starts/advances the default script — the
|
||
PES) and updates the object's `ParticleManager`. This is the ~150/min
|
||
`CallPES` churn the 2026-04-30 live trace counted (the thunder PES
|
||
`0x3300042C` ping-pongs via `CallPES` chains).
|
||
|
||
## The particle laws confirmed against our port
|
||
|
||
- `ParticleEmitterInfo::GetRandomOffset @0x005174A0`: random vector, **minus
|
||
its projection onto `offset_dir`**, normalized, × rand[min,max] — a disk
|
||
PERPENDICULAR to the dir (a shell when dir is zero). Our
|
||
`ParticleSystem.RandomOffset` is byte-faithful. The aurora's 450–700 m
|
||
"Z-dir" offsets are therefore a horizontal RING around the sky-object
|
||
origin, not a column above it.
|
||
- `Particle::Update @0x0051C290` writes **only the part origin** per type
|
||
formula (our `ComputePosition` matches, incl. Swarm cos/sin); only the
|
||
GR/LR parabolic variants rotate.
|
||
- `ParticleEmitter::SetInfo @0x0051CE90`: parts are ordinary
|
||
`CPhysicsPart::makePhysicsPart(hw_gfxobj_id)`; there is no special "2D
|
||
particle" draw. `CPhysicsPart::Draw @0x0050D7A0` always feeds
|
||
`DrawMesh(gfxobj[deg_level], &draw_pos)`.
|
||
- **The facing law** — `CPhysicsPart::calc_draw_frame @0x0050DFA0`, driven by
|
||
the FIRST degrade entry's mode (`GfxObjDegradeInfo::get_degrade
|
||
@0x0051E4B0`; `viewer_heading` = normalized part→viewer from
|
||
`UpdateViewerDistance @0x0050E030`):
|
||
|
||
```
|
||
draw = pos
|
||
switch deg_mode:
|
||
2: Frame::set_vector_heading(draw, viewer_heading) // face viewer, roll-free
|
||
3/4/5: Frame::rotate_around_axis_to_vector(draw, X/Y/Z, vh) // cylindrical, one free axis
|
||
else: authored orientation (mode 1, mode 0, out of range)
|
||
```
|
||
|
||
Our renderer camera-plane-aligned every "billboard" particle
|
||
(`cameraRight`/`cameraUp`); retail faces each part toward the viewer
|
||
per-part and honors constrained modes. `CPhysicsPart::Always2D @0x0050D8A0`
|
||
(mode != 1) is only consulted for **cell membership**
|
||
(`CLandCell::add_all_outside_cells @0x00533360`), not drawing.
|
||
|
||
- The aurora emitters (`0x32000455/56/57`): BirthratePerSec 10, max 3,
|
||
initial 3, lifespans 3300/900/400 s, StartTrans 0.8 → FinalTrans 1.0
|
||
(≤20 % opacity fading to nothing), StartScale 7–8, sprites
|
||
`0x01001A61..63` = single ±137.5 m quads whose 64×64 additive textures are
|
||
soft glow blobs (row/col profiled — no banding; peak RGB 36–71/255). First
|
||
degrade mode = **2** on all three. The visible aurora is therefore nine
|
||
huge, faint, viewer-facing glows in a slow Swarm drift — a pulse that
|
||
re-brightens when a cohort is reborn (~6.7/15/55 min cycles), not a steady
|
||
fixture.
|
||
|
||
## Why the 2026-08-23 experiment looked like "whole-sky tint"
|
||
|
||
The debug controller (`ACDREAM_ENABLE_SKY_PES=1`) ran under day group 16
|
||
(Rainy) at t=0.125 — inside the lightning window (0.03–0.19) — so the
|
||
**lightning-flash and thunder PES** played at the camera anchor alongside the
|
||
aurora. A flash sprite at the anchor IS a full-screen additive wash. The
|
||
aurora itself was drowned under a faithful-but-wrong-conditions storm.
|
||
|
||
## Port deltas (this change)
|
||
|
||
1. Sky default-script playback becomes **production** (no env flag): every
|
||
visible sky object whose Setup carries a `DefaultScript` plays it through
|
||
`PhysicsScriptRunner`, anchored at the camera (retail sky-cell space is
|
||
viewer-centered), pass-routed by `props & 1`.
|
||
2. **Persistence contract**: script/emitter state is keyed by
|
||
(gfx id, properties) per `CreateDeletePhysicsObjects` — a day-group flip
|
||
that keeps the same carrier Setup must NOT restart its emitters.
|
||
3. Weather gating: `props & 4` objects follow the weather-enabled state
|
||
(acdream's weather system), matching `MakeObject`'s guard.
|
||
4. `calc_draw_frame` facing law in the particle renderer: per-sprite degrade
|
||
mode picks face-viewer (2), axis-constrained (3/4/5), or authored (else)
|
||
orientation — replacing the blanket camera-plane alignment.
|
||
|
||
Script ids resolve from the **Setup's DefaultScript** (the retail source);
|
||
the `PesObjectId` column is only a cross-check.
|