- Status: Released v0.3.1
- Date: 2026-09-30
- Owner: Project author
- Name: Blockfall (final — resolved 2026-09-27, §14 item 1 closed)
A fast, responsive falling-block puzzle game in the spirit of Tetris, built in Rust on the Bevy ECS game engine — single-player marathon plus 1v1 versus (local and online, netplay v0.1, §15). The game targets desktop first (Linux/Windows/macOS), with a Web (WASM) build as a stretch goal. Gameplay follows modern community-standard mechanics: SRS rotation, 7-bag randomizer, hold, ghost piece, lock delay, and soft/hard drop.
Bevy has no polished, idiomatic reference game of this genre. Building one produces:
- A genuinely fun, replayable game with tight input feel.
- A clean demonstration of ECS-driven game architecture: deterministic pure logic core decoupled from rendering/input/audio presentation layers.
- A testable core (board/piece logic is pure Rust, unit-testable without the engine).
- G1: Arcade marathon mode with levels, escalating gravity, and standard scoring.
- G2: Modern controls feel: DAS/ARR handling, hold, ghost piece, hard drop, lock delay.
- G3: SRS rotation with wall kicks, including basic T-spin detection.
- G4: 60 FPS on integrated graphics; input latency imperceptible (< 1 frame).
- G5: Clean separation of simulation and presentation, enabling headless unit/property tests.
- G6: Configurable key bindings and volume from a settings screen.
Multiplayer (local or online).Superseded 2026-09-29: local versus (T24–T26) and online 1v1 lockstep netplay (netplay-plan.md N1–N8) shipped as netplay v0.1 — see §15. The multiplayer items that remain out of scope: lobbies, relay/signaling and LAN discovery, rollback netcode, and session tokens/invites.- Guideline "Adventure"/mission modes, 40-lines sprint, ultra mode timers.
- Mobile/touch controls.
- User accounts, leaderboards, telemetry, monetization.
- Reskinning/piece themes beyond one default visual style.
| Priority | Platform | Notes |
|---|---|---|
| P0 | Desktop Linux / Windows / macOS | Native builds via cargo, CI later |
| P1 | Web (WASM via bevy/web) |
Stretch; needs perf pass on UI text |
- 10 columns × 22 rows: 20 playable rows + 2 spawn-buffer rows above; all 22 are drawn so spawning/kicked pieces never clip at the top edge.
- Line clear: full rows removed, stack shifts down. Multi-line clears score more (1–4 lines).
- Game over: a piece spawns overlapping existing cells (block-out) — top-out rule only.
All 7 standard pieces (I, J, L, O, S, T, Z), rendered as colored cells on a grid.
- 7-bag: shuffle all 7 pieces, deal one at a time, reshuffle on empty.
- Preview queue: next 5 pieces shown (configurable 1–6).
- Super Rotation System with JLSTZ and I kick tables.
- Clockwise, counter-clockwise (Q/E or mouse wheel), and 180° flip (A, optional binding).
- T-spin detection: 3-corner rule with kick-history exception (TST/TSD/TST-lite accepted at author's discretion). T-spins score bonus and count for back-to-back.
- Left/right shift with DAS 150 ms / ARR 33 ms defaults (configurable).
- Soft drop: +1 point per cell, gravity-speed × N.
- Hard drop: instant lock, +2 points per cell, triggers lock immediately.
- Lock delay: 500 ms grounded, reset on successful move/rotate, max 15 resets.
- Hold: one swap per piece, first press spawns next piece from bag instead of swapping in.
Level advances every 10 lines cleared. Gravity table follows the Tetris Worlds curve, capped at 20G; UI displays current level, lines, score.
| Action | Base points (× level) |
|---|---|
| Single / Double / Triple / Tetris | 100 / 300 / 500 / 800 |
| T-spin mini / T-spin (no lines) | 100 / 400 |
| T-spin single / double / triple | 800 / 1200 / 1600 |
| Back-to-back Tetris/T-spin | ×1.5 |
| Combo | 50 × combo count × level (capped 10) |
| Soft drop / hard drop | 1 / 2 per cell (flat) |
| Perfect clear (single piece) | 3500 |
- Title — start, settings, quit.
- Game — playfield, next queue (5), hold box, score/level/lines HUD, pause key hint.
- Pause — resume, restart, settings, quit to title.
- Game over — final score/level/lines, "best" persisted locally (JSON in config dir), restart, title.
- Settings — key rebinding, master/SFX/music volume, DAS/ARR sliders, next-queue size.
- SFX: move, rotate, lock, line clear (distinct for tetris/t-spin), level up, hard drop, hold, game over.
- Simple looping BGM track with pause ducking; Mute/sound in settings.
- Juice: line-clear flash + brief freeze (≤80 ms), hard-drop shake (subtle), ghost piece, lock-flash. All juice skippable in settings ("effects: low/med/high").
| Action | Default |
|---|---|
| Move left / right | ← / → |
| Soft drop | ↓ |
| Hard drop | Space |
| Rotate CW / CCW | ↑ / Z (X too) |
| Rotate 180 | A |
| Hold | C (Shift too) |
| Pause | Esc / P |
- Rust (stable, pinned via
rust-toolchain.tomlat scaffold), edition 2021. - Bevy pinned to latest stable minor at scaffold time (0.19.x as of the 2026-09-26 author amendment; bump deliberately).
- Plugins:
bevy_ecs(in engine),bevy_kira_audiofor audio,serde+bevy_mod_rpchandling-free simple JSON persistence (no extra state crate unless proven necessary). cargo clippy -D warnings,cargo fmt,cargo testgate in CI.
tetris/
├── Cargo.toml # workspace root
├── crates/
│ ├── tetris-core/ # pure simulation: board, pieces, SRS, scoring, state machine. No Bevy.
│ └── tetris-app/ # Bevy binary: ECS wiring, scenes, rendering, input, audio, UI
tetris-core owns all rules and is deterministically testable (fixed RNG seed → replayable).
tetris-app subscribes to core events and animates them.
- Resources:
GameCore(wrapper overtetris-core::Game),GameEventsqueue,Settings,AudioHandles. - Systems (fixed-step
Simulationset at 60 Hz, ahead of render):input → apply actions → core step (gravity/lock) → produce events → visuals/audio response. - Events:
PieceSpawned,PieceLocked,LineCleared {rows},ScoreChanged,LevelUp,GameOverconsumed by presentation systems. - Rendering: 2D sprite batches /
bevy_uigrid; cell size derived from window; letterboxed. - Determinism core → trivial headless tests and seeded replays; replay export is post-v1.
~/.config/<app>/settings.json + best.json via serde; corruption → reset to defaults, log.
| # | Deliverable | Scope | Acceptance |
|---|---|---|---|
| M0 | Repo scaffold (this PRD, README, .gitignore) | git, docs | CI-ready tree ✔ |
| M1 | tetris-core |
board, 7-bag, SRS + kicks, scoring, lock delay, hold, gravity | Unit tests: SRS kick tables, bag, scoring, line clears, T-spin detect; cargo test green |
| M2 | Playable vertical slice | window, grid render, keyboard input (DAS/ARR), core wired, HUD, game over→restart | A human can play a full game; 60 FPS |
| M3 | Full UX | pause, settings (rebind, DAS/ARR, volumes), best score, next×5, hold UI | All §7 screens reachable; settings persist |
| M4 | Juice & audio | SFX, BGM, line flash, freeze frames, shake | "Feels good" pass; effects quality setting |
| M5 | Release hardening | CI (fmt/clippy/test), release builds for 3 OSes, icon, README/screenshots | Tagged v0.1.0, downloadable binaries |
| M6 | WASM build (stretch) | bevy/web config, reduced UI text, asset compression |
Playable in browser |
Suggested ordering is strict: no milestone starts its polish before its acceptance gate passes.
- Author plays ≥ 3 marathon sessions per week during development without friction complaints.
- Zero crash-to-desktop over 1 h automated headless soak (random action fuzz vs core).
- Clippy clean, core test coverage on all rules (SRS/scoring/lock/bag).
- New machine:
cargo runworks with no manual setup.
| Risk | Impact | Mitigation |
|---|---|---|
| Bevy API churn across minors | Rework | Pin version; upgrade only at milestones; core is Bevy-free |
| Input feel mistakes found late | Rework core API | Input schema decided in M1; core takes discrete actions with timestamps |
| WASM perf with text/UI | Stretch slips | M6 explicitly last, non-blocking |
| Scope creep (multiplayer) | Delay | Netplay confined to the §15 v0.1 scope; lobby/relay/rollback stay post-v1 |
- Final game name & art style (flat color vs. sprite-based)?
- Include 180 rotation and hold UI animation in M2 or defer to M3?
- Sprint/40L mode as v1.1 quick win after M5?
Implemented as netplay v0.1 (T24–T26 in tetris-plan.md + N1–N8 in netplay-plan.md):
local two-player versus (shared keyboard, human or bot seats, Garbage/Race rules) and
online 1v1 lockstep netplay — direct IP/port join over bevy_renet/netcode, delay-based
input sync (D = max of the two peers' desires, default 8 ticks ≈ 133 ms), host-authoritative
tick clock, snapshot-hash desync detection, version handshake. Unauthenticated netcode
auth in v1; no pause in net matches (Esc = leave-with-confirm).
Post-v1: lobbies, relay/signaling and LAN discovery, rollback netcode, session tokens/invites, replays/watch, additional modes (sprint, ultra), skins, controller support, Steam packaging.