App.run ended its menu loop on Quit and just returned. The console exits then,
but in the GUI run() is on a daemon worker while the Swing JFrame stays
displayable on the EDT, keeping the JVM alive — and the dismissed menu overlay
just revealed the running game underneath ("back into the game"). EXIT_ON_CLOSE
only fires for the native window X, not a programmatic quit.
Add a GameIO.shutdown() teardown hook (default no-op) and call it after the menu
loop ends. SwingIO overrides it to stop music, dispose the window, and exit the
JVM, mirroring EXIT_ON_CLOSE; console keeps the no-op. Extract an injectable
App.run overload and add AppQuitTest asserting the Quit path invokes shutdown().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the map/quests sidebar more room: raise the width fraction (0.28 -> 0.34)
and minimum (180 -> 220), and remove the SIDE_MAX upper clamp so the panel
keeps scaling with the window instead of stopping at 460px.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Install the downloaded Cascadia Mono Nerd Font as src/main/resources/fonts/
game.ttf — the exact name SwingIO.loadFont() looks up on the classpath
(/fonts/game.ttf), so the GUI now uses a real monospaced Nerd Font (icon
glyphs included) instead of falling back to the logical monospaced font.
Include the font's SIL Open Font License as required for redistribution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switch OggMusicBackend from streaming an external, git-ignored music/ folder
(relative to the run directory) to streaming bundled classpath resources under
/music/, so the four manor tracks ship inside the JAR and play out of the box —
consistent with how fonts are bundled. The play loop reopens the resource each
iteration via a BufferedInputStream (the vorbisspi SPI needs mark/reset to read
the OGG header). Missing tracks still fall back to silence.
Add the four converted OGG Vorbis tracks (manor-theme/cellar-drone/musicbox/
lament) under src/main/resources/music/, un-ignore that path, and re-anchor the
gitignore so only the top-level MP3 source folder stays ignored. Update
docs/music.md for the bundled approach and conversion recipes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the 5-room demo with a condensed 14-room manor on one spine
(restore power -> escape) that stacks all five mechanics, plus an optional
spirit thread feeding 3 prioritized endings (no hard-fail). Pure YAML on the
existing engines: 14 rooms across 3 levels, ~16 items, 5 combinations, the
old_man hint NPC, restore_power + lay_to_rest quests, and 3 endings.
Consolidate to one world: remove the toy "Test Manor" test fixture so the
loader tests exercise the real shipped world. Adapt WorldLoaderTest and
TutorialLoaderTest to form-invariants of the real world, and add
WorldSolvableTest, which walks the critical path (light/lock gating +
Combinations.tryUse + flags) to guarantee power_on, ghost_banished, and a
victory ending are reachable — an unsolvable world now fails the build.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per-room OGG Vorbis background music in the Swing GUI: a testable MusicController
(idempotent track switch, null->silence, OFF disables) over an isolated
OggMusicBackend (vorbisspi/jorbis, streamed PCM, ~400ms gain fades, graceful on
missing files). Driven from GameIO.setMusic per turn (console no-op) and a new
persisted Music level (Off/Low/Med/High, default Medium) in Settings. Tracks come
from an external gitignored music/ folder via an optional rooms.yaml `music:`
field. Console stays silent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard colorMode and glyphMode against null in the Settings compact
constructor (alongside the existing musicLevel guard), so a hand-edited
settings.json with a missing field no longer NPEs in SettingsMenu.
Remove the unused MusicLevel import from SettingsMenu. Assert that the
defaulted musicLevel survives a round-trip in SettingsStoreTest.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A command during the walkthrough (e.g. use front_door -> fled) no longer ends
the game mid-tutorial; a pending ending fires once the tutorial finishes or is
skipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A skippable interactive walkthrough shown on New Game: a TutorialGuide layered
onto the real game loop makes the player perform the core commands
(look→examine→take→inventory→use→go→go to) in the starting room, with verb-/
alias-aware completion (movement steps require an actual room change), gentle
hints on wrong-but-valid input, and closing tips for the rest. Data-driven
(tutorial.yaml + standalone TutorialLoader); 'skip' ends it; loaded games never
show it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Alias-aware, stateful engine with verb/minArgs matching, go_direction/go_to_room
room-change detection, skip, and closing-tips on completion. 7 tests, all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BFS auto-walk over visited rooms (respecting locked exits + darkness), routed
through GoCommand (go to <room> vs go <direction>). Console: silent traversal +
a start/middle/destination summary line. GUI: the minimap animates the player
moving room-by-room via a new GameIO.travelStep hook (SwingIO override, ~300ms;
console no-op). New stateless Pathfinder BFS in the game package. Trie
autocomplete deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- UX: unknown-target message now reads "don't know any direction or place
called '...'" so direction typos (e.g. 'go nroth') make sense
- clarity: resolveVisited uses equalsIgnoreCase for id/name matching;
add null-world guard comment pointing to CommandTestSupport
- Javadoc: note that 'to' filler word is stripped, making go-to forms equivalent
- tests: assert 'through the Hallway' in 3-node route; update unknown message
assertion; add adjacentWalkUsesShortSummary covering the n==2 short-form path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Data-driven combination recipes (combinations.yaml: requires/consume/produce/
effects/response/failText), operands resolved from inventory or current room,
order-independent canonical pair key. UseCommand routes 2+ args to a new
Combinations engine; no parser change (on/with/to already fillers). Loader
validates ids + rejects self-combination/duplicate pairs at load. Demo:
matches + torch -> lit_torch (a working light source).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Combinations.tryUse: only write response when non-null and non-blank,
preventing NPE/IllegalArgument for effects-only recipes.
- CombinationFactory.fromDto: throw WorldLoadException when a == b,
since item singletons make a self-combination impossible at runtime.
- Tests: +effectsOnlyRecipeAppliesEffectsWithoutProduceOrResponse
(CombinationsTest 7→8), +rejectsSelfCombination
(CombinationFactoryTest 4→5).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the plain Swing JDialog menu with a glass-pane overlay that fills
the whole window: dark background matching the game palette, bundled font,
lavender heading, flat hover-highlighted buttons, Escape = last option. The
overlay hides the game chrome (HUD/map/quest panels) while in the menu and
rebuilds at the current frameScale*zoom on window resize, so it scales
correctly from startup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a main-menu shell above the game loop (New Game / Load / Settings /
Quit) via a new GameIO.choose primitive (console numbered menu + Swing
buttons), JSON save/load as a delta over the YAML world (single active
slot; manual save, autosave on quest-completion + every 10 turns, and
quit-to-menu), and minimal runtime-togglable settings (colour + glyph).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>