# Symph Rhythm-driven grid traversal. Real-time spatial movement over a quantized musical beat, on a 3×3 grid. ## Concept A song advances one chord at a time on the beat (the Z-axis). Each chord is a 3×3 matrix of notes. The player holds a cell on the grid and moves across lanes (X) and rows (Y) to collect items and avoid walls. Lane shifts persist; row shifts are transient and collapse back to the base row after a fixed number of chords — a jump you must ride out or duck out of. Returning into a wall on landing is fatal. The engine is a deterministic, side-effect-free state machine. Rendering and input polling run at host framerate (~60Hz), decoupled from beat progression. Frontends translate native input to platform-neutral actions and drain queued gameplay events into audio. ## Mechanics | Item | Glyph | Effect | |---|---|---| | Energy | `*` | +1 energy, consumed on contact | | Magnet | `M` | sweeps every Energy note in the lookahead window, all grid positions | | Shield | `S` | arms one absorb charge; no expiry | | Boost | `B` | doubles tempo for a fixed duration; refreshes, does not stack | | Wall | `#` | fatal, unless a charge absorbs it or a Boost is active | Two persistent effects (Shield charges, Boost deadline) cross level boundaries and clear on death. A Boost grants wall immunity but the field keeps rendering walls lethal — Boost expiry inside a wall band is the primary death mode. ## Build ```sh make build-term # terminal frontend (symph-term) make build-raylib # raylib frontend, linux only (symph-raylib) make test ``` Requires Go 1.26+. The raylib frontend builds CGO-free (embedded `.so`, linux amd64/arm64). ## Run ```sh bin/symph-term ``` ### Controls Vim-style bindings: - `h` `j` `k` `l` — left, down, up, right - `Enter` — restart after game over - `Ctrl+S` — toggle mute - `Esc` / `Ctrl+C` / `q` — quit Host controls (quit, mute) bypass the engine and work in every phase, including paused. ## Frontends - **Terminal** (`symph-term`): full Unicode glyph set, density-shaded walls, wall-band colored borders. Pauses and shows a notice below minimum viewport size. - **Raylib** (`symph-raylib`, linux): same presentation contract, drawn with primitives. Substitutes ASCII for the Unicode atlas (default font covers codepoints 32..126); draws walls as rectangles. Both share the grid-scan and burn-out projection pipeline in `render`; presentation policy lives there, not in the engine. ## Audio Fire-and-forget effects, PCM synthesized at startup. PipeWire backend (`pw-play`) on Linux; silent `nullEngine` elsewhere or when `pw-play` is absent — the game stays playable without audio. FreeBSD (sndio/OSS) is unimplemented and falls through to silence. ## Layout Unidirectional dependency flow (supports a future CGO/gomobile mobile target): - `types/` — dependency-free structures and the value registry (identity, glyph, polarity) - `parameter/` — compile-time constants: grid bounds, timings, glyphs, geometry - `game/` — pure state engine; contact behavior and persistent effects - `input/` — native key → neutral `Key` → `game.Action` - `render/` — appearance tables and the shared draw pipeline; `render/raylib/` is the graphical frontend - `audio/` — synthesized effects and platform backends - `cmd/` — hosts: wiring, event poller, ticker Adding an item kind is three rows — identity (`types.valueSpecs`), behavior (`game.resolvers`), appearance (`render.valueVisuals`) — with no switch edits. `init` asserts registry completeness at process start. ## Status Early development. Terminal and raylib frontends are functional. Planned: - **Android** frontend via `gomobile` (`mobile/`, `platform/android/`). The `input` package is bypassed on that path — touch/gesture translates directly to `game.Action`. - Symphony (multi-song) and Symphace (bounded environment) domain layers. - Persistent audio stream to replace per-effect `pw-play` spawns before latency matters. - FreeBSD audio backend (sndio/OSS). ## License See `LICENSE`.