Skip to content

Report the run's own score on the obituary screen - #138

Merged
dmccoystephenson merged 2 commits into
mainfrom
feature/obituary-run-score
Aug 27, 2026
Merged

Report the run's own score on the obituary screen#138
dmccoystephenson merged 2 commits into
mainfrom
feature/obituary-run-score

Conversation

@dmccoystephenson

Copy link
Copy Markdown
Member

Summary

  • formatObituaryLines() (src/progression/obituary.py) now reads the score key off the obituary record and reports it in the narrative line, so the end-of-run screen names the score of the run that just ended.
  • The score is read with a default of 0, so obituaries written before score was recorded still render rather than raising.
  • Both UIs render the end-of-run screen through this one formatter, so the graphical and text interfaces are covered by the same change — no UI asymmetry is introduced.
  • README's Scoring section now says where the run's score is reported once the run is over.

Before this change, the only score anywhere on the screen was the chronicle's Highest score ever, which is the lifetime maximum held in lifetimeStats — the all-time best was shown at the exact moment a run was summarised, with no way to see what the run just played had scored.

Rendered output, taken from the text UI after a run was ended with q:

== Obituary ==
The ophidian Jormungandr lived to level 1, reaching a length of 2 and a score of 18, surviving 21 ticks, before the player's own hand.

== Chronicle ==
Total runs: 1
...
Highest score ever: 18

Test plan

  • ./test.sh — 310 passed (306 before this branch); coverage for src/progression/obituary.py stays at 100%, and no touched module regressed.
  • Regression evidence gathered empirically, not by reasoning: with src/progression/obituary.py stashed, the four new tests plus the two updated ones fail (6 failed); with it restored, all 26 pass. Sentinel data is used so the run's score (4242) cannot be satisfied by the chronicle's lifetime best (9999).
  • python3 src/ophidian.py launched — no startup crash.
  • python3 src/ophidian.py --text-ui launched under a pty and driven to a run end — obituary renders as shown above.
  • python3 -m compileall src clean; black reports the three changed Python files unchanged.

New coverage:

  • test_format_obituary_lines_reports_the_score_of_the_run_that_just_ended
  • test_format_obituary_lines_defaults_score_for_records_predating_it
  • test_format_obituary_screen_distinguishes_run_score_from_lifetime_best
  • test_ending_a_run_shows_that_run_s_score_on_the_obituary_screen — end-to-end, from Ophidian.recordCurrentRun() through to the formatted screen

Closes #136

Issues deferred this cycle

Skip reasons are recorded here rather than as comments on each issue:

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 2 commits August 26, 2026 01:30
formatObituaryLines() read name, level, length, ticksSurvived and
causeOfDeath off the obituary record but never score, even though
SaveManager.recordRun() stores it and the function's own docstring lists
it. The only score anywhere on the end-of-run screen was the chronicle's
"Highest score ever", which is the lifetime maximum from lifetimeStats -
so a player was shown their all-time best at the exact moment a run was
summarised, with no way to see what the run just played scored, including
when that run was the one that set the best.

The narrative line now carries the run's score, read with a default of 0
so obituaries written before score was recorded still render. Both UIs
render through this one formatter, so the graphical and text interfaces
pick the change up together.

README's Scoring section says where the run's score is reported once the
run is over.

Closes #136

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new end-to-end test was inserted between two restart tests, splitting
a group that otherwise reads top to bottom. It belongs after them, since
it is about what an ended run reports rather than about restarting one.

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

Copy link
Copy Markdown
Member Author

