Implement deferred features: in-game options menu + level editor#2
Merged
Conversation
Implements the two features deferred during the modernization. Options menu (scenes/options.py + ui/widgets.py): a small keyboard-driven menu toolkit (Toggle/Slider/Choice/Action + a Menu that handles navigation and rendering) and a scene that edits the live Settings and persists them on exit. It only surfaces settings that have a real effect — music, volume, fullscreen, decorations, and a developer overlay — so there are no dead toggles; audio and fullscreen apply immediately. A small developer HUD (player pos/vel, lives, counts) is gated on the developer-mode setting, which makes that toggle meaningful. Level editor (scenes/editor.py over world/editing.py): a schematic, mouse-driven editor. The display-free EditorModel holds the level records, enforces the .dat slot limits, round-trips through world/loader.py, and exposes playability(), which encodes the structural rules World.from_level_data needs to build a level without crashing or hanging — most importantly that generate_mobs' placement loop can terminate (>=3 platforms with >=2 distinct non-spawn x). Levels save to lvl_custom.dat in the user data dir and can be test-played in place via a new sandbox LevelScene mode that pops back to the editor instead of going to the title/game-over flow. Both are reachable from the title screen (O / E). Adds 20 tests (widgets, options persistence, editor model + round-trip + the full editor->play path). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Implements the two features that were deferred during the modernization (and documented in
CLAUDE.mdas "good next tasks"). Both are reachable from the title screen — O for options, E for the editor.Options menu —
scenes/options.py+ui/widgets.pyToggle/Slider/Choice/Actionrows + aMenuthat owns navigation and rendering. Fully unit-tested without any mouse hit-testing.Settingsand persists them on exit.antialiasing/resolution/vsyncknobs are launch-only or vestigial and stay out of the UI.developer_modeto a real debug HUD inLevelScene(player pos/vel, lives, coin/key state, entity counts), so that toggle is meaningful.Level editor —
scenes/editor.pyoverworld/editing.py[/]size the brush, arrows pan,S/Lsave & loadlvl_custom.dat(in the user data dir),Ptest-plays.EditorModelholds the records, enforces the.dat25-slot limits, round-trips throughworld/loader.py, and exposesplayability().playability()encodes the structural rulesWorld.from_level_dataneeds to build a level without crashing or hanging: ≥3 platforms with ≥2 distinct non-spawn x-positions. (I traced an infinite loop ingenerate_mobsfor degenerate levels — its placementwhileonly terminates when a non-spawn platform's x differs from the longest platform's. The editor refuses to test-play a level that would trip it.)LevelScenemode (custom_data=+sandbox=True) that builds from editor data and pops back to the editor on win/death/Esc instead of routing to the title/game-over flow.Tests (+20, 96 total)
Widget logic, options navigation + on-disk persistence, editor model add/erase/snap/limits/round-trip/validity, the title→options and title→editor transitions, the developer-overlay render path, and the full editor→play path running headless (which also exercises the
generate_mobshang guard).Verification
uv run pytest(96 pass) ·ruff check·ruff format --check·mypy src· headless smoke run — all green locally.🤖 Generated with Claude Code