Conversation
A typo like `take segfault_sheld` used to dead-end at "Cannot find segfault_sheld in this directory." with no clue whether the mechanic or the spelling failed. Now every not-found path (take, cat, examine, talk, drop, equip, cd) appends one line: the close match via difflib, or the list of what is actually here. cd hints only discovered exits so hidden rooms stay hidden. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZzvukbvLcLhqtaJMd6KxS
Esc was bound straight to `quit`. At tutorial start there is no "progress", so quit skipped the confirm screen and the game closed the instant a player hit Esc to abandon a typo. Now Esc with text in the prompt clears the prompt, like a shell; Esc on an empty prompt still goes through the existing quit flow. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XZzvukbvLcLhqtaJMd6KxS
Remove three pieces of code that could never run: - `_get_discoverable_hidden_rooms` / `_get_hidden_room_hint` had zero callers and had drifted out of sync with the YAML they duplicated: they described `opt_mage_tower`/`srv_warrior_tomb` as restricted to "mages"/"fighters" (pre-dating the Guardian/Weaver/Shaman rename) and called three `hidden: false` rooms hidden. Wired up today they would have misled the player. The data they hardcoded already lives in data/rooms/*.yml. - `self.commands` / `self.commands_with_args` were assigned empty in __init__ and never populated, so the `elif cmd in self.commands` branch in handle_command was unreachable. Phase 3 finished moving every verb to `command_registry`; the legacy path was left behind. Also replace the stale mirror_sector xfail. It claimed the room was "genuinely unreachable content", but all three of its premises were false: /proc/self is a path-tree child of /proc (proc_secrets), the Process Scheduler NPC there grants `sudo_quest_active`, and `_may_discover` gates `ls -a` on exactly that flag. The bug was in the test's `_reachable`, which BFS-walks `exits` only — but `cd` does path arithmetic over the room tree and is not restricted to exits. Two replacement tests model real navigation instead, and both were mutation-checked to confirm they fail when the invariant breaks: reparenting mirror_sector strands it, and dropping the NPC's story_flag seals it. Fix two pre-existing quality-gate failures surfaced along the way: E501 in test_not_found_hints.py and a `str = None` annotation on _show_error. `make check` now exits 0 (401 passed, 0 xfailed). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpGLtNtiShivwXKf555goM
game_world.py mixed one-shot world generation with the runtime state hit on every command. The generation half (place_items, place_starter_items and ~800 lines of _place_* / rarity / zone helpers) only writes item_locations and item_spawn_counts, so it moves to src/item_placer.py as ItemPlacer(world) with no back-edge into the runtime. Seeded placement output is byte-identical before and after for all three classes plus the unknown-class fallback. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
_show_error was the most common private reach-back from src/commands into CommandHandler (~25 call sites), yet it only writes to the output sink and logs. It now lives on GameOutput, which every command already has as ctx.output, so the commands no longer need the handler for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
event_bus and state_manager were process-wide singletons, so any handler a run left subscribed kept reacting to the next run's events (the recurring "fight each enemy twice / double loot" leaks), and two engines could never coexist in one process. ImprovedGameEngine now creates its bus and StateManager and hands them down: GameWorld, CommandHandler (commands reach it as ctx.bus), CombatSession, and the UI via a new UIProtocol.attach_bus(). The module globals are deleted rather than kept as defaults, so a missed reference fails at import instead of silently talking to a bus nobody hears. GameSession exposes .bus; tests use it, and the conftest fixtures that reset the shared bus between tests are gone. Verified with the full suite plus a live Textual smoke under Pilot (menu -> difficulty -> class -> tutorial -> play -> combat by key press). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CommandHandler was the shared junk drawer: commands reached back into 16 of its private helpers, so the src/commands split was structural only. The helpers now live in five collaborators the handler builds and exposes to commands: - ItemResolver (ctx.resolver): shortcut/partial/name lookups, player keys - ItemEffects (ctx.effects): key/weapon/lore/consumable/upgrade/spell use, execute_effect, story flags - LootService (ctx.loot): enemy drops, gear loot tables, award-once guard - TutorialCoach (ctx.tutorial): gated hints, current step, tutorial reactions to combat events - GameFlow (ctx.flow): game-over screen, victory/endings, quit confirm No command touches a ctx._private name any more. The handler keeps dispatch, event wiring, combat start/end and room presentation (1,375 -> ~530 lines). Moved method bodies were diffed against the originals; the only differences are the renames. Also drops the unused _get_room_status_indicator and adds tests for the death -> game-over flow, which nothing covered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GameWorld.get_enemy used to model_dump the typed Enemy template back into
a dict, so combat, loot, the view builder and the sim read it through
~40 .get("key", default) calls where a typo fails silently. It now
returns a deep model_copy (scaling and a fight both mutate it, and the
shared template must never leak), and every consumer uses attributes.
difficulty.scale_enemy now scales models; before, it returned anything
that was not a dict unchanged, which would have silently switched
difficulty scaling off.
Behavior is unchanged: every enemy YAML sets experience and
description, so no model default changes a value, and seeded
sim.playtest output is identical across all modes and classes. Two
deliberate deletions, both no-ops at runtime:
- the 3x boss-XP branch in combat and sim keyed on boss_room/boss_enemy,
fields no enemy has ever had, so it never fired
- the unused build_enemy_view / EnemyView
The one visible change: an enemy whose YAML has an empty `dialogue:`
no longer prints "Name: " with nothing after it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NPCs were the last content type still handed to the runtime as dicts (load_npc_data dumped the validated models straight back). They now stay NPC models end to end; the talk command, auto-dialogue, look and room views read attributes. on_talk becomes a declared field — five NPCs use it — so reading it no longer depends on an undeclared extra key. Verified by talking to all 11 NPCs headlessly: every name and on_talk message still renders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… saves
Items were the last content type dumped back to dicts at the world
boundary, and the inventory stored those dicts, so ~130 reads across
commands, effects, combat, the placer, the view builder and the sim used
.get("key", default). Now:
- world.get_item and a single data_loader.load_item return private deep
copies of the typed template (load_item replaces the hand-rolled
load_weapon_data/load_consumable_data YAML readers; the weapon one
handed out a shared cached dict)
- Player.inventory holds Item models; to_dict/from_dict convert at the
save boundary with model_dump(exclude_unset=True), so the save JSON is
unchanged and existing saves load
- the Item schema declares every field the code reads, with the same
defaults the .get calls used; keys no code reads stay undeclared extras
Behavior is unchanged, verified three ways: a seeded transcript that runs
every verb on all 41 items for each class (plus combat item use and the
saved inventory JSON) is byte-identical before and after; seeded
sim.playtest and new-run placement output are identical; and a save
written by the old code loads into typed items.
Also removes ItemEffects.equip_weapon: nothing called it, and it passed
Player.equip_weapon an argument it does not take.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Starts typing src/ in place instead of waiting on a rewrite: difficulty, events, game_output, game_states, rarity, rng, room_paths and state_manager now pass mypy --strict and are listed in pyproject, so CI keeps them typed. Changes are annotations only (plus bool() around enum comparisons whose __eq__ returns Any). Config notes, because the obvious setup silently checks nothing: - explicit_package_bases names src files src.<module>, matching imports (src/ has no __init__.py) - a module-specific override sets follow_imports=normal for the typed modules; without it the src.* skip rule turns their imports of each other (GameState, EventBus) into Any and every call type-checks - utils.* is followed silently so debug_log's new signature is seen Verified by injecting a bad set_state/emit_event call: mypy now flags both, where before it reported success. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
engine.validate proved every id exists, not that content says what the author meant. Two new checks run in validate (and so in CI): - find_lock_problems: every locked room names a key, that item is a key, the key's unlocks lists the room, and no key claims a room guarded by a different key or by nothing - find_unread_fields: the schema now declares every content field the game reads, so any undeclared field is authored intent the game ignores (the "phantom reward" bug class). Known unimplemented fields are listed in UNIMPLEMENTED_FIELDS; any other undeclared field fails, and so does an allowlist entry no content uses any more To make "undeclared" mean "unread", Room gains enemy_tier, enemy_count and discovery_requirement, and Ability/Attack gain healing and enemy_damage_reduction; code already read all five through getattr or raw dicts. Tests pin each rule against deliberately broken content, the CLI wiring, and art coverage: every enemy, NPC and class has a sprite and every room zone a backdrop, so the scene never falls back to placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The TUI kept the player, inventory, room and combat views as plain
dicts rebuilt from event payloads, and the panels read them with
.get("key", default). Event payloads stay to_dict() dicts (engine
emitters and tests are untouched); the UI now rebuilds typed views at
the boundary with new from_dict constructors and passes those to the
stats panel, inventory panel and scene view. view_models and the two
sidebar panels join the strict-mypy set.
One aliasing dependency made explicit: the scene view used to zero
enemy_health on the combat dict the UI also held, so the UI saw the
defeat by accident. Views are frozen now, so both sides replace() it.
Verified with a scripted Pilot session (title, pickers, play commands,
combat turns, finale, death and game over): all 26 exported
screenshots are identical before and after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
textual_ui.py still rendered the title screen and main menu, paced the victory finale, built the combat panel text and the game-over card itself. Each now lives in its own module the app delegates to: - title_menu.TitleMenu owns the intro/menu state (typewriter, highlight, selection) and key handling; the engine's _display_title_screen hook stays on the app as a one-line delegate - endings.FinaleReveal owns the reveal queue and timers; build_recap and game_over_text are plain functions - combat_log.render_combat_output / hotkey_display are pure functions over the typed views, so they join the strict-mypy set with endings Code and text were moved by extraction, not retyped: every string constant in the moved methods is byte-identical, and the scripted Pilot session's 26 screenshots match the previous commit. New tests drive the menu keys, the typewriter skip, and the finale beats/skip/reduce-motion paths the screenshot run does not reach. These stay content in the main output panel rather than becoming Textual Screens; switching to Screens changes what the player sees and wants a hands-on play-check first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
QuitConfirmScreen defined its own _render(), which silently overrode Textual's Widget._render() -- the method that returns what the widget draws. Ours returned None, so any full repaint of the screen raised "'NoneType' object has no attribute 'render_strips'" and killed the app. It showed up after fleeing the tutorial fight, but any repaint could trigger it. Renamed to _draw(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re Echo Playtest feedback on the tutorial: - Equipping the first weapon opened a fight with no instructions. The step-4 hint was written before the fight started and the battle panel replaced it; it now arrives after, inside the combat log. The battle panel's opening view (attack list + selection-mode help) also never showed, because it required an empty log and the "combat initiated" line is logged immediately; it now shows until the first blow. - Fleeing the tutorial fight removed the enemy until the player left and re-entered the room, while Echo kept saying "press 1" -- a dead end. During the tutorial the scripted enemy now stays put and Echo explains that flee works and `attack` restarts the fight (new step4_fled hint); stray commands at that point repeat it. - After `take`, Echo spoke before the room re-list; the list now comes first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nput
Tutorial hints scrolled through the output with everything else, where
the next room listing or the battle panel could push them out of sight
before the player read them. TutorialCoach now emits a TUTORIAL_HINT
event instead of writing to the output: the TUI shows it in a new
EchoPanel ("ECHO · step N of 6") pinned above the input that stays put,
survives combat, and hides when the tutorial ends; the closing summary
still lands in the output as a record. The headless UI appends the text
to its log, so tests and scripted runs see the same order as before.
Step 1 and the skip summary were shown before start_game(), whose
GAME_STARTED reset wiped them; they now follow it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the screen holds a single prompt (name entry, the tutorial offer),
the text sat in the top-left corner: the centering rule never applied
because the text widget only sized to its content. Selection mode now
centers a bordered card (84 columns, green border, centered text) in the
output panel.
The prompts themselves are reflowed for it: the name prompt loses its
hard line breaks at ~90 columns and ends on a clear question with a
"type it below" hint; the tutorial offer shows its two answers as
styled choices ("yes — show me around", "skip — I know my way").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts at
Playtest: Echo said "See those directories?" in /home — but /home has no
subdirectories, so `ls` there never shows a Directories section and the
player had nothing to look at; they just typed the example. The tutorial
also never covered ps, which in /mnt reveals a hidden chamber.
The second half now teaches where you are before telling you where to go
(9 steps, was 6):
5. after the fight: ps -- what's running; odd ones hide secrets
6. after ps: pwd -- "you're in /home"
7. /home is a dead end: cd .. -- go up a level
8. somewhere new: ls
9. ls lists directories: cd into one (example = a real listed name)
When step 9 fires, the TUI flashes the Directories section of that ls
output three times and leaves it highlighted (steady with reduce-motion).
The hint carries a "highlight" field; hints can now use {location} and
{example_dir}. Progression moved into TutorialCoach (after_ls, after_ps,
after_pwd, after_move) so commands just report what happened. If the
player wanders or skips ahead, each step adapts: pwd somewhere that has
directories skips "cd ..", and ls in a dead end repeats the way up.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
452 -> 354 tests, same coverage of real behaviour: - One test per table instead of one per row: legacy nav aliases and path arithmetic (test_nav), and the per-hint "has text" / "renders without placeholders" checks. Failures now list every bad entry at once. - Dropped low-value tests: GameOutput.error (a three-line method), two of three session-isolation tests (kept the leaked-session scenario, the real bug), two of three fake-timer finale tests (kept skip). - Tutorial tests assert which step fired and the tutorial state, not Echo's text, so copy edits stop breaking them. The headless UI now records each hint (s.ui.hints) for that. The prescribed-commands test follows the current tutorial path (ps, pwd, cd .., ls, cd bin). Checked the rewritten tests still bite: reintroducing the flee dead end or the take/Echo ordering bug each fails its test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ommand names
Playtest screenshot: after "skip", the tutorial offer stayed on screen
with the quick-reference summary glued underneath, and the summary read
"type use instead".
- start_game() never cleared the output; the next Echo line used to
replace it, which stopped once hints moved to the Echo panel. It now
clears the panel and opens on the room (name + description); the skip
summary follows after a blank line.
- "[item]"-style placeholders are markup tags to the renderer, which
silently drops them -- in four tutorial lines, the help screen, eight
commands' usage errors, the combat hint screen and the enemy-blocking
message. They are now "<item>" etc. ps shows kernel threads the Unix
way, in brackets, so those are escaped ("\[kthreadd]") -- they had been
invisible. `man` printed "[dim]Try: man ls[/dim]" literally (markup in
a plain Text); it is dimmed now.
- A command word at a name prompt ("quit", "ls", "help", "yes", ...) is
refused and asked again, instead of becoming the player's name.
Tests: the start clears a stale prompt; no tutorial line or command
output loses a bracketed word; command words are refused. The old
combat-hint test that pinned the broken "use [item]" now checks
"use <item>".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The tutorial never had the player read a file, so the checkpoint loop (cat a lore file -> memory restored -> autosave) went untaught. Two new steps after the fight: ls -a reveals bash_profile (item hidden: true now actually hides files from plain ls — it was declared in content but unread), cat fires the identity memory restore, and Echo names it as the checkpoint you resume from on death. ps/pwd/nav follow as before; 11 steps total. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Skip sets tutorial_state.completed but never equipped_weapon, and the equip hook only checked the latter: a skipping player's first equip still spawned the tutorial enemy and opened combat — with the step4 explanation suppressed, since hints no-op once completed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two game-over screen bugs from the architecture review (F4a, F4b), both from the UI guessing what a GAME_OVER meant: - On death the room scene reappeared for ~2.6s before the drain. The UI hears COMBAT_ENDED before the engine sets GAME_OVER, so the is_in_game_over() guard never fired, and the GAME_OVER reset called end_battle() again. The UI now reads the event's own data: defeat keeps the battle on screen and starts play_death immediately. - F5 and the post-win "new game"/"restore" choices showed the GAME OVER card 2.6s later over whatever came next. GAME_OVER now carries reason "defeat" or "restart"; only a defeat shows the card, and a restart returns to the title screen. - The death card said "Unknown Sysadmin": the reset cleared the player view it reads. The name is kept for the card. Tests drive the real engine and TUI together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The event bus logs and swallows a listener's exception, and ViewBuilder returns a placeholder view when a build fails. Both are right for the game (a panel degrades instead of crashing) and wrong for tests, where a crashed handler left a log line and a green test. EventBus takes strict=True (class default strict_by_default) and ViewBuilder has raise_errors; tests/conftest.py turns both on for every test. The game keeps the forgiving defaults. The full suite still passes strict, so no crash was hiding today; test_strict_mode pins that the switches stay wired. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Loading a save quietly changed the run (review findings F1, F2): - Player.to_dict never wrote equipped_armor or status_effects, so a load unequipped your armor (mitigation 0) and dropped active effects. Armor is re-equipped on load so mitigation is recomputed from the item rather than trusted from the file. - The run's difficulty was not saved, and mounting the UI reset it from user settings, so a Hard run reloaded as Medium. The save now records it and both load paths restore it; the UI no longer touches it. Save format is v4. v3 saves still load: medium difficulty, no armor, no effects. Also: everything the game opens (content, saves, logs, settings) is relative to the working directory, and some of it at import time. Launched from another directory the game could not find its content and left empty data/ and saves/ folders behind. main.py now moves to the repo before importing the game. test_every_player_field_survives_a_round_trip walks every Player attribute rather than listing them, so the next field added without save support fails there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hint-text conflict: kept the new ls -a / cat checkpoint steps, adopted dev's 'use <item>' spelling (bracketed words now survive markup). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(tutorial): teach ls -a and cat via the memory checkpoint
fix(tutorial): don't ambush skipping players with the scripted fight
Choosing "r" after a win restored the save but rebuilt the command handler without its event subscriptions (review finding F3): walking into a room with an enemy started no fight, NPCs said nothing after combat and tutorial combat hints stopped. It also never told the UI the run had started (no GAME_STARTED / ROOM_ENTERED) and left the state alone. Both load paths now go through _enter_loaded_run(), so neither can skip a step. Its failure fallback called ui.display_message, which the Textual UI did not implement (UIProtocol declares it); it does now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ins it - After a revive: "That Legacy Backup is spent and gone from your inventory", then how many are left or that the next fall ends the run. The three revive sites share Player.revive_text. - `use legacy_backup` in a fight gives the same "works automatically" answer as outside one, instead of "cannot be used in combat". Other non-combat items now say "try it after the battle". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tab after cd suggested nothing: it read the room as a dict, the typed Room model raised, and the error was swallowed. It now asks GameWorld.visible_exits (also used by cd's "did you mean"), so a hidden room stays out until ls -a finds it and a locked door until its key reveals it. Doors complete as their real path (/usr, not the old /usr/lib/arcane alias), which retires the room-alias plumbing the suggester alone used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In /bin it printed "Found: /dev/null_void … cd dev_null_void" before the Sudo Privileges Badge had revealed the door, pointing new players at a directory they could neither see nor enter. /dev now exists for find only once its door is visible, from any room (like a real absolute path), and the hint is the real path: cd /dev. REFERENCE.md no longer lists find as a way to reveal a hidden directory (it never was one). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- data/hidden_rooms_guide.txt: no code loads it, and it was stale (it still called /etc a hidden room reached by ls -a). - data/difficulty.proposed.yaml: sim/tune.py's scratch proposal for a human to review; now gitignored so a tuning run can't commit it. - data_loader.load_abilities_data / get_abilities_for_class: never called. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- health_packet keeps its heal in combat_effects.player_heal, so the inventory view's healing was always empty. ViewBuilder._heal_info reads heals in combat's order; heal-over-time shows its real total and turns (Stable Cache: +39 HP over 3 turns). New view field healTurns. - AttackView gains kind (attacks.yml type); the fight menu shows ⚔ / ✨ / 🌿 instead of the same bullet on every row. - CHANGELOG entries for this batch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ave on The class sprite appeared only in battle, and equipped gear only as "⚡EQUIPPED" tags in a long inventory. The new panel shows the sprite (idle bob, off with reduce motion) beside the equipped weapon (+DMG) and armor (-% dmg taken); an empty slot says "type equip <name>". It redraws with every Stats/Inventory refresh. Sidebar layout: the inventory now takes the leftover height and scrolls, so Stats and Character are never clipped on a long inventory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Filling the leftover height (b13d08a) kept the Character panel whole but left a huge empty inventory box early in a run. The inventory now sizes to its items up to 35% of the sidebar and scrolls past that; the sidebar itself scrolls on a very short terminal instead of clipping a panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Playtest: a player cleared the map and reached the boss at 26/160 HP with
no way to recover. A capture now refills HP ("✚ +N HP — the recovered
memory has you fully restored"), so the flag hunt is the way back to
health.
The sim models it too. Its deaths all came from attrition across fights,
so it now wins ~100% in every mode (was 94/80/65% avg); any partial
refill does the same. difficulty.yaml carries a dated note that its
measured rates are stale; re-tuning is a separate decision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saves from before the keys chain still had chmod_key lying in /var, so capturing the /var flag later handed out a second one. Loading now clears flag-granted keys from floors (GameWorld.flag_granted_keys, which the placer also uses). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Typing equip, take, cd, cat… in a fight printed "bash: …: command not found", which reads like a typo to someone new to the shell. They now get "<cmd> works once the fight is over. Right now: press 1-9 to attack, 0 to flee, or type use <item>." Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…balance Adds the player-visible changes since v0.1-playtest that weren't listed (item icons, ✓ marks, room art, F12 logs, loot and save fixes, tutorial and launcher fixes), today's capture refill, stray-key and mid-fight command messages, and replaces the old win-rate targets, which the capture refill made stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d scrolling" This reverts commit 16b5961: the inventory fills the leftover sidebar height again, as the user prefers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sprite grows from 18px to 26px (13 rows). Beside it, a narrow sidebar
cut weapon names short ("Privile…"), so the panel now picks its layout
by width: gear beside the sprite when it has 26 columns, below it
otherwise, with names whole either way. Re-lays out on resize.
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>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…/legacy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… when full Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…un cleared Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ooser Ctrl+Q / Ctrl+C / quit mid-fight used to print 'command not found'. 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>
A valid-JSON but malformed run file (or a failed old-save migration) is skipped instead of crashing LOAD GAME and NEW GAME; a run that lists but fails to load re-opens the picker; saved class/difficulty text is escaped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shared mgr/clock fixtures in conftest (the isolated save_manager instead of per-file managers patched into two modules), no real sleep in the picker Pilot test, duplicate tests folded (v6 envelope + round trip into test_saves, save_cap into the one-file test, main-menu-after-death, the double migration check), palette tests parametrized, a combat fixture, local imports hoisted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hape) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All five retaken at a full-screen terminal size (240x64, ~1.84:1), the explore shot through the real start flow, with Fira Code embedded so GitHub renders the logo and box art like a terminal. New paths under assets/screenshots/ (sidesteps GitHub's image cache); an HTML table with equal image widths replaces the markdown table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG dated; README points players at the Releases zip; REFERENCE loses its Development section; .gitattributes export-ignores dev tooling so release/source zips contain only the game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release v0.2.0 to main. Full notes: the v0.2.0 section of CHANGELOG.md.
Highlights:
menucommand, game over r/m/qPlayers get a game-only zip from the v0.2.0 release (.gitattributes export-ignores tests/, sim/, CI and dev tooling).
🤖 Generated with Claude Code