Skip to content

Quest arc phase 0: one menu primitive for every choice (#375) - #642

Merged
pruuk merged 45 commits into
masterfrom
feat/375-phase0-menu
Oct 20, 2026
Merged

pruuk merged 45 commits into
masterfrom
feat/375-phase0-menu

Conversation

@pruuk

@pruuk pruuk commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Summary

Quest and dialogue arc (epic #375), phase 0: one menu primitive that every fixed choice in the game uses, plus the arc's requirements, roadmap, phase 0 spec and plan.

  • Menu core (internal/menu): render, resolve, confirm, label lint, close reasons, interrupt and follow lines. Game menus match only a number, Enter, or an exact label, id or alias; prefix matching is builder-only (owner-approved change from the spec, so n and l stay movement and look).
  • Game menu session (internal/users): OpenMenu/RouteMenuInput with a busy gate (combat, casting/crafting/salvaging, dead, asleep, quitting, controlled, off feet). State machines got RegisterObserver; characters push busy changes; conditions and room changes close menus. OpenMenu has no production caller until phase 1 (talk).
  • Prompts (internal/prompt): menu questions (AskMenu, AskConfirm). Player flows (character, guild, mail, rename, picklock, auctions, deletecharacter) are non-modal: a typed command that is not a choice closes the menu and runs. They close only on a NEW interrupt (OpenedUnder), so mail while sitting still works; room changes and follow pulls always close them. Mail says "You set your unsent mail aside." when a draft is dropped. Builders and start stay modal.
  • Typed input only: new events.CmdTyped. Engine-queued commands (follow steps, scripts, admin command forcing a player, web-client Char.Action.Try) can no longer answer a menu or prompt. A player's own macros still can.
  • Follow pulls: a leader pulling a follower out of a menu tells the follower, the leader and the room, judged with the leader's own light.
  • Builders: every admin numbered list renders through the menu (two columns for long lists, [x] toggles, 0 still finishes a toggle list). Room-editing builders are cancelled when the admin moves (RoomBound). Picking server config entries by number now works; "new" recipe by number now creates one.
  • Confirms: two entries, Enter is No, irreversible actions name the action ("Yes, disband it").
  • Web client: new GMCP Char.Menu {open} (queued ahead of the menu text, resent on spawn). Ticks and triggers queue while a menu or prompt is open and release one per round after; nothing fires during login (timers cleared on disconnect). Char.Action.Try replies deferred / menu open as a backstop. Queue audit found no defects.
  • Both numbered-list templates are deleted; a guard test keeps numbered-menu code inside internal/menu.

Known gaps (filed)

Test plan

  • go build ./..., gofmt -l clean
  • go test ./... (all packages; root guards fixed and re-run)
  • golangci-lint run --new-from-merge-base=origin/master: 0 issues
  • all tools/jstest/*.test.js pass (queue audit 31 checks, automation pause)
  • boot check in a detached worktree on private ports: Server Ready, 0 panics
  • race detector (needs gcc; not available locally, CI if configured)
  • playtest: talk-free menus in play (character, guild, mail, picklock), follow pull, web-client tick pause

Every task had an implementer plus spec and quality reviews; review fixes are in follow-up commits.

🤖 Generated with Claude Code

pruuk and others added 30 commits October 20, 2026 10:10
…#375)

QUEST_DIALOGUE_REQUIREMENTS.md is the one-page target: goal, player
flow, rules and the done-when checklist with its tests and guards.
QUEST_DIALOGUE_ROADMAP.md is the route: verified facts, approach,
topic structure, build order and phases 0 to 7.

Brainstormed with the owner 2026-10-20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Phase 0 spec: pure internal/menu core, non-modal game session with
interrupts, modal builder session, follow-pull lines, typed-input flag,
and migration of every fixed choice. Roadmap and requirements record
the session's rulings (no terminal clicking, help and context.md
coverage, corrected menu-parse counts).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…login (#375)

Ticks and triggers queue while a menu or prompt is open (server pushes
Char.Menu), the queue resumes paced after close, and nothing fires at
the login prompts: tick timers are cleared on socket close and start
only after the session's first Char.Automation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ueue audit (#375)

Quest menu entries show the title with the player's line beneath it,
and choosing any topic plays out to the room as a conversation while
menus stay private. Phase 0 audits the web client action queue (front
mode especially) with tests before building the pause on it; login
automation stays dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Game menus resolve only numbers, Enter and exact labels, ids or aliases,
so a movement or look shortcut typed mid-menu runs as a command. Builder
menus opt into prefix matching and free text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n, control (#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 phase 0)

OpenMenu, RouteMenuInput, CloseMenu, CloseMenuForFollow,
CheckMenuInterrupts and CloseMenusForRoomChange on UserRecord, gated by
menuGate (dead, combat, busy, asleep, quitting, off feet, controlled).
characters.BusyStateChanged lands on CheckMenuInterrupts. MenuChanged
fires when the first menu or prompt opens or the last one closes; the
open state is read before and after each change in the same critical
section, and callbacks, SendText and the event run with sessionMu
released. prompt.Prompt gains NonModal (Task 9 adds the rest).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every grapple position is off the feet, so a seized player read "as you
go down". The controlled rule now comes first and stays nil-safe for a
character loaded from a save, which has no Control machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 phase 0)

- events.DrainQueuedMenuChangedForTest, and tests for the MenuChanged
  sequences: open/close, replace, prompt/clear, clear with nothing open,
  one interrupt closing a menu and a non-modal prompt.
- A choice holds MenuChanged until OnChoose returns, so a choice that
  opens the next menu never reads as closed in between.
- MenuChanged is queued under sessionMu with the change it reports, so
  signals cannot reorder across goroutines; callbacks and SendText stay
  outside the lock.
- StartPrompt over an open game menu closes it with CloseReplaced.
- One interrupt takes the menu and a non-modal prompt in one critical
  section and signals once.
- Table test for every menuGate rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…375 phase 0)

prompt gains AskMenu, AskConfirm and Question.Resolve; a menu question
answers through menu.Resolve and records LastResult. world.go gives a
typed line to an open game menu first, then to the prompt through
UserRecord.RoutePromptInput, and clears a finished prompt before the
redraw (ClearFinishedPrompt) so Char.Menu reports it closed promptly.

A queued command never answers a menu or prompt any more, which also
closes the hole where a follow step could be swallowed as a builder
prompt's answer. A non-modal prompt closes on a line its menu does not
recognise and the line runs as a command. The exit-message requeue in
go strips CmdTyped so its delayed re-run cannot answer a prompt opened
in between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ee (#375 phase 0)

Also closes a menu on the synchronous Sleep path and makes the hook's mob
room-change test able to fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase 0)

Adds cases A2-A10: front ordering, dedup, cap, rejected, deferred aging,
one release per round, login hold (with setTimeout stubbed so no stray
timer), front/back ordering while held, death clear. No queue defect found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…login (#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…phase 0)

The leader has already left the room when the lines are spoken, so the
follower, leader and room lines now use DepartureSight and a new
Room.DepartureSnapshot. The party follow path breaks off before "You follow
the party leader." Also updates the condition-apply allowlist line for
sleep.go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pruuk and others added 14 commits October 20, 2026 12:22
…rows in the queue (#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… route tests (#375 phase 0)

A line a game menu consumes never reached TryCommand, so it neither
stamped the last input round nor woke Idle/AFK; a player working
through a menu could idle out. The wake moves to UserRecord.WakePresence
(TryCommand calls it), and world.go calls NoteActivity for a consumed
non-empty menu line, then redraws the prompt line so telnet players get
one after a silent close.

A modal builder prompt that edits the current room survived a follow
step or forced move, so the next answer acted on the new room. Prompts
gain RoomBound; the room container, mutator and exit builders and zone
edit set it, and CloseMenusForRoomChange cancels a RoomBound prompt with
an admin line. A source guard keeps every StartPrompt in those files
room bound.

Adds the missing RoutePromptInput tests: free-text answer and re-run,
empty command, NoSuchNumber and Ambiguous refusal lines, and the menu
AcceptText branch of Question.Answer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l/menu (#375 phase 0)

Item type and subtype, room containers and exits, trap conditions,
container recipes, room and zone mutators and server config are now
AskMenu questions; the restNum parse blocks and numbered-list renders
are gone. The spell list is display only and prints plain lines.

Departures from the plan, both from reading the source:
- optionsMenu uses an option's Id (as text) as the entry Id when it has
  one. Condition names are not unique (two shipped specs are "Stunned"),
  so Id=Name made picking the second by number select the first.
  optionByResponse maps an answer back to its option.
- getConfigOptions keeps the full path in Id and the segment in Name, so
  configMenu uses Name: server_Config appends the answer to its prefix.
  It re-asks after each level so a number picks from the level drawn,
  and a bad path now redraws the current level instead of the top one.

Toggle lists re-ask with updated marks so [x] shows. Choosing "new"
recipe by number now creates a recipe (it used to edit recipe 0).

builder_menu_test.go drives each flow by number through
RoutePromptInput and the command function.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es, subtype Enter redraws (#375 phase 0)

- Trap conditions sort by name then id. Two specs are named "Stunned"
  and ids arrive in map order, so their numbers swapped between redraws
  (the toggle test failed 15 of 50 runs without the tie-break).
- Condition 0 (the quit timer) is not offered as a trap condition; its
  id "0" collided with the toggle menus' leave answer.
- menu.Menu gains LeaveAliases, checked before the number branch, so a
  typed "0" still finishes the trap, room mutator and zone mutator
  lists. LintLabels flags a leave alias that shadows an entry.
- The item subtype menu has no Cancel entry again: Enter with no
  subtype redraws instead of ending the build. The abort branch it fed
  is gone (an answer can no longer be empty).
- A modal menu that gets a line it cannot use, or Enter with no default
  and no leave, now says "Pick a number from the list." before the
  builder redraws (the old builders said "Invalid selection.").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l/menu (#375 phase 0)

Confirms are two entries, Enter picks No, and actions that cannot be
undone name the action. Player flows are non-modal, so a command typed
mid-choice closes the menu and runs; builders and start stay modal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st templates (#375 phase 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…side line; tests (#375 phase 0)

A non-modal prompt records the interrupt rule already holding when it
opens (Prompt.OpenedUnder), so mail opened while resting or picklock
started in a fight is closed only by a different rule, and a rule that
lifts and returns counts as new. Room changes and follow pulls still
close it. Prompt.SetAsideLine is spoken whenever such a prompt closes
unfinished; mail says "You set your unsent mail aside."

Also: rename confirm names the action, character confirms fail safe on
anything but yes, the veteran decline no longer announces the arrival
twice, and driving tests for the character room alt delete, picklock,
the mail set-aside line and the rename labels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The perma-gear AddCondition allowlist key moves to its new line. The
refusal phrase guard already skips cmdPrompt.Ask questions because a
question waiting for an answer is not a refusal; a question can now be a
menu, so the skip also covers a menu.Menu Heading and the question
argument of cmdPrompt.AskConfirm and menu.Confirm. Entry labels, confirm
labels and Heading keys of other types are still scanned, with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove unused util.BoolYN, document Menu.LeaveAliases, scan root *.go in
the menu guard, share the "no such choice" refusal line, and early-out
CheckMenuInterrupts when nothing is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the quest arc requirements, roadmap, phase 0 spec and plan onto
the implementation branch so they ship in one PR. README conflict:
master's archived combat-reachability rows kept, phase 0 rows added.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner ruling 2026-10-20: being off their feet is not a gate for audio
interactions. menuGate drops its not-standing rule; CloseOffFeet, its line
and the position busy observer are removed. Combat, busy, dead, asleep,
quitting and grapple control still gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pruuk

pruuk commented Oct 20, 2026

Copy link
Copy Markdown
Owner Author

Owner ruling 2026-10-20 applied (96b9253): being off your feet (sitting, prone) is not a gate for audio interactions. The menu gate no longer refuses or closes menus for position; combat, busy, dead, asleep, quitting and grapple control still gate. CloseOffFeet and its line are removed; tests prove seated and prone players can open menus and that sitting down keeps one open.

@pruuk
pruuk merged commit 243b7fb into master Oct 20, 2026
4 checks passed
pruuk added a commit that referenced this pull request Oct 21, 2026
Moved 2 to completed/ (the #375 phase 0 spec and plan, shipped in PR
#642), 0 to abandoned/. Stayed at the top: the phase 1 spec and plan
(in progress), the autosave and messaging arc specs (open follow-ups).
README repointed; the relative-link check found no new breaks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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