Self-review rubric

  • Scope: PASS — four files are touched and each is needed for The obituary screen does not report the score of the run that just ended #136: the formatter, its unit tests, one end-to-end test, and the README section that documents where the score is reported. git diff --stat origin/main is 58 insertions / 2 deletions across 4 files, with 45 of those insertions being test code — far inside the scope ceiling. No unrelated reformatting rode along; black was invoked on the three changed Python files by name rather than through format.sh, which formats tree-wide.
  • Tests-new: PASS — no new public function was added, but the changed behaviour of formatObituaryLines() is exercised by three new unit tests (a scored record, a record with the key deleted, and an empty dict) plus one end-to-end test through Ophidian.recordCurrentRun().
  • Tests-fix: PASS — confirmed empirically, not by reasoning. With src/progression/obituary.py stashed, pytest tests/progression/test_obituary.py tests/test_ophidian_run_lifecycle.py reports 6 failed, 20 passed; with it restored, 26 passed. The end-to-end test seeds highestScore to 9999 against a run score of 4242 so the assertion cannot be satisfied by the chronicle's line instead of the obituary's.
  • Sibling structure: FAIL, then fixed — the end-to-end test was first placed between test_restart_records_the_run_before_reinitializing_the_board and test_restart_still_reinitializes_and_signals_restart, splitting the restart cluster that file groups at its end. It was moved below that cluster in 0b803a8 and the item re-scored.
  • Sibling renames: no signal this cycle — nothing was renamed.
  • Docs: PASS — README is the sole documentation source of truth here. Its Scoring section now states that the run's score is reported in the obituary; its Controls table and Libraries section are untouched by this change, and no CLI flag, keybinding or dependency was added.
  • Issue resolution: PASSThe obituary screen does not report the score of the run that just ended #136 names formatObituaryLines() and asks for the score to be read from the record's score key with a default for older records. Both are done, and the issue's observation that one formatter covers both UIs was verified against src/ophidian.py:303, :440 and :458.
  • UI parity: PASS — no asymmetry is possible: printObituaryToConsole(), renderCollisionFrame() and renderObituaryScreen() all render through formatObituaryScreen(), so the graphical and text interfaces pick the change up from one edit. The text UI was driven to a run end under a pty and observed printing ...reaching a length of 2 and a score of 18, surviving 21 ticks....
  • No stray artifacts staged: PASSgit status --porcelain after staging listed only the four intended paths. The save.json written by the manual UI launches is gitignored and was removed afterwards; no __pycache__, .pyc, cov.xml or .claude/ path was staged.
  • Manual validation: PASSpytest --cov=src reports 310 passed (306 on main), zero failures. Coverage of src/progression/obituary.py stays at 100% and no touched module regressed. Both UIs were launched: python3 src/ophidian.py under a dummy SDL driver ran without a startup crash, and python3 src/ophidian.py --text-ui ran under a pty and rendered the obituary shown above. Note that this repository has no CI, so this local run is the whole anchor.

Out-of-diff observation

The graphical UI clips this line — including before this change. renderObituaryScreen() passes each formatted line to Graphik.drawText(), which renders the whole string at freesansbold.ttf size 18 and centers it without wrapping or shrinking. Measured with that font, the narrative sentence occupies 1160 px in a 500 px window (Config.displayWidth), so roughly 330 px is cut from each side; the same sentence without a score already measured 1007 px, so the over-width condition is pre-existing rather than introduced here. The score itself was measured to land at x=299..394 and is therefore visible; what is clipped is the ophidian's name at the start and the cause of death at the end.

Wrapping was deliberately left out of this PR: it is a rendering concern in src/ophidian.py rather than a formatting one, it must not be pushed into formatObituaryScreen() (the text UI shares that function and has no pixel width), and folding it in would turn a targeted fix into an unrelated second change. It is filed as #139 instead.

src/progression/obituary.py:45 — the score default is 0 rather than a -/unknown marker, so a pre-score obituary is indistinguishable from a genuine zero-score run. That was judged acceptable because a run that ends before any food is eaten legitimately scores 0, and SaveManager.recordRun() has written the key since the record shape was introduced, so the defaulted path is reachable only from a hand-edited or truncated save.

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


drafted by Claude on behalf of Daniel Stephenson

@dmccoystephenson
dmccoystephenson merged commit 412bb4b into main Aug 27, 2026
@dmccoystephenson
dmccoystephenson deleted the feature/obituary-run-score branch August 27, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The obituary screen does not report the score of the run that just ended

1 participant