your corpse is your own dead body, and particles take the room's light

- corpses render: a corpse create spawned a real entity, latched its guid for the reclaim send, and drew nothing — one missing EntityKind variant folded it into the catch-all and four downstream lanes then failed by default, not even the debug cube fired; the reference forks once, on the bones flag: a fresh body builds a character composite from the corpse's OWN appearance bytes and 19 item slots through the same compositor a living player is dressed by (its own hide-helm/hide-cloak bits honoured, the ranged slot never dresses, and weapons never dress because the client looks a packed display word up as a guid — we reproduce the outcome, not the dead lookup), while a bone pile takes its race and sex's DeathSkeleton, sixteen of which ship; the pose is one settled arm — Dead, or Drowned when the body lies more than 0.66666669 yd under a liquid surface — and a corpse is not a unit, so clicking one no longer sends a selection for a guid no unit owns
- a lit particle takes its MODEL's committed light node, not the sky's: indoors the reference lights an object's particles from the object's own node — the footprint's baked floor colours on the fixed interior axis plus the room's placed lights — never the day/night sun; a particle quad carries exactly one normal, world up, one constant per draw, so the whole node collapses to three floats, published off the same fold the model's mesh renders from so a mesh and its own particles can never disagree about the room they stand in; in Onyxia's Lair the 78 lit falling-rock emitters on the 26 anchors that classify interior now take the room's light
- and that lit lane is FIXED FUNCTION, not the mesh's soft SH curve: the shipped client null-binds both shader slots for a particle batch and re-zeroes the shader mask at the head of every batch, so a model and its own particles share the same committed light words and legitimately differ in the curve applied to them — the mesh's wrapping lobe against the hardware's max(N·L, 0) is 0.900 vs 0.869 on the key axis, up to the three nearest room lights arrive diffuse-only under GL's own 1/(0.7d + 0.03d²) falloff (a light at or below the emitter cannot reach it at all), and the clamp sits on the product, never the multiplier, because it is GL that clamps the lit vertex colour
- the cinematic letterbox has no aspect test in it: CinematicFrame.lua's if guards a recompute, not whether there are bars — below 4:3 the reference's bar textures simply stand at their declared 1024x128 on the client's native 1024x768 sheet, which is height/6, the same formula evaluated at exactly 4:3 — so one expression covers every window shape, min((h − min(w/2, h))/2, h/6): a 2:1 picture when wide, bars at 4:3 too; and a shown keyboard frame swallows EVERY key, not the ten names the host used to feed it — you could walk around underneath your own intro; the proof is the reference's own handler, which has to RunBinding("SCREENSHOT") by hand to get one key back; ESC still skips, and a W already held when the shot starts keeps running, which is faithful too

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 19:49:15 +07:00
co-authored by Claude Fable 5
parent d7fb26713c
commit e9e38bf6ec
39 changed files with 1851 additions and 205 deletions
@@ -0,0 +1,129 @@
//! **The cinematic frame's key-down consumption** — what makes a fly-by something you *watch*.
//!
//! `CinematicFrame` is fullscreen, keyboard-enabled, and carries an `OnKeyDown` that answers
//! ESCAPE. In the reference that is enough to swallow **every** key while it is up, because the
//! key-down walk's gate is EXISTENCE, not handling (wow-re `ui/scratch/frame-key-script-delivery.md`
//! §3, VERIFIED): a shown keyboard frame with the slot set consumes the key whatever its script
//! does with it, and a 1.12 handler has no way to signal "not handled" (§3.1).
//!
//! The reference's own Lua is the proof, and it is why these tests exist: that same `OnKeyDown` has
//! to call `RunBinding("SCREENSHOT")` **by hand** to get one key back. It would not need to if
//! unhandled keys fell through to their bindings.
//!
//! benilla had the walk (decision 1319) but fed it only ten key names, so ESCAPE was consumed and
//! `W` was not — and the player could walk around underneath their own intro cinematic.
use benilla_ui::script::UiScript;
fn load_xml(s: &UiScript, file: &str) {
let text = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("assets/ui")
.join(file),
)
.unwrap();
let doc = benilla_ui::framexml::parse(&text).unwrap();
let report = benilla_ui::loader::load(s, &doc, &|_| None);
assert!(report.errors.is_empty(), "{file}: {:?}", report.errors);
}
/// The frame tree a cinematic actually runs against: `UIParent` (whose `ShowUIPanel` the frame's
/// `CINEMATIC_START` arm calls) and the cinematic frame itself.
fn ui_with_the_cinematic_frame() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
load_xml(&s, "Fonts.xml");
load_xml(&s, "UIParent.xml");
load_xml(&s, "MoneyFrame.xml"); // StaticPopup's money row, or UiPanels errors at load
load_xml(&s, "UiPanels.xml");
load_xml(&s, "CinematicFrame.xml");
s.resolve();
s
}
/// Show the cinematic frame the way the engine does — the `CINEMATIC_START` edge `feed_ui` fires.
fn start_cinematic(s: &mut UiScript) {
s.set_in_cinematic(true);
s.fire_event("CINEMATIC_START", vec![]);
s.resolve();
}
/// With a fly-by on screen, the movement keys are the cinematic frame's, not the world's.
///
/// `frame_key_input` returning `true` is exactly the consumption that suppresses the key's binding
/// and the gameplay readers ([`super::UiKeyboardCapture`]) — so this *is* the assertion that you
/// cannot walk during a cinematic.
#[test]
fn a_playing_cinematic_swallows_the_movement_keys() {
let mut s = ui_with_the_cinematic_frame();
// Before it starts, the frame is hidden and declines: the world keeps its keys.
for key in ["W", "A", "S", "D", "SPACE", "1"] {
assert!(
!s.frame_key_input(key),
"{key} must reach the world while no cinematic is playing"
);
}
start_cinematic(&mut s);
assert_eq!(
s.eval::<i64>("return CinematicFrame:IsVisible() and 1 or 0")
.unwrap(),
1,
"the frame itself is up — `InCinematic()` would only echo the flag this test just set"
);
for key in ["W", "A", "S", "D", "SPACE", "1", "F1", "P"] {
assert!(
s.frame_key_input(key),
"{key} must be swallowed by the cinematic frame"
);
}
}
/// The two keys the reference deliberately keeps working, and the third it does not.
///
/// ESCAPE ends the shot; the screenshot binding is re-run by hand from inside the handler
/// (`RunBinding("SCREENSHOT")`) precisely *because* consumption is unconditional. Both are
/// consumed at the walk either way — "keeps working" means the frame's own script acts on them,
/// never that they fall through.
#[test]
fn escape_is_consumed_and_acted_on_rather_than_falling_through() {
let mut s = ui_with_the_cinematic_frame();
start_cinematic(&mut s);
assert!(
s.frame_key_input("ESCAPE"),
"ESCAPE is consumed like any key"
);
assert!(
s.take_session_requests()
.iter()
.any(|r| matches!(r, benilla_ui::script::SessionRequest::StopCinematic)),
"and its handler asked the engine to stop the cinematic"
);
}
/// The host feed itself: every key the walk is supposed to carry must actually *have* a name, or
/// the delivery in [`super::input`] is a silent no-op for it. This is the half that was missing —
/// the walk was faithful, the feed was ten keys wide.
#[test]
fn the_host_has_a_reference_name_for_the_keys_it_now_delivers() {
use crate::bindings::chord::key_token;
use bevy::prelude::KeyCode;
for (code, name) in [
(KeyCode::KeyW, "W"),
(KeyCode::KeyA, "A"),
(KeyCode::KeyS, "S"),
(KeyCode::KeyD, "D"),
(KeyCode::Space, "SPACE"),
(KeyCode::Digit1, "1"),
(KeyCode::F1, "F1"),
] {
assert_eq!(
key_token(code),
Some(name),
"{code:?} must reach a keyboard frame under the reference's own name"
);
}
}
+24
View File
@@ -277,6 +277,30 @@ pub(super) fn feed_ui_input(
continue;
}
}
// EVERY OTHER KEY reaches a keyboard frame by name too. The reference's key-down walk
// carries the whole key table — its `arg1` comes from the *same* table the keybinding
// chord uses (wow-re `frame-key-script-delivery.md` §4.2), which is [`chord::key_token`]
// here — and the frame's gate is EXISTENCE, not handling: a shown keyboard frame with an
// `OnKeyDown` swallows the key whatever its script does with it (§3, §3.1). This host used
// to feed only the ten names above, so `CinematicFrame` — fullscreen, keyboard-enabled, an
// `OnKeyDown` that answers ESCAPE — consumed ESC and let W straight through, and the player
// walked around underneath their own intro cinematic.
//
// The reference's own Lua is the proof this is the law and not an over-reading: that
// handler has to call `RunBinding("SCREENSHOT")` **by hand** to get one key back. It would
// not need to if unhandled keys fell through to their bindings.
//
// Consumption sets the capture gate and nothing else: it suppresses the binding and the
// world/gameplay readers, and deliberately does NOT suppress `char_input` below — `OnChar`
// is a separate channel off a separate dispatcher (`0x765df0`), which is exactly how the
// stack-split spinner receives a digit whose key-down its own `OnKeyDown` already ate.
else if named.is_none() {
if let Some(token) = crate::bindings::chord::key_token(ev.key_code) {
if script.frame_key_input(token) {
capture.0 = true;
}
}
}
if let Some(name) = named {
// The three box-event keys. An unconsumed press is not acted on here: every GAME
// action of a key — ESCAPE's close/cancel ladder (TOGGLEGAMEMENU), TAB's targeting,
+3
View File
@@ -745,6 +745,9 @@ impl benilla_ui::script::TextMeasure for FixedWidthFont {
}
}
#[cfg(test)]
mod cinematic_tests;
#[cfg(test)]
mod cast_tests;