Skip to content

Budgets and cleanup: frame budgets, Touch ID, plugin API additions, review follow-ups - #31

Merged
MHohlios merged 20 commits into
mainfrom
wiffletree/budgets-and-cleanup
Nov 3, 2026
Merged

MHohlios merged 20 commits into
mainfrom
wiffletree/budgets-and-cleanup

Conversation

@MHohlios

@MHohlios MHohlios commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

The work between Phase 7 and Phase 8: the frame budgets missed on main, Touch ID for the vault, the plugin API gaps Aardwolf found, and the review follow-ups from Phases 6 and 7.

What's in it

  • Inspector frames (VYT-255): lines are laid out once when they arrive, only the newest 16 of a burst, and older rows the first time they scroll into view. The redraw no longer shapes text.
  • Chat at text size 22 with Settings open (VYT-256): a line the sheet hides except for a strip at its left is laid out only for that strip. Wrapped, command and non-ASCII lines are still drawn whole; a screen reader still gets every line.
  • Map spikes (VYT-254): profiled as the main thread waiting in -[CAMetalLayer nextDrawable], not map work (main-thread CPU p99 1.6–3.1 ms at 2,000 rooms). No map change. The harness verdict now judges only pan and zoom.
  • Touch ID for the vault (VYT-253): in a macOS release build, the device key lives in the data-protection Keychain behind user presence. A cancelled or failed prompt leaves the vault locked and offers the master password; without the entitlement, no copy is kept and auto-login waits. Builds from source keep today's behaviour and say so. New setting: "Keep a copy of the key on this device". Windows and Linux say plainly that their copy isn't guarded.
  • Plugin API (VYT-257), additive to v1: [[channel]] delivers chat channels from GMCP (they skip the map, tables and gear); a send_gmcp permission with [[request]], approved like send_commands and sent only as a GMCP frame; gear stats and map doors read plugin-declared fields, so the core holds no game's GMCP names. Aardwolf 0.2.0 uses all three.
  • Cleanup (VYT-258): approvals kept per copy (installed copy by hash, folder by path); a plugin loads from the bytes it was hashed from; every plugin change runs in one queue; shared JSON, atomic-write (with fsync) and UI helpers; the rest of the Phase 6 and 7 follow-ups. Old approvals ask once again after this lands.

Checks

  • fmt, clippy -D warnings, simulated Linux lint, cargo test --workspace --no-fail-fast --locked (1,724 passed, 0 failed), --synthetic --ax-check 77 of 77.
  • Quiet machine, load 2 to 4, three rounds beside main 7efee42:
    • Every Settings page, the Inspector and the closed window: stream and split p99 under 8 ms each round. Main's Inspector missed once (8.3 ms).
    • Map at 2,000 rooms: pan 3.6–3.8, zoom 3.3–3.5 ms, the same as main.
    • Chat size 22: max 7.94 ms; main missed twice (8.06, 8.21).
    • Scrolling the Inspector sometimes locks onto 8.3 ms frames (p99 up to 10.4) on both builds alike: the macOS drawable wait, not MUDling's work.

Follow-ups

  • Report main-thread CPU beside wall time in --measure, so a run separates work from the drawable wait (map_view/harness.rs, measure.rs).
  • Fractional display scaling can still cut the Inspector's time label (settings/inspector.rs:256).
  • Windows Hello for the vault's device key (vault/key_store.rs); the signed Touch ID path is unchecked until the provisioning profile exists (vault/key_store/touch_id.rs).
  • Area names learned from GMCP are lost on a map reload (map/live.rs:290); mazes match by the plugin's name, not the GMCP one (session/map_rules.rs:115).
  • Tests: requests before GMCP and after reconnect (session/requests.rs:226), sends_gmcp and the directory row text (registry/mod.rs:114), the Inspector's ellipsis path (settings/inspector.rs:112).
  • One GMCP field type instead of two (plugin/points.rs:450); approval doc comments to mention GMCP requests (settings/plugins.rs:171); one shared trim for the newest Inspector lines (settings/inspector.rs:186).
  • Phase 6 data items that need a design: the vault and sync lock window, a tombstone from a clock running behind, a duplicate note after a killed merge, the unfinished join sweep, import ranking by time.

