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>
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>