Roadmap
Every item here is permanent once written -- only its status changes over time. See the schema and process documentation for the full editing discipline.
Phase 1 — Foundation & Core Engine Architecture
- RM-001
Deployment target
CompletedSingle-player, browser-only.
io,render, andgameare JavaScript-target only;peremains target-agnostic, compiling and testing cleanly on both Erlang and JavaScript.Approved 2026-08-07.
- RM-002
Build strategy
CompletedNo bundler.
gleam build --target javascriptproduces browser-loadable ESM directly, consumed by a hand-writtenindex.html.See decision 0003. decision 0003
- RM-003
Development server
Completeddeno run --allow-net --allow-read jsr:@std/http/file-server --header "Cache-Control: no-cache", serving the repository root. Theno-cacheheader is load-bearing: its absence produced an observed version-skew failure mode (a stale cached module alongside a fresh module graph).See decisions 0008, 0026. decision 0008decision 0026
- RM-004
Game loop and module wiring
CompletedModel-Update-View:
peownsModel/tick;glemy/game.gleamandgame_ffi.mjsown therequestAnimationFrameloop and wireio -> pe.tick -> rendertogether each frame. Presentation moved from an initial Canvas2D approach to a realGPUCanvasContext. Per-frame logic (tick_and_render) is covered directly bygleam test --target javascriptvia a realOffscreenCanvas; therequestAnimationFramescheduling wrapper itself is the one piece with no automated coverage, exercised instead bytools/browser_check.ts.See decisions 0010 (superseded), 0012, 0013, 0015. decision 0010decision 0012decision 0013decision 0015
- RM-005
Entity and state representation
CompletedEntities are a plain immutable
Entityrecord in aList(Entity), not an entity-component-system. Revisit only if entity type diversity, not count, becomes a real constraint.See decision 0004. decision 0004
- RM-006
GPU-resident data flow between compute and render stages
SupersededOriginally scoped as a question about feeding
glemy/cs's compute output torenderwithout a CPU roundtrip. The underlying research (GPUBufferwith combinedSTORAGE/VERTEXusage flags enabling same-frame compute-to-render withoutmapAsync) remains valid reference if GPU-parallel batch physics is reintroduced.Moot:
glemy/cswas deleted as unused, dead code before this question needed resolving. See decision 0031. decision 0031 - RM-007
Input handling
Completedglemy/ioexposes synchronous polling queries (is_key_down,mouse_position,is_mouse_button_down), JavaScript-target only, backed by a plainEventTargetrather thanwindow.See decision 0006. decision 0006
- RM-008
Physics: single-particle integration
CompletedSemi-implicit (symplectic) Euler integration, implemented in
glemy/pe/entity.gleam(Entity,integrate).Implemented; no dedicated decision entry.
- RM-009
Physics: batch integration via compute shader
SupersededImplemented in
glemy/cs/entity_batch.gleam, later deleted with the rest ofglemy/cs:pe's CPU-only physics never grew to need it.Decision 0014 capped entity count instead of requiring GPU batch integration;
glemy/csdeleted per decision 0031. decision 0014decision 0031 - RM-010
Physics: boundary collision
CompletedImplemented in
glemy/pe/bounds.gleam(Bounds,bounce); each axis resolved independently, reflecting by magnitude rather than sign-flip alone.Implemented; no dedicated decision entry.
- RM-011
Physics: circle-circle collision
CompletedImplemented in
glemy/pe/collision.gleam(resolve). Originally an equal-mass simplification, verified against the textbook fact that an equal-mass head-on collision must exactly swap velocities; later made mass-aware (mass proportional to radius squared), required once entity sizes began varying substantially across tiers.See decisions 0007 (superseded), 0018. decision 0007decision 0018
- RM-012
Physics: broad-phase collision culling
DeferredTrigger condition (O(n^2) collision cost becoming the actual bottleneck) was hit once, measured directly, and resolved by capping
pe.max_entitiesinstead of building broad-phase culling -- which also means the trigger condition no longer holds. Remains deferred pending a real, re-confirmed need.See decision 0014. decision 0014
- RM-013
Physics: rotation and rigid bodies (SAT)
DeferredStretch goal; not scoped further pending an actual game-design need for non-circular collision shapes.
- RM-014
Testing strategy for GPU-dependent modules
CompletedJavaScript-target testing runs under Deno (native WebGPU), not Node -- the
webgpunpm package could not be trusted for render-pass execution under Node.renderhas full automated coverage undergleam test.See decision 0008. decision 0008
- RM-015
Module dependency direction
Completedperemains the one dependency-free, dual-target core;renderandgamedepend on it, never the reverse.Reference maintained in ARCHITECTURE.md's Core/Shell section. ARCHITECTURE.md
- RM-016
Decouple game-rule outcomes from
CompletedpeviaGameEventGame-rule outcomes specific to a given genre (tier progression, merge results, scoring) are to be represented as
GameEventdata returned frompe.tick, rather than embedded as first-class concepts inside genre-agnostic physics logic -- the prerequisite for a second, genre-distinct game to reusepewithout inheriting the current reference game's rules.Implemented: pe.SoundEvent generalized into pe.GameEvent, returned as #(Model, List(GameEvent)) from tick. See decision 0044. decision 0042decision 0044docs/technical-architecture.md#2.2
- RM-017
Persisted state schema versioning policy
PlannedEstablished as standing policy ahead of any persistence implementation: every persisted record carries an explicit
schema_versionfield, loaded via stepwise migration functions, represented as save data-transfer structures distinct from runtimeModeltypes. - RM-029
Formalize the Core API surface and the games/<name>/ convention
CompletedDefine and document the stable, fundamental Core API a game built on this engine actually needs (pe.Model/update/spawn_entity, the pe/collision_sweep PairInteraction(event) callback, render/io), and move every remaining Suika-specific rule/state concept (tier radii/color/score/merge, current_tier/next_tier/danger_timer/game_over/cooldown_remaining/score) out of pe and into a new glemy/games/tiers/ tree -- closing a concrete backwards dependency found in the process (render.gleam importing glemy/pe/tier directly) and establishing glemy/games/<name>/ as the standing convention a second, genre-distinct game will follow. In-repo boundary hardening only; does not itself trigger RM-027's physical package extraction.
Implemented: pe/collision_sweep.gleam generalized to a caller-supplied interact(a, b) -> PairInteraction(event) callback; Entity.tier renamed to Entity.kind (no built-in meaning at the pe layer); render.gleam takes an explicit colors parameter instead of importing genre-specific logic; pe.gleam reduced to Model(entities, bounds, gravity)/update/spawn_entity/max_dt; glemy/games/tiers.gleam (own Model wrapping pe.Model, tick, Input, GameEvent) and glemy/games/tiers/rules.gleam (relocated from the deleted pe/tier.gleam) created; game.gleam now glues io/render to games/tiers. Core API surface and Rust-API-Guidelines-informed design notes documented in docs/technical-architecture.md #2.4. See decision 0047. decision 0042decision 0047docs/technical-architecture.md#2.4ARCHITECTURE.md
- RM-030
Core API cleanup: rename pe to physics, fix the render colors/entities pairing and Canvas construction gaps
CompletedFollow-up review of RM-029/decision 0047, requested explicitly to check whether the newly-formalized Core API surface was actually clean, not just correctly bounded. Found: pe (Physics Engine & Logic) was stale as a name once decision 0047 moved the Logic half out, and collided with docs/technical-architecture.md already using Core API as the umbrella term; render_entities_to_bytes/render_entities_to_canvas took entities and colors as two separate same-length lists synced only by caller convention, a real reproducible runtime-crash class on mismatch; render.Canvas had no public constructor anywhere, making render unusable standalone outside glemy/game; io.gleam had a stale header doc comment describing input as read directly by pe.
Implemented: pe renamed to physics throughout src/ and test/ (mechanical, ~24 files, doc comments included); render_entities_to_bytes/render_entities_to_canvas now take one List(ColoredEntity) (entity paired with its own color) instead of two parallel lists; render.get_canvas(id) added as the one public Canvas constructor, moved out of a private copy in glemy/game.gleam; io.gleam header corrected. See decision 0048. decision 0047decision 0048docs/technical-architecture.md#2.4ARCHITECTURE.md
- RM-031
Extend the Core API with cooldown/stopwatch/random utilities and simplify games/tiers on top of them
CompletedProactive next step after RM-029/RM-030 (decisions 0047/0048): rather than waiting for a second game, grow the Core API with small, research-backed, independently-testable utilities to give the architecture the feel of a real game engine, and rewrite games/tiers.tick on top of them to simplify it. Research (Bevy bevy::time::{Timer, Stopwatch}, Godot Timer node vs. PhysicsServer2D and its documented seeded-RNG-non-reproducibility issue godot#27856, LOVE2D love.timer/love.math vs. love.physics) confirmed timer/random utilities belong as Core siblings to physics, not nested inside it. A generic PreviewQueue(a) abstraction (generalizing the current_tier/next_tier two-ahead pattern) was seriously considered and explicitly rejected as premature genericity over an unvalidated data shape, the same failure mode already paid for once (glemy/cs, decision 0031).
Implemented: added glemy/cooldown.gleam, glemy/stopwatch.gleam, glemy/random.gleam (plain functions over Float/List(a), not opaque wrapper types -- matching physics/entity.gleam resting_time/settle precedent) and physics.entity_count. Rewired games/tiers/rules.random_droppable and three expressions in games/tiers.tick to use them; Model field types unchanged, so tiers_test.gleam/game_test.gleam/rules_test.gleam required zero edits and their unmodified passing served as the regression proof. gleam test both targets green (186 Erlang / 209 JavaScript, exactly the prior baseline plus the new modules own tests), deno task check-warnings unchanged (51/51, zero new warnings), deno task browser-check passes for real. See decision 0049. decision 0047decision 0049docs/technical-architecture.md#2.4ARCHITECTURE.md
- RM-032
Add physics/bounds.clamp_x and physics.settle_all, simplify games/tiers.tick further
CompletedSecond follow-up round after RM-031/decision 0049, same instruction: keep growing the Core API with evidenced, testable utilities extracted from games/tiers.gleam. Investigating a generalized clamp_preview_x surfaced a real, verified correctness bug: the naive gleam/float.clamp-based version disagrees with physics/bounds.gleam own already-shipped bounce/bounce_axis on a degenerate box (radius larger than half the box width), a scenario the existing test suite already exercises. A third candidate (bounds.y_from_top, generalizing spawn_y/danger_line_y) was investigated and rejected as zero-risk duplication with no testable-logic benefit.
Implemented: added physics/bounds.clamp_x (with a private clamp_axis matching bounce_axis own edge-resolution order, verified via a test reusing the existing degenerate-bounds fixture) and physics.settle_all (bulk-apply entity.settle, mirroring update own internal composition shape). Rewired games/tiers.gleam to delete clamp_preview_x and call bounds.clamp_x directly, and replaced the manual settle map-then-reconstruct with one call to settle_all. No Model field changed shape, so tiers_test.gleam/game_test.gleam/rules_test.gleam needed zero edits. gleam test both targets green (195 Erlang / 218 JavaScript, exactly prior baseline plus new tests), deno task check-warnings unchanged (51/51), deno task browser-check passes for real. See decision 0050. decision 0049decision 0050docs/technical-architecture.md#2.4ARCHITECTURE.md
Phase 2 — Reference Game: Suika-Style Merge Puzzler
- RM-018
Game design and initial implementation
CompletedA Suika-style merge puzzler: dropped circles merge into the next tier on same-tier collision, score accrues per merge, the game ends if the stack remains above a danger line too long. Delivered phase by phase: the tier system, mass-aware collision, the merge algorithm, per-tier render color, a click-coordinate bugfix the drop mechanic surfaced, the cooldown-gated drop mechanic itself, the danger-line/game-over condition, and the score/game-over HTML overlay.
See decisions 0017-0024. decision 0017decision 0018decision 0019decision 0020decision 0021decision 0022decision 0023decision 0024
- RM-019
Playtesting-driven fixes and feature polish
CompletedA silent black-screen failure mode and a boundary-overflow bug were fixed; a resting-contact resonance bug required two empirically disproven attempts before a Box2D-style sleep timer resolved it; restitution and fall-speed were corrected against a reference-game clone's values; the drop-preview circle was wired up for rendering; a danger-line indicator, a two-ahead tier queue with a UI swatch, synthesized drop/merge sound effects, whole-canvas merge visual feedback, and
dtclamping against tunneling were added.See decisions 0025-0037. decision 0025decision 0026decision 0027decision 0028decision 0029decision 0030decision 0031decision 0032decision 0033decision 0034decision 0035decision 0036decision 0037
- RM-020
Hands-on feel-tuning
DeferredCooldown length, danger threshold, tier radii/colors/scores, and spawn height are left to direct hands-on play rather than guessed at blindly. Empirical verification confirms current constants are sane; remains open-ended by design, not a task with a defined completion point.
See decision 0025. decision 0025
Phase 3 — CI/CD & Deployment Pipeline
- RM-021
Public website scaffolded and deployed
Completedglemy-website, built withlustre_ssg, deployed to GitHub Pages via a GitHub Actions workflow that checks out both repositories, buildsglemy's JavaScript target, and publishes the result. Runs on push, daily schedule, and manual dispatch.See decision 0041. decision 0041
- RM-022
Internal link/asset resolution under a project-site subpath
CompletedRoot-relative internal links resolved incorrectly under GitHub Pages' project-site subpath, confirmed live. Resolved by routing every internal link and asset reference through a single build-time absolute base URL, sourced from
actions/configure-pages' own output.Documented in glemy-website's own repository history. glemy-website repository history
- RM-023
Site-wide SEO, sharing metadata, and content-derived pages
CompletedCanonical/OpenGraph/Twitter metadata,
sitemap.xml,robots.txt, an Atom feed for the devlog, devlog category filtering, and a roadmap page rendering this project's own planning documentation -- all generated at build time from data already maintained in this repository, not hand-duplicated.Documented in glemy-website's own repository history. glemy-website repository history
- RM-024
Content-hashed static asset filenames
CompletedBuilt static assets are to be published under content-derived filenames, closing the risk of a stale/new asset mismatch for a visitor reloading during the several-second deployment window.
Implemented: style.css hashed per-file, glemy build output hashed as one renamed directory. See decision 0045. decision 0042decision 0045docs/technical-architecture.md#3.3
- RM-025
Stable and edge release channels for the live demonstration
Completed/playis to remain a pinned, deliberately promoted build; a separate/play-edgepath (or equivalent) is to track the default branch directly, decoupling the public-facing demonstration from active development.Implemented: STABLE_GLEMY_REF pins the stable channel, dual glemy checkout/build in CI, both linked from the game card. See decision 0046. Superseded by decision 0057: a real deploy crash (a per-game HTML file missing from a lagging stable checkout) plus an explicit user request replaced 0046's manual-only promotion with CI-gated automatic promotion -- glemy-website's promote-stable.yml polls glemy's own new CI (RM-035) hourly and bumps STABLE_GLEMY_REF to the latest commit whose build/test actually passed, then explicitly dispatches deploy.yml to publish it. decision 0042decision 0046decision 0057docs/technical-architecture.md#3.4
- RM-026
Project-level Technical Architecture Document
Completeddocs/technical-architecture.mdestablished as the system-level architecture reference spanning both repositories, distinct in scope fromARCHITECTURE.md's in-repository module placement rules.See decision 0042. decision 0042
- RM-035
Continuous integration for glemy itself
Completedglemy had no automated CI of its own -- CLAUDE.md's build/test/warnings gate was a standing rule enforced only by whoever (human or agent) happened to run it locally before declaring work done, with no checkable GitHub status. Became a concrete need, not a theoretical one, once glemy-website's stable-channel promotion needed a real, external signal for "this commit's build/test actually passed" to gate on (decision 0057).
Implemented: .github/workflows/ci.yml runs gleam build/gleam test on both the Erlang and JavaScript targets on every push to main. The JavaScript target needed a real, working headless WebGPU backend (decision 0015 -- Deno's native WebGPU, not a mock) on a GPU-less ubuntu-latest runner; installing mesa-vulkan-drivers/libvulkan1 (lavapipe, Mesa's software Vulkan implementation) gave wgpu a real adapter to find, confirmed working on the very first real run (263 Erlang / 288 JavaScript tests passed, matching local counts exactly). This is now the real, checkable signal glemy-website/.github/workflows/promote-stable.yml polls to decide what's safe to auto-promote to the public stable demo. See decision 0057. decision 0057docs/technical-architecture.md#3.4
Phase 4 — Multi-Game Expansion Trigger
- RM-027
Engine module extraction trigger
Completedpe,render, andioremain part of theglemyrepository until a second, genre-distinct game begins active development. At that point, these modules are to be extracted into an independently versioned package consumed by each game, mirroring the structure observed in every comparable engine surveyed (Bevy, Godot, Phaser). Semantic Versioning is adopted forglemy's own version number at the same trigger point.Extraction happened: Tiers, Breakout, and Platformer moved into a new recregt/glemy-games repository, consuming glemy as a real dependency (a git dependency initially, swapped for a real Hex dependency once glemy published). glemy itself now contains only physics/physics/*, render, io, and small Core utilities. Semantic Versioning adopted at the same point (0.1.0). decision 0042decision 0064docs/technical-architecture.md#2.3docs/technical-architecture.md#4.3
Phase 5 — Process & Documentation Tooling
- RM-028
Expand tools/decisions to also manage docs/development-plan.jsonl
Completedtools/decisions(a type-safe Gleam CLI) currently only appends todocs/decisions.jsonl.docs/development-plan.jsonlis presently hand-written, with no equivalent validation or ID-assignment tooling. Expanding the tool's name, schema, and CLI surface to cover both append-only decision records and status-tracked roadmap items -- including support for editing an existing item's status field, which decisions.jsonl's own append-only tool deliberately does not need -- is a reasonable future direction.Implemented as tools/decisions/'s roadmap subcommand (decisions/roadmap_id.gleam, decisions/roadmap_schema.gleam, decisions/roadmap_log.gleam):
roadmap newappends a new item,roadmap update <id> <patch.json>patches status/resolution/decision_refs/doc_refs on an existing one, leaving phase/phase_title/title/description untouched. decision_refs entries are cross-validated against a real decisions.jsonl read; doc_refs is deliberately left unvalidated (see decisions.gleam's own doc comment). 104 tests passing in tools/decisions (up from 61). Dogfooded immediately: this very update was written viaroadmap update, not a hand-edit. See decision 0056. decision 0056docs/development-plan.mddocs/decisions.mdARCHITECTURE.md
Phase 6 — Reference Game 2: Breakout-Style Paddle Game
- RM-033
Second reference game: Breakout/Arkanoid-style paddle-and-brick game
CompletedA genuinely genre-distinct second reference game, chosen to stress-test the genre-agnostic claims of the Core API built across decisions 0047-0050 with a real second consumer -- a win condition, a hit-position-dependent paddle bounce, and rectangular collision. Rectangles (paddle, bricks) are scoped to stay entirely inside glemy/games/breakout, never becoming physics.Model entities, per an explicit user decision -- consistent with this project standing practice of not generalizing Core from a single caller. Phased: 0 (runner split, RM in decisions 0051) -> 1 (pure game logic, this entry) -> 2 (rendering/input wiring) -> 3 (playtesting + website catalog integration).
Phase 1 (pure game logic) and Phase 2 (rendering/input wiring) both implemented and verified. Phase 2: glemy/game_breakout.gleam (format_score/x_fraction/rect_to_css pure helpers, tick_and_render/start/loop/main runner) + glemy/game_breakout_ffi.mjs (score/status/paddle-box/brick-div/sound DOM writes) + breakout.html, mirroring glemy/game_tiers.gleam/game_tiers_ffi.mjs (decision 0051)'s split. Only the ball is a real physics.Entity, rendered via the existing WebGPU pipeline unchanged; the paddle and bricks are CSS-div overlays, positioned per-frame (paddle) and event-driven (bricks). Building the brick-hiding mechanism surfaced a real design gap in Phase 1's Brick/GameEvent shapes (list position is not a stable DOM key across brick destruction) -- fixed by giving Brick a permanent id field and changing BrickDestroyed to carry it. glemy/io.is_key_down got its first real caller (held-key paddle movement). tools/browser_check_shared.ts extracted out of tools/browser_check.ts at this second real caller, and tools/browser_check_breakout.ts added on top of it -- verifying a real RAF loop, a real held-ArrowLeft paddle move, and a real ball-brick collision against a real browser. gleam test both targets green (263 Erlang / 288 JavaScript), deno task check-warnings clean (72/72, all confirmed target-gating artifacts), deno task browser-check and browser-check-breakout both pass for real. See decision 0053. Phase 3 (feel-constant tuning + glemy-website catalog integration) remains. Phase 3 tuning: a real multi-exchange browser session confirmed the current constants (initial_ball_speed, paddle.speed, brick grid) are internally consistent and produce correct, working gameplay (two real brick hits, a clean Lost transition, zero page errors) -- subjective feel-tuning explicitly deferred to the user's own hands-on play, mirroring RM-020/decision 0025's own precedent for Tiers. See decision 0054. Phase 3 website integration: glemy_website/game_card.gleam's tiers_card generalized into a parameterized game_card, now with a real second caller (breakout_card); glemy-website's build.gleam and .github/workflows/deploy.yml gained a second stable/edge demo-dir pair; /play's catalog now lists both games, verified via a full local build simulating the real deploy workflow. See decision 0055. All four phases of this initiative are now complete. decision 0051decision 0052decision 0053decision 0054decision 0055docs/technical-architecture.md#1docs/technical-architecture.md#2.3docs/technical-architecture.md#2.4ARCHITECTURE.md
Phase 7 — Reference Game 3: Cross-Genre Validation
- RM-034
Third reference game: choose and build a genre-distinct game to validate (or reject) promoting physics.update's restitution parameterization / Rect collision into Core
CompletedDecision 0052 (Breakout's own pure-logic decision) explicitly deferred two Core API generalizations pending exactly this evidence: physics/bounds.bounce's restitution ramp is Tiers-specific tuning, and Breakout composes physics/entity.integrate + physics/collision_sweep.resolve_all_collisions directly with its own bespoke bounce rather than physics.update -- and rectangles (paddle, bricks) were scoped to stay entirely inside glemy/games/breakout, never touching physics/entity.gleam, per an explicit user decision. Decision 0052's own stated reasoning: 'If a third game later needs rectangles too, promoting this to Core then has two real data points to design from, not one.' Tiers and Breakout together have still only produced one real data point on the restitution question (both bounce; neither needs zero-restitution landing) and one on the Rect question (Breakout is the only caller). This item tracks choosing and building a third, genre-distinct game specifically to produce that second data point, not to add a feature for its own sake. Two candidate genres were identified as worth considering when this moves from planned to active work (deliberately not locked in here, the same way Breakout's own genre was resolved via an explicit fork before RM-033 began): (1) a platformer -- gravity plus player-controlled jump plus solid-ground landing that must NOT bounce, the single most direct test of decision 0052's own restitution finding, since Tiers/Breakout both currently want bouncing, never its absence; (2) a top-down shooter -- zero gravity (a genuinely different physics regime neither existing game exercises, Model.gravity actually set to a non-Tiers-tuned value), and heavy real use of collision_sweep's Consume interaction (bullet-hits-target removal), which Tiers/Breakout barely exercise today. Whichever genre is chosen, this item's own resolution should state plainly whether the two Core generalizations decision 0052 deferred are now justified by real second-data-point evidence, or whether they remain correctly scoped to their current callers -- a negative result here is exactly as valuable as a positive one, matching this project's own standing practice (decision 0052's physics.update finding itself was a negative result, recorded, not hidden).
All four phases implemented and verified. Verdict (decision 0061): circle-vs-Rect collision detection genuinely generalizes and is now Core (physics/rect.gleam, decision 0058); no single wall/landing restitution policy belongs in Core, confirmed a third distinct way (Tiers damped bounce, Breakout near-elastic bounce, Platformer zero-restitution stop) -- physics.update's composition remains correctly not a drop-in generic physics step. Phase 1 (decision 0059) also caught and fixed a real Landed-event design bug via a grounded-state-transition pattern before any test ran. Phase 3 caught and fixed a genuinely unwinnable level (jump peak height < platform step) via direct calculation before playtesting, then verified the fix with a real browser probe. glemy-website's already-generalized game_card/copy_demo needed only new call sites for Platformer, confirming both were correctly scoped at Breakout's own second-caller promotion. Final state: 281 Erlang / 308 JavaScript tests, 104/104 warnings baseline, all three browser-checks passing for real. See decisions 0058-0061. decision 0052decision 0058decision 0059decision 0060decision 0061docs/technical-architecture.md#2.3docs/technical-architecture.md#2.4ARCHITECTURE.md
Phase 8 — Engine Extraction and Library Release
- RM-036
Detach games into glemy-games, clean up the Core API, release glemy on Hex
CompletedRM-027's extraction trigger was met by Breakout and exceeded by Platformer (three real, structurally different games). Direct request to act on it: physically detach the games into an independently versioned repository, audit and clean up the engine's own public API now that a real external consumer exists to test it against, and publish glemy as a properly documented, versioned Hex package.
All three stages completed. Tiers/Breakout/Platformer moved to recregt/glemy-games (decision 0064), consuming glemy first via a pinned git dependency, later swapped for a real Hex dependency once published. glemy's public API (physics/physics/*, render, io) was audited against glemy-games' real usage -- no internal_modules candidates, full doc-comment coverage, no naming inconsistency survived reading the actual code (decision 0065). glemy.toml's Hex metadata was set (version 0.1.0, BSD-3-Clause, description, repository, links), LICENSE/README/CHANGELOG written, and a real publish blocker (io.gleam empty on the default erlang target) fixed by declaring target = "javascript" (decision 0066). glemy 0.1.0 published to Hex (decision 0067). glemy-website's deploy pipeline reworked to build from glemy-games instead of glemy directly. decision 0064decision 0065decision 0066decision 0067docs/technical-architecture.md#2.3docs/technical-architecture.md#4.3