Skip to content

Show the fleet, voyage and export record on the career ledger - #183

Merged
dmccoystephenson merged 5 commits into
mainfrom
feature/career-ledger-totals
Aug 23, 2026
Merged

Show the fleet, voyage and export record on the career ledger#183
dmccoystephenson merged 5 commits into
mainfrom
feature/career-ledger-totals

Conversation

@dmccoystephenson

Copy link
Copy Markdown
Member

Summary

Thirteen lifetime totals were being tracked on Stats and written into every save file, while two of them were never produced and eleven were never shown. This PR closes both ends of that gap in the one subsystem.

The two dead totals are now recorded. totalHaulingContracts and totalTransportRuns had no assignment anywhere in src/ — permanently zero — even though runDailyProduction already routes a piracy boat through _runPiracyDay (which fills raidDays) and a freight or passenger boat through _runHonestDay (which recorded nothing role-specific). _runHonestDay now counts the day under its own role, so freight and passenger work leave the same record piracy already did. Populating the fields was preferred over deleting them precisely because deletion would have meant touching schemas/stats.json and a *JsonReaderWriter — save-file-compatibility-sensitive paths this change deliberately avoids.

The ledger now shows what the boats did. Home._statsLines() — reached via See Stats, and reused by the retirement summary — is the only career ledger in the game, and it named gambling and bank interest while leaving exporting, captaining and piracy invisible. A Fleet: block and an Exports: block have been added, plus the drunken losses beside the existing gambling line. Four milestones are thresholds on totals that were previously unshown (Scourge of the Coast, Coastal Trader, A Fleet in Truth, Old Hand at the Wheel), so progress toward them was readable only as an unticked box.

Both blocks are gated on the lifetime totals rather than on what the player owns today, so a career that ended in a sold-off fleet still shows the fleet's record; a zero entry is dropped and a block with nothing left in it is omitted entirely, which is how the existing business and investment blocks behave. A brand-new game's ledger is therefore unchanged.

Two money lines now keep their cents. An export market pays a fractional multiplier (1.2 / 1.5 / 2.0), so gross — and with it totalMoneyMade and totalMoneyFromExports — is a float. %d was silently truncating it, leaving the ledger permanently disagreeing with the $%.2f the status header shows. The other %d money lines in the ledger were each traced to integer-only sources and left alone.

Test plan

  • python3 -m pytest under the dummy SDL drivers: 843 passed, up from 829 on main (14 new tests, 0 failures)
  • python3 -m compileall -q src clean
  • Each new day-counting test confirmed to fail with the _runHonestDay change stashed, and to pass with it restored
  • Each new ledger test confirmed to fail with the _statsLines change stashed, and to pass with it restored
  • black run against the four changed files only; no unrelated file was reformatted
  • No persisted field added, removed or retyped — schemas/*.json and both *JsonReaderWriters are untouched, so existing saves load exactly as before

Closes #180
Closes #181
Closes #182

Deferred this cycle

No other issue was open at triage; all three closed here were filed during this cycle's scan. One candidate examined and rejected rather than filed: the tavern's Current Bet: $%d looked like the same float-truncation bug, but changeBet forces the bet integral with self.amount = int(amount) (src/location/tavern.py:286), so the display is correct as written.


This PR description was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).


drafted by Claude on behalf of Daniel Stephenson

dmccoystephenson and others added 5 commits August 23, 2026 01:08
totalHaulingContracts and totalTransportRuns were written to every save
file and never incremented, so both were permanently zero while piracy
had its raidDays recorded. _runHonestDay now counts the day under its
own role, the way _runPiracyDay already does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Eleven lifetime totals were tracked and persisted but shown nowhere, so
progress toward the plunder, export, boat-count and voyage milestones was
invisible behind an unticked box. See Stats (and the retirement summary
that reuses it) now carries a Fleet block and an Exports block, each
gated on the lifetime totals rather than on what the player owns today,
plus the drunken losses beside the gambling line.

Total Money Made and the export gross are rendered with cents: an export
market pays a fractional multiplier, so both are floats and %d was
quietly truncating them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new Fleet block is gated on the lifetime totals, but the business
block above it was gated on owning a boat today - so a player who sold
off their fleet saw its takings without the wages those takings were
earned against. Gate the business block on ever having run a business
instead. A player who never bought a boat still sees neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Restores the source half of the previous commit, which was reverted by a
stray checkout while verifying that its test fails without it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The See Stats screen had never been documented, and it now carries the
fleet and export record as well as the milestone checklist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dmccoystephenson

Copy link
Copy Markdown
Member Author

Self-review

Scored adversarially against the diff and against command output, not against intent.

Universal rubric

Repo-specific rubric

  • Schema-sync: PASS (vacuously) — no persisted field was added, removed or retyped, and no *JsonReaderWriter was touched. Existing saves load exactly as before, which is the whole reason totalHaulingContracts and totalTransportRuns are persisted but never incremented #181 was resolved by populating the two dead fields rather than deleting them.
  • Money-format: PASStotalMoneyMade and totalMoneyFromExports are rendered %.2f; both were traced to the float gross in export.py. The other money lines were each traced to an integer-only source and deliberately left on %d: moneyMadeFromInterest (int(math.ceil(...))), moneyLostFromGambling (bet forced integral by int(amount)), totalRentPaid, totalWagesPaid, totalRentalIncome, totalPlunder and totalMoneyFromVoyages (all integer sources — voyage takings come from random.randint or an explicit int(...)).
  • Deterministic-tests: PASS — the one new test whose path touches randomness, test_fishing_and_piracy_days_are_not_counted_as_honest_work, patches both random.random and random.randint so no raid damage or fatality can perturb the assertion. The rest set stat values directly and roll no dice.
  • Headless-pygame: PASS, with the scope stated — no pygame code path was changed. The ledger does get up to twelve lines longer, and PygameUserInterface.showDialogue was read to confirm that case is already handled: _dialogueScrollBounds/_dialogueHint scroll a dialogue taller than the window, with a docstring naming "the stats and retirement screens" as the reason. The whole suite, including the pygame tests, runs under SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy.
  • camelCase: PASS_careerBlock, valueFormat, haulingDays, transportDays.

Findings raised and acted on during the review

  • src/location/home.py:223 — the Fleet block is gated on lifetime totals while the business block above it was gated on player.hasBoat, so a player who sold off their fleet would have been shown its takings without the wages those takings were earned against. Fixed in 3ae5a1a, with a regression test confirmed to fail without it.

Judgment calls left standing, disclosed rather than silently taken

  • _careerBlock drops a zero entry, so a captain with a clean record sees no Voyages Foundered: 0. This is deliberate and matches how Lifetime Rent Paid already behaves, but it does mean the ledger cannot distinguish "never foundered" from "the game does not track that". Reversing it would mean showing zeroes for every role the player has never touched, which is the wall-of-zeroes the block gating exists to avoid.
  • Money From Boat Work renders totalMoneyFromVoyages, which mixes the fleet's own working days with voyages the player captained — that is genuinely what the stat holds (see the comment on it in stats.Stats), so the label was chosen to span both rather than implying either one alone. Splitting the stat would be a persistence change and is out of scope here.

This comment was drafted during a Gardener session (https://github.com/Stephenson-Software/gardener).


drafted by Claude on behalf of Daniel Stephenson

@dmccoystephenson
dmccoystephenson merged commit eef52ee into main Aug 23, 2026
1 check passed
@dmccoystephenson
dmccoystephenson deleted the feature/career-ledger-totals branch August 23, 2026 07:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant