From 6b980b9efbc8d65a3b6a05b73e284aa7c1db42cd Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 11 Aug 2026 18:18:02 +0700 Subject: [PATCH] the player build: one binary that carries its content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - a player build ships beside the dev one — dev chords and probe surfaces go dark, and a gate enforces what a player can still reach, by medium not by module - the binary carries its own shaders and its shipped UI, finds the WoW folder by itself, and keeps its local state in one benilla-config folder - the glue screens get a capture shutter, and the engine stops depending on the protocol crate Co-Authored-By: Claude Fable 5 --- Cargo.lock | 21 +- crates/benilla-app/Cargo.toml | 34 +- crates/benilla-app/assets/ui/benilla.toc | 297 ++++++++++++ crates/benilla-app/src/area.rs | 6 +- crates/benilla-app/src/area_trigger.rs | 6 +- crates/benilla-app/src/bindings.rs | 14 +- crates/benilla-app/src/bindings/store.rs | 4 +- crates/benilla-app/src/capture/fixtures.rs | 12 +- crates/benilla-app/src/capture/fxview.rs | 231 +++++++++ crates/benilla-app/src/capture/mod.rs | 450 ++++++++++-------- .../src/{player => capture}/probe_cam.rs | 51 +- .../src/{player => capture}/probe_look.rs | 36 +- crates/benilla-app/src/capture/probe_rig.rs | 17 +- crates/benilla-app/src/capture/probes/mod.rs | 17 +- crates/benilla-app/src/capture/scenarios.rs | 76 ++- crates/benilla-app/src/capture/waterfx.rs | 2 +- crates/benilla-app/src/char_select/mod.rs | 107 +++-- crates/benilla-app/src/cvars.rs | 4 +- crates/benilla-app/src/debug_panel/inspect.rs | 9 +- crates/benilla-app/src/debug_panel/journal.rs | 2 +- crates/benilla-app/src/debug_panel/mod.rs | 36 +- crates/benilla-app/src/dev.rs | 248 ++++++++++ crates/benilla-app/src/entities.rs | 40 +- .../src/entities/attach/redress.rs | 6 +- crates/benilla-app/src/entities/display.rs | 10 +- .../benilla-app/src/entities/spell_fx/mod.rs | 226 +-------- crates/benilla-app/src/glue/add_material.rs | 2 +- crates/benilla-app/src/hover_log.rs | 25 +- crates/benilla-app/src/lib.rs | 273 +++-------- crates/benilla-app/src/local_state.rs | 264 ++++++++-- crates/benilla-app/src/login/mod.rs | 65 +-- crates/benilla-app/src/minimap/blips/dots.rs | 6 +- crates/benilla-app/src/net/apply/loot.rs | 6 +- crates/benilla-app/src/player.rs | 54 +-- crates/benilla-app/src/player/camera.rs | 28 +- crates/benilla-app/src/player/camera_saved.rs | 2 +- crates/benilla-app/src/player/land.rs | 2 +- crates/benilla-app/src/player/setup.rs | 2 +- crates/benilla-app/src/player/state.rs | 10 + crates/benilla-app/src/player/step_probe.rs | 11 + crates/benilla-app/src/player/wire_in.rs | 4 +- crates/benilla-app/src/preflight.rs | 144 +----- crates/benilla-app/src/run_mode.rs | 389 +++++++++++++++ crates/benilla-app/src/shaders.rs | 64 +++ .../{assets => src}/shaders/ui_add.wgsl | 0 .../{assets => src}/shaders/ui_gamma.wgsl | 0 .../shaders/ui_node_gamma.wgsl | 0 .../{assets => src}/shaders/ui_quad.wgsl | 0 .../shaders/ui_slice_gamma.wgsl | 0 crates/benilla-app/src/sound/mixer.rs | 10 +- crates/benilla-app/src/sound/mod.rs | 2 +- crates/benilla-app/src/target/mod.rs | 2 +- crates/benilla-app/src/ui_action/cast_fail.rs | 6 +- crates/benilla-app/src/ui_action/errors.rs | 18 +- crates/benilla-app/src/ui_action/ranks.rs | 6 +- crates/benilla-app/src/ui_action/state.rs | 6 +- crates/benilla-app/src/ui_aura.rs | 12 +- crates/benilla-app/src/ui_chat/commands.rs | 36 +- crates/benilla-app/src/ui_chat/input/mod.rs | 9 +- crates/benilla-app/src/ui_chat/input/parse.rs | 2 +- crates/benilla-app/src/ui_chat/tests.rs | 36 +- crates/benilla-app/src/ui_duel.rs | 8 +- crates/benilla-app/src/ui_gamma.rs | 8 +- .../benilla-app/src/ui_items/equip_error.rs | 6 +- crates/benilla-app/src/ui_items/feed.rs | 6 +- crates/benilla-app/src/ui_macro/mod.rs | 2 +- crates/benilla-app/src/ui_macro/store.rs | 2 +- crates/benilla-app/src/ui_macro/tests.rs | 10 +- crates/benilla-app/src/ui_pass.rs | 2 +- crates/benilla-app/src/ui_pet_stats.rs | 12 +- crates/benilla-app/src/ui_quest.rs | 6 +- crates/benilla-app/src/ui_script/content.rs | 114 +++++ .../benilla-app/src/ui_script/extract/mod.rs | 43 +- crates/benilla-app/src/ui_script/manifest.rs | 424 +++++------------ crates/benilla-app/src/ui_script/mod.rs | 66 ++- .../src/ui_script/options_tests.rs | 8 +- .../src/ui_script/shipped_xml_tests.rs | 15 +- .../src/ui_script/skills_frame_tests.rs | 6 +- crates/benilla-app/src/ui_social/query.rs | 6 +- crates/benilla-app/src/ui_taxi/routing.rs | 12 +- crates/benilla-app/src/ui_tooltip/tests.rs | 18 +- crates/benilla-app/src/ui_trainer/tests.rs | 24 +- crates/benilla-app/src/ui_world_map.rs | 6 + crates/benilla-assets/Cargo.toml | 10 +- .../benilla-assets/examples/face_facing_at.rs | 2 +- crates/benilla-assets/src/lib.rs | 47 +- crates/benilla-assets/src/model.rs | 12 +- crates/benilla-assets/src/wmo.rs | 6 +- crates/benilla-formats/Cargo.toml | 10 + .../examples/area_trigger_at.rs | 2 +- .../benilla-formats/examples/batch_alpha.rs | 2 +- crates/benilla-formats/examples/batch_lit.rs | 2 +- crates/benilla-formats/examples/dbc_head.rs | 2 +- .../benilla-formats/examples/dump_emitters.rs | 2 +- crates/benilla-formats/examples/dump_file.rs | 3 +- .../examples/dump_model_anim.rs | 2 +- crates/benilla-formats/examples/emdump.rs | 2 +- .../examples/eye_burial_anim.rs | 2 +- .../benilla-formats/examples/eye_quad_pass.rs | 2 +- crates/benilla-formats/examples/gseqdump.rs | 2 +- crates/benilla-formats/examples/list_chain.rs | 2 +- .../benilla-formats/examples/order_probe.rs | 2 +- .../benilla-formats/examples/scan_events.rs | 2 +- crates/benilla-formats/examples/seamswing.rs | 11 +- .../examples/spell_interrupt.rs | 2 +- crates/benilla-formats/examples/spell_row.rs | 2 +- crates/benilla-formats/examples/srci_dump.rs | 2 +- .../benilla-formats/examples/surface_here.rs | 2 +- .../benilla-formats/examples/what_is_here.rs | 2 +- crates/benilla-formats/examples/where.rs | 30 ++ crates/benilla-formats/examples/wmo_bsp.rs | 2 +- .../examples/wmo_chunk_census.rs | 2 +- .../examples/wmo_collision_cost.rs | 2 +- .../benilla-formats/examples/wmo_coplanar.rs | 2 +- .../examples/wmo_dark_floors.rs | 2 +- .../benilla-formats/examples/wmo_doubled.rs | 2 +- .../examples/wmo_liquid_scope.rs | 2 +- crates/benilla-formats/examples/wmo_mocv.rs | 2 +- crates/benilla-formats/src/anim_data.rs | 6 +- crates/benilla-formats/src/area_poi.rs | 6 +- crates/benilla-formats/src/area_sound.rs | 10 +- crates/benilla-formats/src/area_table.rs | 12 +- crates/benilla-formats/src/area_trigger.rs | 6 +- .../src/characters/customization.rs | 11 +- .../benilla-formats/src/characters/geosets.rs | 11 +- .../src/characters/sections.rs | 17 +- .../benilla-formats/src/creature_families.rs | 6 +- crates/benilla-formats/src/creature_sound.rs | 10 +- crates/benilla-formats/src/creature_types.rs | 11 +- crates/benilla-formats/src/creatures.rs | 35 +- crates/benilla-formats/src/elevators.rs | 6 +- crates/benilla-formats/src/emotes.rs | 10 +- crates/benilla-formats/src/exhaustion.rs | 6 +- crates/benilla-formats/src/factions.rs | 23 +- crates/benilla-formats/src/footsteps.rs | 16 +- crates/benilla-formats/src/install.rs | 229 +++++++++ crates/benilla-formats/src/item_sounds.rs | 16 +- crates/benilla-formats/src/item_visuals.rs | 34 +- crates/benilla-formats/src/itembagfamily.rs | 6 +- crates/benilla-formats/src/items.rs | 6 +- crates/benilla-formats/src/itemsets.rs | 6 +- crates/benilla-formats/src/itemsubclass.rs | 18 +- crates/benilla-formats/src/lib.rs | 10 +- crates/benilla-formats/src/liquid.rs | 21 +- crates/benilla-formats/src/loading_screen.rs | 11 +- crates/benilla-formats/src/lock.rs | 18 +- crates/benilla-formats/src/lock_type.rs | 6 +- .../benilla-formats/src/minimap_translate.rs | 6 +- crates/benilla-formats/src/models/anim.rs | 47 +- .../benilla-formats/src/models/m2_batches.rs | 35 +- crates/benilla-formats/src/models/mat_anim.rs | 35 +- crates/benilla-formats/src/models/tex_anim.rs | 6 +- crates/benilla-formats/src/npc_greeting.rs | 10 +- crates/benilla-formats/src/particles.rs | 35 +- crates/benilla-formats/src/pet_name_token.rs | 6 +- crates/benilla-formats/src/pet_stats.rs | 6 +- crates/benilla-formats/src/race_sound.rs | 6 +- crates/benilla-formats/src/ribbons.rs | 23 +- crates/benilla-formats/src/sheathe.rs | 12 +- crates/benilla-formats/src/skill_lines.rs | 48 +- crates/benilla-formats/src/sound_entries.rs | 17 +- crates/benilla-formats/src/sound_provider.rs | 6 +- crates/benilla-formats/src/sound_water.rs | 6 +- crates/benilla-formats/src/spell_focus.rs | 6 +- .../benilla-formats/src/spell_visual/tests.rs | 54 +-- .../benilla-formats/src/spells/cast_times.rs | 6 +- .../src/spells/catalog_tests.rs | 102 +--- .../src/spells/dispel_types.rs | 6 +- crates/benilla-formats/src/spells/duration.rs | 6 +- crates/benilla-formats/src/spells/radius.rs | 6 +- crates/benilla-formats/src/spells/ranges.rs | 6 +- crates/benilla-formats/src/talents.rs | 6 +- crates/benilla-formats/src/taxi.rs | 6 +- crates/benilla-formats/src/taxi_nodes.rs | 6 +- crates/benilla-formats/src/taxi_path.rs | 6 +- crates/benilla-formats/src/terrain.rs | 6 +- .../benilla-formats/src/transports/tests.rs | 18 +- crates/benilla-formats/src/unit_blood.rs | 11 +- crates/benilla-formats/src/weapon_impact.rs | 6 +- crates/benilla-formats/src/wmo_area.rs | 6 +- crates/benilla-formats/src/world_map_area.rs | 6 +- .../src/world_map_continent.rs | 6 +- .../benilla-formats/src/world_map_overlay.rs | 6 +- crates/benilla-formats/src/zone_map.rs | 6 +- crates/benilla-formats/tests/billboards.rs | 12 +- .../benilla-formats/tests/blob_shadow_box.rs | 6 +- crates/benilla-formats/tests/blp_alpha.rs | 12 +- crates/benilla-formats/tests/chain.rs | 169 ++----- crates/benilla-formats/tests/ground_quad.rs | 24 +- .../tests/helm_vis_wants_a_helm.rs | 18 +- crates/benilla-formats/tests/item_icons.rs | 12 +- .../tests/light_area_blend_order.rs | 12 +- .../tests/light_no_row_fallback.rs | 18 +- .../benilla-formats/tests/m2_anim_events.rs | 12 +- crates/benilla-formats/tests/m2_billboard.rs | 12 +- .../tests/m2_billboard_card_face.rs | 18 +- .../benilla-formats/tests/m2_cold_breath.rs | 12 +- crates/benilla-formats/tests/m2_color.rs | 12 +- crates/benilla-formats/tests/m2_emissive.rs | 12 +- crates/benilla-formats/tests/m2_env_map.rs | 18 +- .../tests/m2_global_seq_constant.rs | 12 +- .../benilla-formats/tests/m2_go_arm_book.rs | 12 +- .../benilla-formats/tests/m2_go_crate_lid.rs | 12 +- crates/benilla-formats/tests/m2_idle_anim.rs | 12 +- crates/benilla-formats/tests/m2_lights.rs | 24 +- .../tests/m2_loader_idle_is_stand.rs | 12 +- .../tests/m2_playable_animation_lookup.rs | 12 +- .../tests/m2_portrait_camera.rs | 18 +- .../tests/m2_sequence_bounds.rs | 6 +- crates/benilla-formats/tests/particles.rs | 42 +- crates/benilla-formats/tests/ribbons.rs | 12 +- .../tests/selection_ring_radius.rs | 6 +- crates/benilla-formats/tests/spell_catalog.rs | 30 +- .../tests/transport_speed_profile.rs | 6 +- .../tests/wmo_chunk_overrun.rs | 18 +- .../benilla-formats/tests/wmo_doorway_fade.rs | 18 +- crates/benilla-formats/tests/wmo_fogs.rs | 12 +- crates/benilla-formats/tests/wmo_lights.rs | 12 +- crates/benilla-formats/tests/wmo_liquid.rs | 12 +- .../tests/wmo_mocv_short_tail.rs | 18 +- crates/benilla-formats/tests/wow_data_env.rs | 36 ++ crates/benilla-ui/src/script/keybind.rs | 6 +- crates/benilla-ui/src/script/macros.rs | 4 +- crates/benilla-ui/src/script/model.rs | 2 +- crates/benilla-world/Cargo.toml | 17 +- crates/benilla-world/src/assets/mod.rs | 18 +- crates/benilla-world/src/boot.rs | 28 +- crates/benilla-world/src/clouds/layer.rs | 2 +- crates/benilla-world/src/ffx_glow.rs | 3 +- crates/benilla-world/src/lib.rs | 1 + .../src/lighting/global_light.rs | 2 +- crates/benilla-world/src/liquid/real_data.rs | 5 +- crates/benilla-world/src/liquid/surface.rs | 6 +- crates/benilla-world/src/model_fade.rs | 2 +- crates/benilla-world/src/particles/render.rs | 2 +- crates/benilla-world/src/shaders.rs | 82 ++++ .../{assets => src}/shaders/celestial.wgsl | 0 .../{assets => src}/shaders/cloud.wgsl | 0 .../{assets => src}/shaders/ffx_glow.wgsl | 0 .../{assets => src}/shaders/sky.wgsl | 0 .../{assets => src}/shaders/star.wgsl | 0 .../{assets => src}/shaders/wmo_skybox.wgsl | 0 .../{assets => src}/shaders/wow_effect.wgsl | 0 crates/benilla-world/src/sky.rs | 2 +- crates/benilla-world/src/sky_order.rs | 16 +- crates/benilla-world/src/sun/materials.rs | 7 +- crates/benilla-world/src/water_fx/mod.rs | 3 +- .../src/wmo_portal/audit/light_probe.rs | 14 +- .../benilla-world/src/wmo_portal/audit/mod.rs | 8 +- .../benilla-world/src/wmo_portal/audit/pin.rs | 6 +- crates/benilla-world/src/wmo_sky.rs | 2 +- crates/benilla-world/src/world_plugins.rs | 4 + crates/benilla-world/src/world_unit.rs | 21 +- crates/benilla-world/src/worldview.rs | 16 +- crates/benilla/Cargo.toml | 11 +- 255 files changed, 3428 insertions(+), 3202 deletions(-) create mode 100644 crates/benilla-app/assets/ui/benilla.toc create mode 100644 crates/benilla-app/src/capture/fxview.rs rename crates/benilla-app/src/{player => capture}/probe_cam.rs (73%) rename crates/benilla-app/src/{player => capture}/probe_look.rs (76%) create mode 100644 crates/benilla-app/src/dev.rs create mode 100644 crates/benilla-app/src/run_mode.rs create mode 100644 crates/benilla-app/src/shaders.rs rename crates/benilla-app/{assets => src}/shaders/ui_add.wgsl (100%) rename crates/benilla-app/{assets => src}/shaders/ui_gamma.wgsl (100%) rename crates/benilla-app/{assets => src}/shaders/ui_node_gamma.wgsl (100%) rename crates/benilla-app/{assets => src}/shaders/ui_quad.wgsl (100%) rename crates/benilla-app/{assets => src}/shaders/ui_slice_gamma.wgsl (100%) create mode 100644 crates/benilla-app/src/ui_script/content.rs create mode 100644 crates/benilla-formats/examples/where.rs create mode 100644 crates/benilla-formats/src/install.rs create mode 100644 crates/benilla-formats/tests/wow_data_env.rs create mode 100644 crates/benilla-world/src/shaders.rs rename crates/benilla-world/{assets => src}/shaders/celestial.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/cloud.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/ffx_glow.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/sky.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/star.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/wmo_skybox.wgsl (100%) rename crates/benilla-world/{assets => src}/shaders/wow_effect.wgsl (100%) diff --git a/Cargo.lock b/Cargo.lock index 42e6de4c..093da7b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,6 +604,7 @@ dependencies = [ "fontdb", "half", "image", + "include_dir", "kira", "libc", "mint", @@ -742,7 +743,6 @@ dependencies = [ "avian3d", "benilla-assets", "benilla-formats", - "benilla-protocol", "benilla-wmo", "bevy", "bytemuck", @@ -4058,6 +4058,25 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40fac9d56ed6437b198fddba683305e8e2d651aa42647f00f5ae542e7f5c94a2" +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "2.14.0" diff --git a/crates/benilla-app/Cargo.toml b/crates/benilla-app/Cargo.toml index bedb17f8..53bc0706 100644 --- a/crates/benilla-app/Cargo.toml +++ b/crates/benilla-app/Cargo.toml @@ -6,6 +6,23 @@ license.workspace = true description = "The Bevy app as a library: everything but the build-id stamp, which lives in the `benilla` launcher shim so a commit dirties ~30 lines instead of this crate (decision 0993)" [features] +# **The dev/player seam** (decisions 0026 + 1173). Default-ON, so every ordinary `cargo run`, +# capture, probe and gate builds exactly what it built before. `--no-default-features` compiles +# the instruments — the debug panel, the perf HUD, the inspector, the capture harness and the +# probe fleet — physically OUT: not dormant behind a runtime flag, absent. That is the whole +# requirement a player client has of them, and a runtime toggle cannot meet it. +# +# The rule the seam enforces is one-directional: **dev may see anything; nothing may depend on +# dev.** It is enforced by `cargo build -p benilla --no-default-features` in `scripts/gates.sh`, +# not by anyone remembering it — 0026 adopted the rule in June 2026 with no failing build behind +# it, and twenty-four couplings had accumulated by August (1173). +default = ["dev"] +# +# The three benilla crates below are taken with `default-features = false` and their `dev` +# re-exported here (1175). Their `dev` is default-on too, so without the round trip a +# `--no-default-features` build gets the feature back through unification — and the binary +# carries the build machine's source-tree path after all. Same trap as the shim's, one level down. +dev = ["benilla-assets/dev", "benilla-formats/dev", "benilla-world/dev", "dep:bevy_egui", "dep:libc"] # Opt-in Tracy profiling: `cargo run --features tracy` streams per-system `info_span!` markers (on the # hot systems) + render-pass CPU timings to a Tracy capture (decision 0026 — the dev/player seam; # a `docs/howto/running.md` this once cited never landed). @@ -25,7 +42,9 @@ anyhow.workspace = true arboard = "3" benilla-ui = { path = "../benilla-ui" } bevy.workspace = true -bevy_egui = "0.39" +# Optional: the dev overlays are its only consumers (the panel, the perf pill, the inspector +# card), so a player build does not link it at all (`dev` feature, decision 1173). +bevy_egui = { version = "0.39", optional = true } # Zero-copy POD→bytes for the shared light storage buffer (one per-frame write_buffer). Already in # the tree transitively; pinned to the same version as the dev-dep below. bytemuck = { version = "1", features = ["derive"] } @@ -33,12 +52,17 @@ bytemuck = { version = "1", features = ["derive"] } # `Rgba16Float` (decision 0804) and the PNG wants display bytes. Already in the tree via # wgpu/bevy_image; declared so the call site is an honest dependency. half = "2" +# The shipped FrameXML tree, compiled into the binary (decision 1175 §2): 61 files, ~2.8 MB, so the +# client cannot boot without its interface on a machine that has never seen our source. A proc +# macro plus a no-dependency runtime shim — it adds nothing to the graph at runtime, and it is the +# crate everyone uses for this. `assets/ui` is OUR content (MIT/Apache), not Blizzard's. +include_dir = "0.7" # `getrusage` — whole-process CPU time (all threads), the perf probes' `cpu_ms`/`cpu_pct` metric # (`perf::process_cpu_secs`). Wall-clock fps is unusable as a regression instrument on a machine # running parallel sessions' builds; CPU-per-frame is, and it is also the number the Mac report # is about ("250 % CPU at 59 fps"). Already in the tree transitively; declared so the call site is # an honest dependency. -libc = "0.2" +libc = { version = "0.2", optional = true } # Resample varying-size layer BLPs to a uniform size for the terrain texture array; `png` so we can # encode the cursor image into data an NSImage can load (macOS hardware cursor). image = { version = "0.25", default-features = false, features = ["png"] } @@ -47,10 +71,10 @@ image = { version = "0.25", default-features = false, features = ["png"] } # (std's mpsc Sender is `!Sync`, so it can't be a Bevy resource without a mutex — the very thing this # bridge deletes). The canonical Bevy background-thread→channel→system idiom. See decision 0006. crossbeam-channel = "0.5" -benilla-assets = { path = "../benilla-assets" } -benilla-formats = { path = "../benilla-formats" } +benilla-assets = { path = "../benilla-assets", default-features = false } +benilla-formats = { path = "../benilla-formats", default-features = false } benilla-protocol = { path = "../benilla-protocol" } -benilla-world = { path = "../benilla-world" } +benilla-world = { path = "../benilla-world", default-features = false } # The CVar config diff (`benilla/config.toml`, decision 0954): serde derive on the persisted # shape, toml as its format. (serde_json left with the era manifest — 0978.) serde = { version = "1", features = ["derive"] } diff --git a/crates/benilla-app/assets/ui/benilla.toc b/crates/benilla-app/assets/ui/benilla.toc new file mode 100644 index 00000000..b792c91d --- /dev/null +++ b/crates/benilla-app/assets/ui/benilla.toc @@ -0,0 +1,297 @@ +# benilla.toc — the built-in interface, as an ordinary addon manifest. +# +# The reference's own interface ships as `FrameXML.toc`; ours was a hand-ordered Rust array until +# decision 1178. It is parsed by the same `.toc` parser any third-party addon will be +# (`benilla-ui/src/toc.rs`), and that is the point: a privileged built-in UI is how addon support +# rots. If our own interface loads through the addon path, the addon path works — it is exercised +# by every single run. If it keeps a private door, that path is untested by construction. +# +# The order is LOAD order and it is load-bearing: a template must be defined before the frame that +# inherits it, and a later file paints on top of an earlier one. Every entry's comment is why it +# sits where it sits; those comments are knowledge, not decoration. +# +# The FIRST entry is the font-object registry, and the loader splits there: it alone loads at +# startup, ahead of the glue screens that share our one glyph atlas, and everything after it +# materializes on entering the world (decision 1051). + +## Interface: 11200 +## Title: benilla + +# The named virtual Font objects (decision 0084) — must load first so any FontString's +# `inherits="GameFontNormal"` resolves against a registered object. +Fonts.xml +# The UIPanel slot manager (decision 0084 §2): script-only, no frames of its own — must load +# before any panel frame (Gossip/Merchant today) so ShowUIPanel/HideUIPanel already exist +# when those frames' OnLoad/OnEvent reference them. +UiPanels.xml +# The managed bottom-stack positions (decision 0272): script-only — +# UIPARENT_MANAGED_FRAME_POSITIONS + UIParent_ManageFramePositions, the ref +# UIParent.lua mechanism that re-anchors the cast bar / chat over the bottom bars. +# Loaded early so the stance bar's OnShow/OnHide can call it; applied once by the +# loader's post-load bootstrap (ui_script/manifest.rs). +UIParent.xml +# The tooltip: a hover window the bag/merchant/loot/unit frames drive. Loaded before them so +# `GameTooltip` + its verbs exist when their OnEnter handlers fire (runtime, so order +# is not strictly required — but it keeps the dependency legible). +GameTooltip.xml +# The dropdown kit (0203 phase 2's closing sub-slice, widened for 0434 phase 5): +# UIDropDownMenuTemplate + DropDownList1/2 + the UIDropDownMenu_* API — before its +# first customers (the unit popups below, the world map's pickers, the trainer filter). +UIDropDownMenu.xml +# The unit right-click popups (0434 phase 5): UnitPopupMenus/Buttons + UnitPopup_ShowMenu +# — after the dropdown kit it drives, before the unit/party frames whose DropDown +# children's OnLoad initialize into it. +UnitPopup.xml +# The chat-link click router (SetItemRef): item links → ItemRefTooltip:SetHyperlink; a +# player name → left-click whisper / right-click FRIEND dropdown. After GameTooltip.xml +# (GameTooltip_OnLoad) AND the dropdown kit + UnitPopup — its FriendsDropDown inherits +# UIDropDownMenuTemplate and opens a UnitPopup FRIEND menu. +ItemRef.xml +# The shared status-bar numerals machinery (decision 1082): TextStatusBar_* + the opt-in +# template — before its customers (the XP bar in ActionBar.xml today; the unit-frame +# health/mana numerals are the ref's other users, when that slice lands). The ref's own TOC +# loads TextStatusBar right before its unit frames, same seat as here. +TextStatusBar.xml +UnitFrames.xml +# The combo-point dots (decision 0869) — anchored to BenillaTargetFrame, so after +# UnitFrames.xml; its fade chain needs UiPanels.xml's UIFrameFade above. +ComboFrame.xml +# The center-screen scrolling combat text (decision 0578) — the Blizzard_CombatText +# transcription; consumes COMBAT_TEXT_UPDATE + the regen/health/power events, needs +# only Fonts.xml (CombatTextFont) + UIParent before it. +CombatText.xml +# The party member frames + the PARTY_INVITE popup + its event driver (decision 0434 +# phase 2): a StaticPopup registry entry, so after UiPanels.xml. +PartyFrame.xml +# The death arc's dialogs + event driver (decision 0308): registry entries on the shared +# StaticPopup engine, so after UiPanels.xml. +DeathFrame.xml +# The two duel dialogs + their event driver (decision 0633): registry entries on the same +# StaticPopup engine, and DUEL_OUTOFBOUNDS leans on its per-tick countdown branch. +DuelFrame.xml +# The two enchant-apply confirms + their event driver (decision 0928): the same StaticPopup +# registry again. Not CraftFrame's — the gate that raises them is the generic item-target +# bind, reached by a poison or a sharpening stone with no profession window open. +EnchantConfirm.xml +# The shared CooldownFrame_SetTimer (the ref's Cooldown.lua file split) — before its +# consumers (ActionBar's buttons, the multibars, the stance bar, BagFrame's slots). +Cooldown.xml +ActionBar.xml +# The two always-on bottom multibars (actions 61-72 / 49-60): pure XML/Lua over +# ActionBar.xml's shared button template + handler set — loads right after it +# (inherits BenillaActionButtonTemplate, anchors to BenillaActionButton1, and its +# buttons join the BENILLA_ACTION_BUTTONS roster ActionBar.xml declares). +MultiBars.xml +# The stance/shapeshift bar: hidden until `crate::ui_shapeshift`'s feed pushes a +# non-empty form list (a formless class never shows it). After Cooldown.xml +# (CooldownFrame_SetTimer) + ActionBar.xml (BENILLA_FALLBACK_ICON, the BenillaActionBar +# anchor target). +StanceBar.xml +# The pet action bar (decision 0982): hidden until `crate::ui_pet`'s feed reports a bar, +# i.e. until the server sends `SMSG_PET_SPELLS` for a live pet or charm — so a class that +# never has one never sees it. Right after the stance bar, whose row it shares (either +# one showing raises UIParent.xml's managed "pet" delta), and after Cooldown.xml +# (CooldownFrame_SetTimer), ActionBar.xml (the `BenillaActionBar` anchor target) and +# GameTooltip.xml (its hover). It is also the pet ARC's file, so decision 1066's three +# right-click-menu dialogs (ABANDON_PET / RENAME_PET / PETRENAMECONFIRM) register here — +# which adds UiPanels.xml to its dependency set, already far above. +PetActionBar.xml +# The micro-button row in the bar's right-hand recess (the ref's own file split, its TOC +# loads MainMenuBarMicroButtons right after MainMenuBar). After ActionBar.xml: it anchors +# into BenillaActionBarArtFrame by name and seats itself with that file's +# BenillaActionBarArt_SeatAbove. It must come BEFORE the panels it toggles, because each of +# their OnShow/OnHide calls the `UpdateMicroButtons` this file defines (the panel frames +# themselves are read back through getglobal, so the reverse dependency is call-time). +MicroMenu.xml +# The HUD minimap cluster (decision 0203 phase 1): the chrome around the engine-rendered +# widget hole (`crate::minimap` fills it). After GameTooltip (its zone-text +# hover uses the shared tooltip). +MinimapCluster.xml +# The minimap's time-of-day indicator (GameTimeFrame): the reference's own file split — its +# TOC loads GameTime.xml right after Minimap.xml. After MinimapCluster (its parent) and +# GameTooltip (the hover tooltip); driven by the app's game clock (`minimap::feed_game_time`). +GameTime.xml +# The zone-entry splash (decision 0287): ZoneTextFrame/SubZoneTextFrame + the inlined +# FadingFrame kit, driven by the ZONE_CHANGED family `crate::area` fires. Needs only +# Fonts (ZoneTextFont/SubZoneTextFont) + UIParent; kept by the minimap cluster — the +# other consumer of the zone-text host globals. +ZoneText.xml +# The faux-scroll kit (decisions 0247/0250/0251): BenillaScrollBarTemplate (the draggable +# Slider scroll bar) + BenillaFauxScrollFrameTemplate + the BenillaFauxScrollFrame_* API — +# before its first customer (the trainer list) below. +ScrollTemplates.xml +# The fullscreen world map (decision 0203 phase 2), over the worldmap host API +# (`crate::ui_world_map` feeds it); the 'M' binding (ui_script/input.rs) calls its +# ToggleWorldMap(). +WorldMapFrame.xml +# The cast bar (decision 0137 phase 1) — the extracted 1.12 CastingBarFrame, driven by +# the SPELLCAST_* events `crate::ui_cast` fires. Only needs Fonts before it. +CastingBar.xml +# The mirror timers (decision 0874): the breath/fatigue bars at top-center, driven by the +# MIRROR_TIMER_* events `crate::ui_mirror` fires. Needs only Fonts (GameFontHighlight) +# before it; kept beside the cast bar, whose border art it shares. +MirrorTimer.xml +# The player buff/debuff bar (decisions 0255/0257 — the player aura arc): the HUD row of +# aura icons under the minimap, driven by the UnitAura feed (`crate::ui_aura`). Needs Fonts +# (its FontStrings) and ActionBar's `BENILLA_FALLBACK_ICON` global (both already loaded). +BuffFrame.xml +# The durability alert (the "armor guy"): under the minimap beside BuffFrame, driven by +# UPDATE_INVENTORY_ALERTS off the same inventory push the character window reads. Needs +# MinimapCluster (its anchor) — loaded above. +DurabilityFrame.xml +ErrorsFrame.xml +BagFrame.xml +# The stack-split spinner (decision 0216 §6/slice 2): loaded beside BagFrame, whose slot +# click handler (`BenillaBagSlot_OnClick`) calls `OpenStackSplitFrame`. Order between the +# two doesn't matter for correctness (Lua globals resolve at call time, and neither file's +# XML `inherits=` reaches into the other's templates) — kept adjacent for legibility. +StackSplit.xml +# The character window (decision 0208 phase 1a): the C-key paper doll. Loaded after BagFrame +# (needs nothing from it, but keeps player-state windows grouped) and before the NPC session +# windows (Gossip/Merchant/Quest*) — it depends only on Fonts/UiPanels/GameTooltip, all +# already loaded above. +CharacterFrame.xml +# The Pet tab (decision 1057): the same separate-top-level-file shape as SkillFrame below, +# and ahead of it because it is the earlier tab. It leans on CharacterFrame.xml for real — +# the `(unit, prefix)` stat setters, the `BenillaStatRow`/`BenillaMagicResistanceFrame` +# templates and the tab row it raises tab 2 of — all of which only exist once that file has +# loaded, so this order is a requirement here, not just tidiness. +PetPaperDollFrame.xml +# The Skills tab (decision 0437 phase 4): a CharacterFrame child page, but a separate +# top-level file (SkillFrame.xml's own header comment — this engine has no cross-file +# `parent=` attachment, so it's positioned by name via `` instead). Loaded immediately after CharacterFrame.xml so it +# paints ON TOP of it (later file = higher z) and so `BenillaCharacterFrame` already exists +# for that anchor to resolve against. +SkillFrame.xml +# The inspect window (decision 0631): another player's paper doll. Beside the character +# window it mirrors — it needs the same three (Fonts/UiPanels/GameTooltip) plus +# `CharacterFrameTabButtonTemplate` from UiPanels.xml, and nothing from CharacterFrame.xml +# itself (its slot template, handlers, and booth slot are all its own — the two windows +# share only the reference's art). +InspectFrame.xml +# The dressing room (decision 1060): the ctrl-click item preview. Loaded right after the +# windows whose slots feed it (the character window above, the bags before that) and before +# everything else that ctrl-clicks into it — order is legibility only, since `DressUpItemLink` +# is a global resolved at call time. Needs UiPanels (the panel manager, its left-slot row) and +# Fonts; its booth pane needs nothing loaded at all. +DressUpFrame.xml +# The spellbook window (decision 0216 §8, slice 5): the P-key window, the spell SOURCE +# for the cursor-payload arc. Loaded right after CharacterFrame (the same "player-state +# windows grouped" posture, same Fonts/UiPanels/GameTooltip-only dependency set); the +# 'P' binding (ui_script/input.rs) calls its bare ToggleSpellBook(BOOKTYPE_SPELL). +SpellBookFrame.xml +# The talent window (decision 0304 §4): the N-key window, the class talent grid over the +# `benilla-ui::script::talent` engine seam. Loaded right after SpellBookFrame (the same +# "player-state windows grouped" posture; needs Fonts/UiPanels/GameTooltip plus +# ScrollTemplates.xml — its real ScrollFrame's scrollbar reuses BenillaScrollBarTemplate — +# all already loaded above). The 'N' binding (ui_script/input.rs) calls its bare +# ToggleTalentFrame(). +TalentFrame.xml +# The social window (decision 0668): the O-key friends/ignore/who window over the social +# API (`benilla-ui/src/script/social.rs`) + `crate::ui_social`'s feed/drain. Needs +# UiPanels (the panel manager, `CharacterFrameTabButtonTemplate`, and the StaticPopup +# engine its two name-entry dialogs register into), ScrollTemplates (three faux lists), +# GameTooltip (the newbie tips) and UIDropDownMenu (the who list's variable column) — +# all loaded above. +FriendsFrame.xml +GossipFrame.xml +MerchantFrame.xml +# The mail window (decision 0544 P1/P2): the mailbox inbox, open-letter, and send tab over +# the Era mail API (`benilla-ui/src/script/mail.rs`) + `crate::ui_mail`'s feed/drain. Loaded +# right after MerchantFrame because it REUSES that file's global BenillaMoney_Set/_Clear +# coin helpers (postage/enclosed/COD displays) — they must be defined first. +MailFrame.xml +# The player-to-player trade window (decision 0592 P1): the two-sided offer over the trade +# API (`benilla-ui/src/script/trade.rs`) + `crate::ui_trade`'s feed/drain. Loaded after +# MerchantFrame because it REUSES that file's global BenillaMoney_Set coin helpers (the two +# gold displays) — they must be defined first. +TradeFrame.xml +# The item-text reader: letters (mail-made permanent copies) and, later, books/plaques — +# the ItemTextGet* seam (`benilla-ui/src/script/item_text.rs`) over `crate::ui_item_text`'s +# feed/drain. Loaded right after MailFrame (the same arc; needs only Fonts/UiPanels, both +# loaded above). +ItemTextFrame.xml +# The trainer window (decision 0237 phase 3): the read-only class/profession trainer list. +# Loaded right after MerchantFrame per the director's brief (kept adjacent even though this +# v1 uses plain-text costs, not MerchantFrame's coin helpers) and before the other NPC +# session windows below. +TrainerFrame.xml +# The bank window (decision 0604 phase 4): the vault, purchase ladder, and the 6 bank-bag +# popouts, over the C_Container feed already carrying container -1/5..10 (ui_items.rs) + +# the bank Lua surface (benilla-ui/src/script/bank.rs) + crate::ui_bank's feed/drain. +# Loaded AFTER MerchantFrame.xml (reuses its BenillaMoney_* coin helpers for the purchase +# row + purse) and AFTER BagFrame.xml, already above (its BenillaBagSlotTemplate/ +# BenillaBagWindowTemplate + the BenillaBagSlot_*/BenillaBagFrame_* function family are +# reused verbatim — the 24 generic slots and the 6 bank-bag popouts are, respectively, a +# container-slot grid and ordinary container windows, exactly like the bag arc's own). +# Kept in the "NPC session windows grouped" cluster beside Trainer/Taxi. +BankFrame.xml +# The taxi-map window (decision 0484 phase 2): the flight-master session, driven by the +# taxi engine seam (`benilla-ui/src/script/taxi.rs`) over `crate::ui_taxi`'s feed/drain. +# Loaded right after TrainerFrame per the same "NPC session windows grouped" posture — it +# needs only Fonts/UiPanels/GameTooltip (SetTooltipMoney), both already loaded above. +TaxiFrame.xml +# The crafting window (decision 0437 phase 2): NOT an NPC session (opens off your own cast of +# an effect-47 opener spell, TradeSkillFrame.xml's own header comment) — loaded right after +# TrainerFrame per the same "player-state/profession window" grouping and because it depends +# on nothing TrainerFrame doesn't already need (Fonts/UiPanels/GameTooltip/ScrollTemplates, +# all loaded above). +TradeSkillFrame.xml +# The Enchanting window (decision 0437 phase 3): the exact twin of TradeSkillFrame above — +# same "opens off your own cast, not an NPC session" shape (CraftFrame.xml's own header +# comment) — loaded right after it so `BuildColoredListString` (a guarded GLOBAL +# TradeSkillFrame.xml defines) already exists when CraftFrame.xml reuses it without +# redefining (CraftFrame.xml's own header comment, deviation 2). +CraftFrame.xml +LootFrame.xml +# The group-loot roll popups (decision 0591): the Need/Greed/Pass dialogs, a sibling of +# LootFrame.xml (loaded right after it) over the `benilla-ui::script::loot_roll` seam +# (`crate::ui_loot_roll`'s feed/drain). NOT a UIPanel — four `frameStrata="DIALOG"` toplevel +# popups with their own hardcoded anchors — so it needs only Fonts (GameFontNormalSmall) + +# GameTooltip (the item/PASS/NEED/GREED hovers), both already loaded above. +GroupLootFrame.xml +# The questgiver window (decision 0088): four sub-panels over the Era quest API. Loaded after +# MerchantFrame (the BenillaMoney coin helpers live there) and GossipFrame (shared parchment +# art conventions); its title/panel-button labels use the auto-centred ButtonText slot. +QuestFrame.xml +# The quest log window (decision 0088 arc, the quest-log slice): durable player state, not an +# NPC session — loaded after MerchantFrame (the BenillaMoney coin helpers it reuses) and +# alongside QuestFrame (same arc); the 'L' binding (ui_script/input.rs) calls its +# ToggleQuestLog(). +QuestLogFrame.xml +# The timed-quest countdown window under the minimap (decision 1150): the reference's own +# file split. After MinimapCluster (its anchor), UIParent (the manage pass that re-seats it, +# and SecondsToTime), GameTooltip (its row hover), and QuestLogFrame — whose window a row +# click opens, and whose MAX_QUESTS it shares. +QuestTimerFrame.xml +ChatFrame.xml +# The macro window (decision 0983): the editor + its name/icon popup, driven over the +# engine's OWN macro table (`benilla_ui::script::macros` — 1.12 macros have no server +# side, so this is the one window whose model is not an app feed). Needs UiPanels (the +# panel manager + `TabButtonTemplate`), ScrollTemplates (BOTH kits — the real one scrolls +# the body box, the faux one the icon grid), MicroMenu (`UpdateMicroButtons` on +# show/hide), and it must come AFTER SpellBookFrame, whose shift-click reaches +# `BenillaMacroFrame_AddMacroLine`. Before GameMenuFrame, whose Macros button opens it. +MacroFrame.xml +# The Keybindings page MODULE (decision 1008, superseding 0997's standalone window): +# the templates + script of the Options window's Keybindings category, over the +# engine's binding table (benilla_ui::script::keybind + crate::bindings — the 1.12 +# GetBinding/SetBinding Lua API). Needs UiPanels (popup engine) and GameTooltip (the +# character-specific checkbox's hover); MUST load before OptionsFrame.xml, whose +# Keybindings body inherits these templates and calls this script at OnLoad. +KeyBindingsPage.xml +# The options window (the 0985 provenance split: 0950's era structure, 0978's +# 1.12-native art, 0981/0984's 1.14 chrome + working era search, 0989's directed +# cuts — bare fully-live sliders, no corner X; 0992's dropdown rows + the Nameplates +# page): the era-shaped OptionsFrame the game menu's Options button opens. Needs +# Fonts' objects, UiPanels' panel manager, and UIDropDownMenu's capsule template +# (all far above); sits right before GameMenuFrame so the menu — which reaches it by +# name at click time — stays the last, outermost layer. +OptionsFrame.xml +# The game menu (decision 0674): the frame ESC opens, plus the CAMP/QUIT dialogs and their +# event driver. LAST on purpose — it is the outermost layer of the shell, it depends on +# everything it touches being loaded (UiPanels' popup engine + panel manager, MicroMenu's +# UpdateMicroButtons, BagFrame's Disable_BagButtons), and nothing depends on it: the ESC +# chain and the micro button both reach it by name at call time. +GameMenuFrame.xml diff --git a/crates/benilla-app/src/area.rs b/crates/benilla-app/src/area.rs index cae90146..b3f15232 100644 --- a/crates/benilla-app/src/area.rs +++ b/crates/benilla-app/src/area.rs @@ -387,11 +387,7 @@ mod tests { /// run through the same WmoAreaCatalog the live feed reads. Skips without client data. #[test] fn indoor_naming_matches_the_byte_law_on_real_data() { - let data = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data"); - if !data.is_dir() { - eprintln!("skipping: vanilla client not present"); - return; - } + let data = benilla_formats::wow_data_or_skip!(); let mut chain = benilla_formats::open_chain(&data).expect("chain"); let cat = benilla_formats::load_wmo_area_catalog(&mut chain).expect("WMOAreaTable"); diff --git a/crates/benilla-app/src/area_trigger.rs b/crates/benilla-app/src/area_trigger.rs index 1a3e9645..e9e09a39 100644 --- a/crates/benilla-app/src/area_trigger.rs +++ b/crates/benilla-app/src/area_trigger.rs @@ -165,11 +165,7 @@ mod tests { use super::*; fn real_catalog() -> Option { - let data = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW/Data"); - if !data.is_dir() { - eprintln!("skipping: vanilla client not present at {}", data.display()); - return None; - } + let data = benilla_formats::wow_data_or_skip!(None); let mut chain = benilla_formats::open_chain(&data).expect("open chain"); Some(benilla_formats::load_area_trigger_catalog(&mut chain).expect("AreaTrigger.dbc")) } diff --git a/crates/benilla-app/src/bindings.rs b/crates/benilla-app/src/bindings.rs index 547a7a16..e6ab4ea7 100644 --- a/crates/benilla-app/src/bindings.rs +++ b/crates/benilla-app/src/bindings.rs @@ -27,7 +27,7 @@ //! — the 1.12 law (lone modifiers ignored, left/right clicks stay UI clicks, ESC binds like //! any key). //! -//! Persistence: `benilla/bindings/account.txt` + `-.txt` ([`store`], through +//! Persistence: `benilla-config/bindings/account.txt` + `-.txt` ([`store`], through //! [`crate::local_state`]); the character file's existence is the character-set state, deleted //! on the confirmed switch back to general — the reference's own semantics. @@ -333,10 +333,16 @@ fn latch_and_dispatch( let ctrl = keys.pressed(KeyCode::ControlLeft) || keys.pressed(KeyCode::ControlRight); let alt = keys.pressed(KeyCode::AltLeft) || keys.pressed(KeyCode::AltRight); let sup = keys.pressed(KeyCode::SuperLeft) || keys.pressed(KeyCode::SuperRight); - // Exactly the dev overlays' plane (`debug_panel::dev_plane`, minus the Super arm the `sup` - // gate below already covers) — it costs the keyboard its fallback probe. See + // Exactly the dev overlays' plane (`modkeys::dev_chord`, minus the Super arm the `sup` gate + // below already covers) — it costs the keyboard its fallback probe. See // [`BindingDispatch::resolve`]. - let dev_plane = ctrl && shift && !alt; + // + // **Only when there is something on the plane** (decision 1179). A player build holds no dev + // chord at all, so suppressing the reference's fallback there buys nothing and costs fidelity: + // `CTRL-SHIFT-P` would resolve to `None` instead of falling through to `SHIFT-P` + // (`TOGGLECHARACTER3`, the pet paper doll) the way the binary does. 1176 gated what the plane + // *offers* and left what it *costs*; this is the other half. + let dev_plane = ctrl && shift && !alt && crate::run_mode::dev_affordances(); let mut script = script; let armed = script.as_ref().is_some_and(|s| s.bind_capture_armed()); diff --git a/crates/benilla-app/src/bindings/store.rs b/crates/benilla-app/src/bindings/store.rs index 9b125fbd..0f634d04 100644 --- a/crates/benilla-app/src/bindings/store.rs +++ b/crates/benilla-app/src/bindings/store.rs @@ -1,5 +1,5 @@ -//! Binding persistence (decision 0997): `benilla/bindings/account.txt` and -//! `benilla/bindings/-.txt`, through [`crate::local_state`] like every resident. +//! Binding persistence (decision 0997): `benilla-config/bindings/account.txt` and +//! `benilla-config/bindings/-.txt`, through [`crate::local_state`] like every resident. //! //! Format is **command-centric diff-vs-defaults** — one line per command whose keys moved: //! diff --git a/crates/benilla-app/src/capture/fixtures.rs b/crates/benilla-app/src/capture/fixtures.rs index b8679b54..8d43b5ff 100644 --- a/crates/benilla-app/src/capture/fixtures.rs +++ b/crates/benilla-app/src/capture/fixtures.rs @@ -56,7 +56,11 @@ pub(super) fn seed_ui_fixture( ResMut, ), ) { - let Some(fixture) = ctx.scenario.ui else { + // A glue-screen capture has no world scenario, and no glue screen opens a UI fixture. + let Some(scenario) = ctx.scenario else { + return; + }; + let Some(fixture) = scenario.ui else { return; }; if ctx.ui_seeded || !(progress.total > 0 && progress.ready == progress.total) { @@ -636,7 +640,7 @@ pub(super) fn seed_ui_fixture( // Server-less, Player defaults to the origin — off every zone rect, so the blip // projects to the (0,0) hide sentinel and the arrow never shows. Park the avatar at // the scenario's Northshire spot so the arrow lands on the Elwynn map for real. - player.pos = benilla_assets::coords::wow_to_bevy(ctx.scenario.eye); + player.pos = benilla_assets::coords::wow_to_bevy(scenario.eye); // Alternating explore bits: roughly half of every zone's overlays reveal, so the // capture shows fog doing its job (some sub-areas drawn, some parchment). script.set_world_map_explored(vec![0x5555_5555; 64]); @@ -881,7 +885,7 @@ pub(super) fn seed_ui_fixture( ])), crate::net::SelfPlayer, crate::net::Guid(PLAYER_GUID), - Transform::from_translation(wow_to_bevy(ctx.scenario.eye)), + Transform::from_translation(wow_to_bevy(scenario.eye)), )); // The wolf: the live spawn's component set (net/apply.rs) with the descriptor seeded // directly, standing at the look point facing the camera, at full health. @@ -1164,7 +1168,7 @@ pub(super) fn seed_ui_fixture( ])), crate::net::SelfPlayer, crate::net::Guid(SELF_GUID), - Transform::from_translation(wow_to_bevy(ctx.scenario.eye)), + Transform::from_translation(wow_to_bevy(scenario.eye)), )); // The named unit out in the river (the `vplates` wolf, re-seated): 25 yd along the // scenario's own look bearing, so its overhead name lands on the water surface diff --git a/crates/benilla-app/src/capture/fxview.rs b/crates/benilla-app/src/capture/fxview.rs new file mode 100644 index 00000000..d45d7d4d --- /dev/null +++ b/crates/benilla-app/src/capture/fxview.rs @@ -0,0 +1,231 @@ +//! The `fxview` fixture's **driver** — the half of the effect-viewer instrument that stands the +//! subject up in the world (the request, its knobs and the camera are [`super`]'s). It lives here +//! because a fixture's driver belongs with its fixture: `entities::spell_fx` is gameplay, and +//! gameplay never depends on the capture harness. + +use bevy::prelude::*; + +use benilla_assets::m2_url; +use benilla_assets::materials::WowModelMaterial; + +use super::{FxViewRequest, FxViewState, FXVIEW_POS}; +use crate::creature_anim::FxStage; +use crate::entities::display::{empty_shell, ModelHandle}; +use crate::entities::spell_fx::{ + attach_effect_visuals, EffectHost, FxTintAnims, SpellFx, FALLBACK_SPAN, +}; + +/// The `fxview` unit lane's synthetic guid (`WOW_FX_DISPLAY`) — a high-word `0xF130` creature guid +/// like the wire's, in a serverless capture where nothing else claims one. +const FXVIEW_UNIT_GUID: u64 = (0xF130u64 << 48) | 0xFC0FEE; + +/// The `fxview` GameObject lane's synthetic guid (`WOW_FX_GO`) — the wire's `0xF110` high word, +/// which is what `crate::net` reads a GO's identity out of. +const FXVIEW_GO_GUID: u64 = (0xF110u64 << 48) | 0xFC0FEE; + +/// Drive the `fxview` capture fixture (see [`super::FxViewRequest`]) — the effect- +/// viewer instrument: once the capture driver arms it (scene settled), spawn a root at the +/// fixture point, create the model's [`SpellFx`] cache entry, attach the full visual set +/// through the same [`attach_effect_visuals`] body the game uses, and (for missiles) fly the +/// root along its facing so trails extend. Inert outside fxview captures (the request resource +/// only exists then). +#[allow(clippy::too_many_arguments)] +pub(crate) fn drive_fx_view( + req: Option>, + state: Option>, + mut commands: Commands, + fx: Option>, + asset_server: Res, + time: Res