Every change here started life as a workaround in a game and is really
the same finding: a fact about an entity had nowhere to live, so the
domain built machinery around the gap.
Character.Label / Character.Voice — a tape is a character that speaks
into the log, not a second spelling of Say. VoiceOf and CharacterLabel
read them; Say obeys them.
Game.Do — a real action queue, in order, so a widget can start an
action. queueAction no longer drops what arrives while the runner is
busy; it queues it.
Widget.When + Gated + WidgetVisible — a widget declares when it is on
screen. Nothing ticks, draws or blocks a click while its condition is
false, so a HUD is hidden for a cutscene without a wrapper per widget.
TopBar.NoteVar — the title was already discovered; the note beside it
no longer needs a domain widget to push it in.
Game.UseWithFail — the pair nobody authored is content, and belongs to
the domain, exactly like ExitLook and ExitTake.
Game.Player / Game.Walkboxes — a scene that names no cast and no floor
means "the usual", instead of a defaults pass rewriting every scene.
Game.WindowScale — Run's 4× is now a default, not a decision.
SceneNav — the dev widget every game was writing.
Colour text: DrawText paints in the colour it is handed, and GlyphW/H,
TextWidth, WrapText and ClipText are the library's, not each game's.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Registration order was the only thing deciding what a widget was drawn
over, which forced a domain to keep one ordered list of every widget it
owns — the one shape that cannot be split into a file per widget.
A widget now says where it belongs: Layer, the eight LayerScene..LayerCursor
constants, the optional Layered interface and LayerOf for wrappers. Draw
runs from the bottom layer up, Tick from the top down, and registration
order only breaks ties inside a layer. Every built-in declares its own;
anything that stays quiet sits on LayerHUD, so existing HUDs come out where
they were.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UIManager named the category, not the contents. Every other manager is
named after what it registers — ItemManager holds Items, SceneManager
holds Scenes — so the one that registers Widgets is the WidgetManager.
The alias, the Game field and both manual pages move together.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The opening a game plays was the one piece of content that had to be
assembled in the wiring, because OnStart wanted an Action. It takes the
name of a registered Script now, so an opening is a Script like any
other, and Validate rejects a name that is not there.
OnFinale names the closing script and queues it straight after the start
one — what a "boot into the ending" debug flag wants.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
AudioPlayer keeps the *AssetManager it is handed, so attaching it during
construction quietly pinned it to the manager NewGame happened to make. A
domain that assigns its own registry onto the Game — legal, the fields are
plain *Manager values — got a working image path and a silent audio one.
Run attaches instead, once the Game is final.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Set is the deliberate overwrite: it replaces a registered entry in place,
keeping its position in the insertion order, and falls back to Register
when the name is new. Register keeps panicking on duplicates.
All returns every entry in insertion order; orderedWidgets is now just
that call, and reversedWidgets builds on it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A connection between two scenes had to be hand-built as a hotspot: an area,
a cursor, a GoTo, and a flag check written out again for every door. Scene
now carries Exits, and the engine expands each one into a hotspot.
- Exit{To, Label, Side, Area, Needs, Blocked, OnLook, OnTake} in scene.exit.go.
With no Area an exit is an edge strip, sized as a fraction of Game.SceneArea()
so a game whose HUD covers the foot of the window gets strips inside the
painting. Game.ExitLook / Game.ExitTake supply the look and take responses
once for the whole game, so the phrasing is not repeated per exit.
- Game.SceneHotspots(name) is the scene's own hotspots followed by its exits,
cached per scene. Authored hotspots come first, so a painted thing beats the
edge strip wherever the two overlap. HotspotAt and HotspotDebug read it.
- Game.CurrentScene() / PreviousScene(), both persisted in the save file. The
engine tracked the current scene but exposed no accessor, so every game had
to shadow it to know where it was.
- Back() returns to the previous scene — what an exit with an empty To binds
to, for a scene reachable from several rooms whose way out is a direction
rather than a place.
- Validate rejects an exit naming a scene nobody registered.
Generated hotspots are named ExitName(To) ("exit:street", "exit:back"), so the
location graph can be read straight back out of the registered scenes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>