creatures keep their first corner, and characters never share files

- a creature walking a path no longer cuts its first corner: the path's first waypoint was dropped at decode (2342)
- two characters whose names differ only in accented or non-Latin letters no longer share macros, bindings and SavedVariables; a file that failed to load is not overwritten at logout (2342)
- custom chat channels survive a logout, a collapsed quest header keeps its quests watched, and a trackpad swipe scrolls as far as it adds up to (2342)
- a hearth off a zeppelin no longer lands back on its deck, and an opened lockbox with loot left in it unlocks (2342)
- every snapshot runs on Vulkan, in a container, before it ships (2344)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 12:19:22 +07:00
co-authored by Claude Opus 5.5
parent 9c49f10882
commit 293b8e57fb
60 changed files with 1876 additions and 425 deletions
+46 -34
View File
@@ -13,6 +13,7 @@ use bevy::prelude::*;
use benilla_assets::LockRecover;
use benilla_ui::script::UiScript;
use super::manifest::silenced_ui_load;
use super::{
load_font_registry, load_ingame_ui, CursorPayloadHeld, PlayerUiHover, UiClock,
UiKeyboardCapture,
@@ -584,43 +585,54 @@ pub(crate) fn load_ingame_ui_on_world_entry(world: &mut World) {
// through its whole load edge and only converged a frame later off whatever poll the caller
// had written to survive it.
seat_raster_seam_for_load(world, &mut script);
let _ = load_ingame_ui(&mut script, identity.as_ref(), &roster, version_check);
// The Minimap widget was born a moment ago with `MinimapState::default()`; seed its two live
// zoom indices from the persisted CVars now, before anything reads them — the reference's own
// minimap reset path copying each CVar object's int into its live index (decision 1131). Once
// only: from here the widget's index is the live truth and `Minimap:SetZoom` writes the CVar
// back. Startup always precedes this state edge (1038), so the knob is already loaded.
let zoom = world.resource::<crate::minimap::MinimapZoom>();
script.set_minimap_zoom(zoom.outdoor, zoom.inside);
// The saved-variables chunk runs HERE — after the XML assigned its file-scope defaults, before
// any consumer reads them — then `VARIABLES_LOADED`. That is the reference's own load order
// (`AddOn_Load 0x51f240` steps 2 → 4 → 6, decision 1128); reversing it means the defaults
// always win and nothing can ever be remembered.
//
// **And the chat cache restores inside it, between `VARIABLES_LOADED` and `PLAYER_LOGIN`** —
// the reference's own slot for the reader's `UPDATE_CHAT_WINDOWS` + `UPDATE_CHAT_COLOR` burst
// (`0x4900d6`, after `0x4900b2` and before `0x490959`; decisions 2119 and 2125). It is the
// sole firer of `UPDATE_CHAT_WINDOWS`, which is the only thing that registers a chat frame for
// any `CHAT_MSG_*` (ref `ChatFrame.lua` l.1261-1273) — as an `Update` system it landed after
// the session's first chat had already been routed, and the login MOTD went to a window
// registered for nothing.
// The plate pair is read out of the world FIRST: the second closure borrows `world` for the
// chat-cache restore, and a `Copy` of two bools costs nothing next to fighting that borrow.
// Absent in a bare test world, where "both off" is also the resource's own default.
// The minimap zoom pair and the plate pair are read out of the world FIRST: the bracket's body
// borrows `world` for the chat-cache restore, and two `Copy` pairs cost nothing next to
// fighting that borrow. The plates are absent in a bare test world, where "both off" is also
// the resource's own default.
let zoom = {
let z = world.resource::<crate::minimap::MinimapZoom>();
(z.outdoor, z.inside)
};
let plates = world
.get_resource::<crate::vplates::VPlateMode>()
.copied()
.unwrap_or_default();
finish_ui_load_with(
&mut script,
// `NAMEPLATES_ON` / `FRIENDNAMEPLATES_ON` (2132). This seat, and not the `Update` feed
// beside it, is what fixes the bug: `UIParent_OnEvent`'s first `UpdateNameplates()` runs
// inside the `VARIABLES_LOADED` fired at the end of this very call.
|script| crate::vplates::push_plate_globals(script, plates),
|script| {
crate::ui_chat::restore_chat_looks(world, script);
},
);
// **The whole load edge is silent** — the TOC walk, every addon, the saved variables and
// `PLAYER_LOGIN`, the span `0x48fbf0` brackets itself across (`0x48fbfa` → `0x49016d`). Both
// login and `ReloadUI` come through here; see [`silenced_ui_load`].
silenced_ui_load(&mut script, |script| {
let _ = load_ingame_ui(script, identity.as_ref(), &roster, version_check);
// The Minimap widget was born a moment ago with `MinimapState::default()`; seed its two
// live zoom indices from the persisted CVars now, before anything reads them — the
// reference's own minimap reset path copying each CVar object's int into its live index
// (decision 1131). Once only: from here the widget's index is the live truth and
// `Minimap:SetZoom` writes the CVar back. Startup always precedes this state edge (1038),
// so the knob is already loaded.
script.set_minimap_zoom(zoom.0, zoom.1);
// The saved-variables chunk runs HERE — after the XML assigned its file-scope defaults,
// before any consumer reads them — then `VARIABLES_LOADED`. That is the reference's own
// load order (`AddOn_Load 0x51f240` steps 2 → 4 → 6, decision 1128); reversing it means the
// defaults always win and nothing can ever be remembered.
//
// **And the chat cache restores inside it, between `VARIABLES_LOADED` and `PLAYER_LOGIN`**
// — the reference's own slot for the reader's `UPDATE_CHAT_WINDOWS` + `UPDATE_CHAT_COLOR`
// burst (`0x4900d6`, after `0x4900b2` and before `0x490959`; decisions 2119 and 2125). It
// is the sole firer of `UPDATE_CHAT_WINDOWS`, which is the only thing that registers a chat
// frame for any `CHAT_MSG_*` (ref `ChatFrame.lua` l.1261-1273) — as an `Update` system it
// landed after the session's first chat had already been routed, and the login MOTD went to
// a window registered for nothing.
finish_ui_load_with(
script,
// `NAMEPLATES_ON` / `FRIENDNAMEPLATES_ON` (2132). This seat, and not the `Update`
// feed beside it, is what fixes the bug: `UIParent_OnEvent`'s first
// `UpdateNameplates()` runs inside the `VARIABLES_LOADED` fired at the end of this
// very call.
|script| crate::vplates::push_plate_globals(script, plates),
|script| {
crate::ui_chat::restore_chat_looks(world, script);
},
);
});
// **Say it out loud when an addon didn't load** (decision 1495). Every failure the walk found
// is retained now, but a log nobody knows to open does not fix silence — and silence is the
// actual defect B293 reports: *"there are a lot of addons that still doesn't work"*, with
@@ -961,7 +973,7 @@ pub(crate) fn end_ui_session(world: &mut World) {
// that lands in an engine-side store that survives), before the VM is replaced. The next
// VM's registration seeds from what this writes ([`crate::cvars`]'s saved base).
crate::cvars::fold_dying_vm_cvars(world);
// The chat cache, on the same terms and for the same reason (decision NNNN): it composes the
// The chat cache, on the same terms and for the same reason (decision 2184): it composes the
// player's file out of the DYING VM, and `/reload` never crosses the `OnExit(InWorld)` edge
// its flush used to hang on — so a window moved in the last second before a reload was
// written nowhere and re-read stale from disk.