Skip to content

UI parity for the restart frame, a speed-boost HUD line, and setup docs - #118

Merged
dmccoystephenson merged 2 commits into
mainfrom
feature/ui-parity-and-setup-docs
Jul 29, 2026
Merged

dmccoystephenson merged 2 commits into
mainfrom
feature/ui-parity-and-setup-docs

Conversation

@dmccoystephenson

@dmccoystephenson dmccoystephenson commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

Summary

Test plan

  • python -m pytest — 161 passed (was 150; 11 new tests)
  • python -m compileall src clean
  • Text UI restart frame: renders without moving, then moves on the next frame (tests/test_ophidian_run_lifecycle.py)
  • Pygame restart frame: same behaviour, plus a test that events queued behind the restart key still land (tests/rendering/test_pygame_run_loop.py)
  • getSpeedBoostRemainingSeconds() — None when idle, counts down while active, clamped at 0 once expired (tests/test_ophidian_food_types.py)
  • Both HUDs render/omit the boost line, and the pygame line moves up when no upgrades are owned (tests/rendering/test_hud_and_banner.py, tests/textui/test_textrenderer.py)
  • README Controls table re-checked against handleKeyDownEvent; --text-ui is still the only argparse flag

Closes #117
Closes #114
Closes #115


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

…ent setup

Closes #117, #114, #115.

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

dmccoystephenson commented Jul 29, 2026 •

Copy link
Copy Markdown
Member Author

Self-review (posted as a comment — the reviews API isn't available in this environment; inline findings are folded in below as path:line — finding).

Overall

The three changes hang together: two of them are the recurring text-vs-graphical drift problem, and the third is the setup gap a new contributor hits before they can run either UI. Behaviour of the sentinel is now stated once ("restart" = skip this iteration's movement step) and pinned by a regression test per loop. No new UI/gameplay coupling: getSpeedBoostRemainingSeconds() returns a number and each renderer formats it, matching the renderMessage precedent from PR #95's decoupling feedback.

Three findings, two of which are being fixed.

Findings

src/ophidian.py:265 — if secondsRemaining: leans on truthiness to cover two different cases. It suppresses the line both when the accessor returns None (no boost) and when it returns exactly 0.0 (boost expired but updateSpeedBoost() hasn't cleared it yet, which can happen for one frame since the accessor clamps with max(0.0, ...)). Both suppressions are intended, but a reader can't tell the 0.0 case was deliberate rather than an oversight — and a future change to the clamp would silently alter what's drawn. Worth making explicit. Will fix.

src/textui/textrenderer.py:118 — same truthiness check, and the docstring above it only documents the None case. The docstring says "None when no boost is running", which under-describes what the guard actually does. Will fix alongside the pygame one so the two renderers keep agreeing.

README.md:5 — "Python 3.8 or newer" is a floor nobody has actually verified. The source was checked for syntax that would raise it (no match, no walrus, no PEP 585/604 builtin generics outside src/lib/), so 3.8 is plausible, but there's no CI matrix and the only version this was exercised on is 3.10. Stating the tested version alongside the floor is more honest than an unqualified claim. Will fix.

Checked and clear

  • moveSelectedSnakePart() is behaviour-preserving. Replacing the two if direction == 0: ... elif direction == 3: chains with a single moveEntity(part, part.getDirection()) looks like it widens the accepted range, but moveEntity (src/ophidian.py:333-344) has the same if/elif and returns early on its newLocation = -1 default — so an out-of-range direction is still a no-op, exactly as before. Directions are only ever set to 0–3 anyway (SnakePart.__init__ defaults to 0).
  • The pygame event drain still completes on a restart. Deliberate, and covered by test_pygame_restart_does_not_drop_events_queued_behind_it — pygame.event.get() has already dequeued the batch, so breaking would have discarded whatever was queued behind the restart key.
  • HUD line placement. The upgrades line and boost line share a flowing lineY, so the boost takes row 2 when nothing is owned and row 3 otherwise; both placements are asserted, and the "omits" test pins that nothing is drawn when no boost is running.
  • No doc drift. The Controls table still matches handleKeyDownEvent, and --text-ui is still the only argparse flag. The new sections sit above "Usage" and don't restate it.

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

- drawHud/renderHud now check `is not None and > 0` instead of relying on
  truthiness, so suppressing the line for an expired-but-not-yet-cleared
  boost reads as deliberate rather than accidental; both are pinned by a
  test now.
- README states the version the project is actually exercised on alongside
  the 3.8 floor, which nothing verifies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dmccoystephenson
dmccoystephenson merged commit 5234d35 into main Jul 29, 2026
@dmccoystephenson
dmccoystephenson deleted the feature/ui-parity-and-setup-docs branch July 29, 2026 01:57
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