From d961d0bc06ba42d6ebf1da9c2a7bb648b71752b4 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 2 Sep 2026 17:00:01 +0700 Subject: [PATCH] Shadowfang's far rooms lose their cyan, and the street lamps hum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Shadowfang Keep's far doorways no longer fill with flat blue-cyan at distance: it was never a hole — the room behind the arches is in the visible set at every camera distance — it was the building's own MFOG record (rgb 40,68,79 at end 106.9 yd, ~95% saturated at 70 yd) painted over a room the reference shows under the scene fog, because benilla picked the fog lane STATICALLY per batch (`flags & 0x48`) where the client's `[0xca7f00]` gate decides it per frame per group along the portal walk; the flood publishes that bit now and all three lanes (entity, retained, WMO liquid) read it, and the wow-re carve confirmed the predicate verbatim and added the conjunct we lacked — the MFOG target exists only when one of the camera's own containing groups is a true interior, so in a courtyard the interior triple decays to the scene triple, which is what a unit standing in the keep reads - every world-placed M2 was silent — the street lamp's hum, the campfire, the waterfall, the windmill: no doodad ever wrote into the anim-event stream (three producers, none a terrain placement), and the sound clock was gated on the RIG, which is rightly never built for a model whose idle is the static mesh — `KalidarStreetLamp01.m2` is one looping 3.3 s Stand keying no bone at all, carrying one `$DSL`; the census (`benilla-extract soundeventscan`) counts 273 carrier models, 108 on tiers no rig gate arms; a sound-carrying placement now gets a clock-only host, `$DSL` is a sustained emitter, and the RE foldback settled three laws: `$DSL` ALWAYS loops and consults no flag (the kit's 0x200 bit is a lane select and 0x400 is random pitch — a neat four-row correlation of ours refuted), `$DSE` is the stop token, and a doodad holds ONE registration slot, which is why `bellows.m2`'s two loops alternate rather than drone - the reference voices sound IDS, not doodads: `$DSL` registers the doodad's position as one record in a 32-entry pool keyed by SoundEntries id (`0x461d80`), each entry runs one channel at whichever of its records is nearest the listener — moved, not restarted (`0x7a5b10`), so one hum follows you down a row of lamps — the pump admits the first four entries by claim order and fades the rest over 3.0 s; the per-id duplicate suppressors turn out to be the one-shot lane's and were being applied to every lane (a three-second dropout every time a loop came back under the cap); three 76 yd hops between two Darnassus lamps now produce ONE play, and Darnassus runs 12 entitled ambiences with 8 withheld by the cap - the selection ring joins the decal band too, and it draws UNDER the shadow: the RE found the ring emitted from inside PHASE 1's own M2 node drain (`[node+0xb4] = 0x481540` → the `+0x38` tick → `0x608e00`), the same iteration that then draws that node's blob shadow — the reverse of the tie-break this ladder had asserted since both rungs existed, a rule invented here and never read anywhere; `WOW_EFFECT_TRACE` names every rung of the band and prints its index in the sorted transparent phase, which is the ladder read straight off a live frame Co-Authored-By: Claude Fable 5.1 --- crates/benilla-app/src/blob_shadow.rs | 6 +- crates/benilla-app/src/capture/mod.rs | 6 + crates/benilla-app/src/creature_anim/lod.rs | 1 + crates/benilla-app/src/doodad_events.rs | 71 ++ crates/benilla-app/src/lib.rs | 2 + crates/benilla-app/src/pipe_warm/mod.rs | 2 +- crates/benilla-app/src/sound/anim_events.rs | 83 +- crates/benilla-app/src/sound/doodad_pool.rs | 714 ++++++++++++++++++ crates/benilla-app/src/sound/kit.rs | 83 +- crates/benilla-app/src/sound/missile.rs | 4 +- crates/benilla-app/src/sound/mod.rs | 2 + crates/benilla-app/src/target/reticle.rs | 15 +- crates/benilla-app/src/target/ring.rs | 2 +- crates/benilla-assets/src/shaders/liquid.wgsl | 28 +- .../benilla-assets/src/shaders/wow_model.wgsl | 21 +- .../src/bin/benilla-extract/main.rs | 12 + .../src/bin/benilla-extract/scan/mod.rs | 5 +- .../src/bin/benilla-extract/scan/sequence.rs | 148 ++++ crates/benilla-formats/src/models/anim.rs | 87 +++ crates/benilla-world/src/doodad_anim/mod.rs | 133 +++- crates/benilla-world/src/exterior_cull.rs | 1 + crates/benilla-world/src/liquid/surface.rs | 5 + crates/benilla-world/src/mesh_tag.rs | 20 +- .../src/model_render/visibility.rs | 62 +- crates/benilla-world/src/particles.rs | 1 + crates/benilla-world/src/particles/render.rs | 37 +- .../benilla-world/src/shaders/static_gx.wgsl | 12 +- crates/benilla-world/src/sky_order.rs | 72 +- crates/benilla-world/src/static_gx/cull.rs | 43 +- crates/benilla-world/src/static_gx/mod.rs | 2 +- crates/benilla-world/src/static_gx/pick.rs | 18 +- crates/benilla-world/src/static_gx/render.rs | 68 +- .../src/terrain_stream/spawn/mod.rs | 3 + .../benilla-world/src/wmo_portal/audit/mod.rs | 12 + .../benilla-world/src/wmo_portal/audit/pin.rs | 207 ++++- crates/benilla-world/src/wmo_portal/mod.rs | 182 ++++- 36 files changed, 2027 insertions(+), 143 deletions(-) create mode 100644 crates/benilla-app/src/doodad_events.rs create mode 100644 crates/benilla-app/src/sound/doodad_pool.rs diff --git a/crates/benilla-app/src/blob_shadow.rs b/crates/benilla-app/src/blob_shadow.rs index 821c6747..effa6501 100644 --- a/crates/benilla-app/src/blob_shadow.rs +++ b/crates/benilla-app/src/blob_shadow.rs @@ -11,7 +11,11 @@ //! [`GroundDecalSurface`] set yet — the shadow lands on terrain + WMO faces only). //! - **Frame slot**: PHASE 1, among the opaque drains — `0x6812c5 call 0x683dd0`, fifth of the //! row `0x6812b1`–`0x6812ca` inside `0x681070`, which the driver `0x483460` calls at -//! `0x48361d` (wow-re `water-frame-straddle.md` §1 + `unit-blob-shadow.md` Q1). So the shadow +//! `0x48361d` (wow-re `water-frame-straddle.md` §1 + `unit-blob-shadow.md` Q1). `0x683dd0` is +//! the **M2 node drain**, and the same loop body ticks each node's object first +//! (`0x48160c call [obj vt+0x38]` → the selection ring) and gates its shadow second +//! (`0x683ec3`) — so per unit the additive ring goes down and this modulate darkens it, never +//! the other way round (wow-re `decal-frame-slot.md`). So the shadow //! lands after terrain and WMO and **before** everything else: the footprint decals //! (`0x483654`), the M2 opaque pass (`0x4836a6`), the water surfaces (phase 3, drawn *between* //! the two M2 transparent passes) and both of those passes. Every transparent in the world diff --git a/crates/benilla-app/src/capture/mod.rs b/crates/benilla-app/src/capture/mod.rs index bcf5a423..9eaf2f91 100644 --- a/crates/benilla-app/src/capture/mod.rs +++ b/crates/benilla-app/src/capture/mod.rs @@ -36,6 +36,12 @@ //! quiescence, or waiting for pixel stability cannot help. It is a renderer defect that the harness //! correctly *reports*; do not try to gate it away here. Read 0815 before touching this file's phases. //! +//! **`creature-indoor-front` is one of them** (named 2026-09-01, while A/B-ing 1787's fog gate): two +//! runs of ONE unchanged build land in the two states at MAE 4.518, the whole difference confined to +//! the subject's own brightness with the room around it pixel-identical, and the settle counts differ +//! (235 vs 211 frames). If a change you are A/B-ing shows exactly 4.518 here, re-run the baseline +//! before believing it — the number is the flake's, not yours. +//! //! ## Running one capture by hand //! **Run through Cargo — never the built binary directly:** //! ```text diff --git a/crates/benilla-app/src/creature_anim/lod.rs b/crates/benilla-app/src/creature_anim/lod.rs index 23220b20..2eab8a22 100644 --- a/crates/benilla-app/src/creature_anim/lod.rs +++ b/crates/benilla-app/src/creature_anim/lod.rs @@ -597,6 +597,7 @@ mod tests { world_from_local: bevy::math::Affine3A::IDENTITY, name_set: 0, visible: vec![false, false], + interior_fog: vec![false, false], liquid_visited: vec![false, false], flooded: vec![None, None], }) diff --git a/crates/benilla-app/src/doodad_events.rs b/crates/benilla-app/src/doodad_events.rs new file mode 100644 index 00000000..1b1a7786 --- /dev/null +++ b/crates/benilla-app/src/doodad_events.rs @@ -0,0 +1,71 @@ +//! The **placed-doodad** animation-event scanner — the third and last producer feeding the +//! [`AnimSoundEvent`] stream, beside the creature scanner ([`crate::creature_anim`]) and the +//! GameObject one ([`crate::go_anim`]). +//! +//! Bug B345 ("every world-placed M2 doodad is silent") was this hole: a lamp's hum, a campfire's +//! crackle, a waterfall, a windmill and a gnome machine are all authored as `$DSL`/`$DSO`/`$SND` +//! keys on the model's own idle sequence — and **nothing in benilla ever wrote a doodad's events +//! into the stream**. The routing end ([`crate::sound::anim_events`]) has handled those tags since +//! decision 0070 slice 3; it simply never received one from a doodad, because the only two +//! scanners gate on `AnimDriver` (units) and `GoAnim` (server-sent GameObjects), neither of which a +//! terrain-stream placement ever gets. +//! +//! **Why it is not enough to scan the hosts that already exist.** The corpus says 273 models author +//! a doodad sound marker (`benilla-extract soundeventscan`); only 165 of them are the `FirstSeq` +//! tier that carries an `AnimationPlayer`. The other 108 — including the reported lamp, +//! `KalidarStreetLamp01.m2`, one looping 3.333 s Stand that keys no bone at all and carries exactly +//! one `$DSL` → `NightElfStreetLampLoop` — are bind-posed, and decision 0130's content gate +//! deliberately builds them no rig. That gate is right about pixels and was never a claim about the +//! clock: the reference arms **every** placed doodad and cycles it (wow-re `doodad-anim-host.md` +//! §1/§5). So `doodad_anim::spawn_anim_host` now gives a sound-carrying model a clock-only host — +//! the arm bookkeeping, no player — and this scanner reads that arm. +//! +//! **The clock is the shared one, never the player** ([`DoodadAnimHost::arm_clock`]): the reference +//! gates the cycle on *linkage* (residency), not on the draw, so a campfire behind you keeps +//! crackling. Reading the draw-gated `AnimationPlayer` would silence it the moment you turned away. + +use bevy::prelude::*; + +use benilla_world::doodad_anim::DoodadAnimHost; +use benilla_world::schedule::WorldStage; + +use crate::creature_anim::{advance_track, scan_events, AnimSoundEvent, TrackMemory}; +use benilla_assets::ModelAnimations; + +/// Fire the event keyframes each placed doodad's armed clip crossed this frame. +/// +/// The arming rules are the shared [`advance_track`]/[`scan_events`] ones the other two scanners +/// use — an arm frame fires nothing, the frame after it opens the clip's `t = 0` head (which is +/// where essentially every doodad sound key sits), and a loop wrap fires tail-then-head. A +/// variation re-roll ([`benilla_world::doodad_anim`]'s self-sustaining re-arm) changes the node, so +/// [`advance_track`] sees a fresh arm and never scans across the seam. +fn fire_doodad_anim_events( + time: Res