test: assert what every voyage-event outcome does to the voyage - #174
Conversation
The two existing sweeps over adventures.EVENTS prove every outcome branch is reachable and nothing about what any of them does - both assert only that resolveChoice returns a truthy string. Deleting _merchantShadow's $400-900 payout left the whole suite green. Add a table naming, for all 29 outcome handlers and each of the 38 branches they can take, which part of the voyage state moves and by how much - with random.randint pinned to each end of its documented range and random.choice pinned so the hand lost is a named one. A coverage guard fails when a choice is added to EVENTS without its effects being written down. Mutation-checked: deleting _merchantShadow's payout, swapping _leakShipwright's repair for a damage, paying _goodGroundsNets in coin, widening _patrolFight's range and losing the wrong hand in _merchantBoard are each caught now and were each green before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Berths are capped by tier, so len(VILLAGERS) and crewAboard() only agree while the roster fits. Deriving the supplies-eaten expectations from the voyage keeps them honest if a thirteenth villager is ever added. Also unpack createVoyage in the boarding-odds test the way every other test in the file does, rather than indexing the tuple. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ReviewReviewed adversarially against the repo's own conventions, with CI green on the head commit ( Rubric
Repo-specific rubric
Findings addressed during reviewTwo were found and fixed in
Observations not blocking
Merge readinessTest-only, one file, no production change, CI green, and the gap reported in #156 demonstrably closed by measurement rather than by inspection. No path on the do-not-auto-merge list is touched: nothing under This review was performed and posted during a Gardener session (https://github.com/Stephenson-Software/gardener). drafted by Claude on behalf of Daniel Stephenson |
Summary
src/business/adventures.pywere covered only by narration assertions.test_every_choice_returns_narrationandtest_every_outcome_branch_is_reachableboth assert nothing beyondresolveChoicereturning a truthy string, so every payout, repair, hull hit and crew loss in the event table was unverified. As reported in test: 30 voyage-event handlers are asserted only to return a non-empty string, so their payouts are unverified #156, deleting_merchantShadow's $400-900 payout left the whole suite green.tests/business/test_adventures.py. For all 29 outcome handlers, and each of the 38 branches they can take, the table names which part of the voyage state should move and by how much.random.randintis pinned to each end of the handler's documented range in turn, so both bounds are asserted, andrandom.choiceis pinned so the hand that is lost is a named one that can be checked.test_every_outcome_in_the_event_table_has_a_state_spec, compares the handlers reachable fromEVENTSagainst the table in both directions. A choice added toEVENTSwithout its effects being written down fails there, which is what keeps the table from going stale._merchantBoard's success chance scales with the number of hands aboard, and a roll that a lone hand cannot clear is now shown to be cleared by a full boat.The structural alternative floated in the issue — having each handler return its state delta alongside its narration — was not taken. It would change production code under what is a coverage gap, and the table gets the same table-driven assertions without touching
src/.Test plan
python3 -m pytest(withSDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy): 806 passed, up from 803.python3 -m compileall -q src testsclean.black --check tests/business/test_adventures.pyclean.src/business/adventures.pyand each was caught by the new tests, having been green before them:_merchantShadow's payout deleted (gain(voyage, money=0)) — the exact mutation reported in test: 30 voyage-event handlers are asserted only to return a non-empty string, so their payouts are unverified #156_leakShipwrightcallingdamagewhererepairwas meant_goodGroundsNetspaying in coin instead of fish_patrolFight's payout range widened at the low end_merchantBoardlosing the wrong handrandom; every roll in the new tests is patched.Scope
Test-only: one file, 386 added lines, no change under
src/. The soft scope ceiling counts non-test net LOC, which is zero here.Closes #156
Issue #153 (the web front-end showing "lost connection" instead of the ended screen) was deferred this cycle. It is a production change across
src/fishE.pyand both browser front-ends, and its own body records an unresolved design question —cleanup()shuts the socket down before the client's next 300 ms poll, so the fix needs a grace period or a client acknowledgement to be visible at all. That warrants a cycle of its own rather than being batched with an unrelated test expansion.This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).
drafted by Claude on behalf of Daniel Stephenson