Appearance
Development Guide
Current repository state
The repository contains a playable Godot 4.7 vertical slice. Scene-independent player, cipher, and sentry models are covered by headless unit tests. Reusable player, world, puzzle, HUD, title, level, and win scenes compose the runtime without an autoload or monolithic level script.
Requirements
- Godot 4.7, as declared by
config/featuresinproject.godot - Node.js 18 or newer only when editing or building this VitePress documentation
- Keyboard for the initial desktop control scheme
Run the game
Open the repository root in Godot or run:
bash
godot --path .For a non-interactive parse and import check:
bash
godot --headless --path . --editor --quitInput map
These actions are committed to project.godot so a fresh checkout is playable:
| Action | Primary | Alternate |
|---|---|---|
move_left | A | Left arrow |
move_right | D | Right arrow |
jump | Space | W or Up arrow |
dash | Shift | X |
interact | E | Enter |
pause | Escape | None |
Gameplay code must query actions, never physical keys directly.
Player composition
PlayerBodyAdapter is the only CharacterBody2D owner. It translates InputMap state and collision facts into PlayerCore, calls move_and_slide() once, and synchronizes the resulting velocity. PlayerCore composes the motion and charge models; the visual child observes motion/exposure signals and owns no gameplay decisions.
Do not move charge, exposure, dash, checkpoint, or death rules back into the scene script. Add integration behavior through narrow adapters and keep deterministic rules in the tested core.
Tests
Run the scene-independent rules and composed-scene contracts separately:
bash
godot --headless --path . --script res://tests/test_runner.gd
godot --headless --path . --script res://tests/integration/scene_test_runner.gdThe integration suite covers scene binding, local collision resources, platform activation, checkpoint routing, clue discovery, terminal pause restoration, cipher completion, and the win transition.
GDScript conventions
- Use typed parameters, returns, properties, and signals.
- Use
snake_casefor files, functions, variables, and input actions. - Use
PascalCaseforclass_namedeclarations. - Connect state changes with signals; do not add per-frame polling to every consumer.
- Export scene references that level designers must assign and validate them in
_ready. - Use groups for discovery such as
playeronly when an exported reference is impractical. - Call
set_deferred("disabled", value)when changing collision shapes from physics callbacks. - Keep one script responsible for one scene contract.
Tuning workflow
- Build a test room that contains one example of every core interaction.
- Record completion time, shadow deaths, missed jumps, dashes used, and cipher retries.
- Change one family of values at a time: movement, charge, sentry timing, then level geometry.
- Re-run from a clean start after every checkpoint or scene-transition change.
Do not balance around the developer's fastest route. The initial target is readability and a recoverable first playthrough.
Documentation site
Install dependencies and start the local site from docs/:
bash
cd docs
npm install
npm run devValidate production output with:
bash
npm run buildGenerated node_modules, .vitepress/cache, and .vitepress/dist directories are not source files and should not be committed.
Documentation rules
- Update
game-design.mdwhen player-facing rules or scope change. - Update
theme-and-art-direction.mdwhen visual, audio, UI, or asset rules change. - Update
asset-plan.mdwhen a source file is accepted, renamed, replaced, or removed. - Update
level-design.mdwhen room geometry, encounters, clue placement, or level cuts change. - Update
architecture.mdwhen scene contracts, state ownership, or signal flow change. - Update
physics-and-engine.mdwhen movement math, project settings, or code contracts change. - Keep speculative ideas in the deferred scope, not mixed into MVP requirements.
- Prefer measured values and acceptance criteria over aspirational prose.