Member reviews: Inspector 1 full review and 1 re-check; chat size 22 1 full review; map spikes 1 full review; Touch ID 2 full reviews; plugin API 1 full review and 1 re-check; cleanup 1 full review.

Reviewed with Wiffletree: 1 full review and 0 re-checks, 1 round (Tests, Claude, Style)

mhohlios added 20 commits November 2, 2026 20:34
The verdict counted moves and the one frame drawn as the run ends, so a run
could print MISSED with pan and zoom both within 8 ms. Also says how one
late frame can lock a run on to the wait for a drawable.
Gear measures its stats from hud.toml's [character] fields instead of
Char.Status keys. map.toml gains room_exits, the field a door changed in
place lists the exits in, and [area_info], the message naming an area.
Avatar declares its room_exits; the core holds no game's names for them.
The Inspector draws once a second, and each draw showed up to 16 lines
it hadn't shown before. Shaping them all in that one frame was most of
the draw, and the draw frames are more than one in a hundred, so they
set the page's p99. Each line is now shaped when it's recorded, and the
row paints the shaped text, cut short with an ellipsis as before.
…ow-ups

Security: an approval is held per copy (the data folder's by hash, a
folder by path), so a folder and an installed plugin with one id no
longer share or take back each other's. Installed plugins load from the
bytes that were hashed. Every change to the plugins runs in turn on the
launcher the last one left, so a folder reload, a sync and installs no
longer drop or undo each other, and directory installs go one at a time.

Also: removal survives a failed mirror delete, a failed re-fetch keeps an
installed link's mirror, a switch flipped back asks for no reconnect,
plugin new refuses the built-in defaults' id, and an ax-check covers the
overrun notice. Shared helpers for JSON files, atomic writes with fsync,
budgets, card lines, problem and question lines, tab focus, cached views
and merge deletes; narrower pub items; doc fixes.
…or Aardwolf

text.toml's [[channel]] names a GMCP field whose text is a chat line, read
by the text rules and triggers like a server line. connection.toml's
[[request]] sends a declared GMCP message each time a field changes, under
a new send_gmcp permission approved like send_commands; a request is only
ever framed as GMCP, and can't be a Core message or hold control
characters. Aardwolf subscribes to Comm 1, shows comm.channel, and sends
request area so room.area names its areas.
At chat size 22, or in a window below about 816 px, the 80 columns start
left of what the sheet hides, so no line counted as hidden and every line
in view was shaped and painted whole each frame.

A one-row line the sheet hides all but the start of now lays out just
those columns, plus one to spare. Lines that wrap, show past the sheet's
right edge, echo a command, or hold characters that may not take one
column are still drawn whole. A screen reader still gets every line,
since nothing counts as covered while one listens. Item names cut by the
reach keep their underline in the part that shows.
A macOS release build (MUDLING_VERSION set) keeps the device's copy of the
vault key in the data protection Keychain with a user-presence access
control, so only Touch ID, or the login password, reads it. A cancelled or
failed prompt gives Error::Cancelled and leaves the vault locked; auto-login
keeps waiting for an unlock. A build without the Keychain entitlement keeps
no copy and logs that once; it never falls back to an unguarded copy.
Unlocking with the master password no longer reads the old copy, so it
never prompts.

Development builds, Windows and Linux keep today's unguarded copy, and
Settings says so. A new "Keep a copy of the key on this device" switch
(on by default) removes the copy and makes every unlock take the master
password.
…sn't cut

gpui rounds an explicit width to the nearest device pixel, which could
make the column narrower than the shaped text and end it in an ellipsis.
…n they show

A burst of a hundred lines shaped every one of them in its arrival
frame. A batch now shapes only as many of its newest lines as a draw
shows; an older line is shaped the first time a draw shows it, and
kept.
A channel line may be another player's words, so only the Text point's
patterns, the colors and links, and the player's triggers read it; its
events go to hooks alone. Other points' rules, table readers and quiet
reads never see it.
@MHohlios
MHohlios merged commit 61f49ac into main Nov 3, 2026
6 checks passed
@MHohlios
MHohlios deleted the wiffletree/budgets-and-cleanup branch November 3, 2026 11:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant