Grey out menu options the game would refuse, with the reason - #168
Merged
Conversation
An option the player had earned but couldn't use right now looked exactly
like one they could: the only way to find out you were too tired to fish
was to pick "Fish" and be told so afterwards. Every front-end now shows
those options as unpickable and says why on the row itself.
showOptions takes an optional {optionNumber: reason} mapping, converted
once in BaseUserInterface into a list parallel to the options. The web
front-end publishes it to the browser, which renders a greyed-out,
disabled button carrying the reason (and ignores its number key); the
console and pygame front-ends tag the same rows, skip them, and name the
blocker instead of "Try again!".
Marked so far: fishing without the energy for an hour, selling with an
empty hold, gear that's unaffordable or already maxed out, drinking or
buying a boat/property/home you can't pay for, repairing or upgrading a
hull whose bill you can't cover, depositing with an empty purse,
withdrawing from an empty account, and guessing a dice face before
staking anything.
The game's own post-choice guards are left untouched, so nothing depends
on a front-end honouring this. Marking every option is treated as a bug
and dropped rather than leaving a menu that accepts nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
7 tasks
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.
Summary
An option the player had earned but couldn't use right now looked exactly like one they could. The only way to find out you were too tired to fish was to pick Fish and be told so afterwards — the same for buying gear you can't afford, withdrawing from an empty account, or guessing a dice face before staking anything.
Every front-end now shows those options as unpickable and says why on the row itself.
showOptionstakes an optional{optionNumber: reason}mapping. Call sites key by 1-based option number because these menus are built by appending —len(optionList)right after an append is always the row just added, so the numbers can't drift as options appear and disappear with progression.BaseUserInterface.unavailableReasons()converts it once into a list parallel to the options.web/client.js, shared by the server-backed and Pyodide front-ends): a greyed-out,disabledbutton carrying the reason in its own span, plus a tooltip; its number key is inert too.(unavailable: ...), and picking it names the blocker instead of"Try again!".Marked so far: fishing without the energy for an hour; selling with an empty hold; bait/rod that's unaffordable or already maxed out; drinking, buying a boat, a property or a home rung you can't pay for; repairing or upgrading a hull whose bill you can't cover; depositing with an empty purse; withdrawing from an empty account; guessing a dice face before placing a bet.
Safety: the game's own post-choice guards are left untouched, so nothing depends on a front-end honouring this.
canAfford/hasEnergyare used as the predicates, so operator mode never sees a greyed-out button. Marking every option is treated as a call-site bug and dropped rather than leaving a menu that accepts nothing, and an out-of-range option number raises with a message naming the fix.Two magic numbers picked up names along the way, since the new gate and the existing charge had to agree:
docks.FISHING_ENERGY_COSTandtavern.DRINK_COST.Test plan
python3 -m compileall -q src testsSDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy python3 -m pytest --verbose -vv --cov=src --cov-report=term-missing --cov-report=xml:cov.xml— 770 passed, 97% coverage (up from 731)black src tests+autoflakeperformat.shtests/ui/test_baseUserInterface.py), the console rows and refusal message (tests/ui/test_userInterface.py), pygame row text/navigation/keyboard (tests/ui/test_pygameUserInterface.py), and the web screen contract (tests/ui/test_webUserInterface.py).tests/web/test_clientParity.pyasserts the shared browser client actually consumes the contract, so it can't be sent server-side and ignored client-side.web/client.jsagainst a DOM stub — an options screen withunavailable: ["needs 10 energy - sleep at home", null, null]renders[1] Fish — needs 10 energy - sleep at homeasclass="unavailable",disabled=true, no click handler, while options 2 and 3 stay live. Pressing1sends nothing; pressing2sends"2".examples/web_app.pyand confirmedGET /statepublishes the newunavailablelist alongsideoptions.Player/Stats/TimeServicefields changed, so noschemas/*.jsonor*JsonReaderWriterupdates are needed.README.mdupdated where the staged reveal is described.🤖 Generated with Claude Code
drafted by Claude on behalf of Daniel Stephenson