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

  1. RM-001

    Deployment target

    Completed

    Single-player, browser-only. io, render, and game are JavaScript-target only; pe remains target-agnostic, compiling and testing cleanly on both Erlang and JavaScript.

    Approved 2026-08-07.

  2. RM-002

    Build strategy

    Completed

    No bundler. gleam build --target javascript produces browser-loadable ESM directly, consumed by a hand-written index.html.

    See decision 0003. decision 0003

  3. RM-003

    Development server

    Completed

    deno run --allow-net --allow-read jsr:@std/http/file-server --header "Cache-Control: no-cache", serving the repository root. The no-cache header 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

  4. RM-004

    Game loop and module wiring

    Completed

    Model-Update-View: pe owns Model/tick; glemy/game.gleam and game_ffi.mjs own the requestAnimationFrame loop and wire io -> pe.tick -> render together each frame. Presentation moved from an initial Canvas2D approach to a real GPUCanvasContext. Per-frame logic (tick_and_render) is covered directly by gleam test --target javascript via a real OffscreenCanvas; the requestAnimationFrame scheduling wrapper itself is the one piece with no automated coverage, exercised instead by tools/browser_check.ts.

    See decisions 0010 (superseded), 0012, 0013, 0015. decision 0010decision 0012decision 0013decision 0015

  5. RM-005

    Entity and state representation

    Completed

    Entities are a plain immutable Entity record in a List(Entity), not an entity-component-system. Revisit only if entity type diversity, not count, becomes a real constraint.

    See decision 0004. decision 0004

  6. RM-006

    GPU-resident data flow between compute and render stages

    Superseded

    Originally scoped as a question about feeding glemy/cs's compute output to render without a CPU roundtrip. The underlying research (GPUBuffer with combined STORAGE/VERTEX usage flags enabling same-frame compute-to-render without mapAsync) remains valid reference if GPU-parallel batch physics is reintroduced.

    Moot: glemy/cs was deleted as unused, dead code before this question needed resolving. See decision 0031. decision 0031

  7. RM-007

    Input handling

    Completed

    glemy/io exposes synchronous polling queries (is_key_down, mouse_position, is_mouse_button_down), JavaScript-target only, backed by a plain EventTarget rather than window.

    See decision 0006. decision 0006

  8. RM-008

    Physics: single-particle integration

    Completed

    Semi-implicit (symplectic) Euler integration, implemented in glemy/pe/entity.gleam (Entity, integrate).

    Implemented; no dedicated decision entry.

  9. RM-009

    Physics: batch integration via compute shader

    Superseded

    Implemented in glemy/cs/entity_batch.gleam, later deleted with the rest of glemy/cs: pe's CPU-only physics never grew to need it.

    Decision 0014 capped entity count instead of requiring GPU batch integration; glemy/cs deleted per decision 0031. decision 0014decision 0031

  10. RM-010

    Physics: boundary collision

    Completed

    Implemented in glemy/pe/bounds.gleam (Bounds, bounce); each axis resolved independently, reflecting by magnitude rather than sign-flip alone.

    Implemented; no dedicated decision entry.

  11. RM-011

    Physics: circle-circle collision

    Completed

    Implemented 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

  12. RM-012

    Physics: broad-phase collision culling

    Deferred

    Trigger condition (O(n^2) collision cost becoming the actual bottleneck) was hit once, measured directly, and resolved by capping pe.max_entities instead 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

  13. RM-013

    Physics: rotation and rigid bodies (SAT)

    Deferred

    Stretch goal; not scoped further pending an actual game-design need for non-circular collision shapes.

  14. RM-014

    Testing strategy for GPU-dependent modules

    Completed

    JavaScript-target testing runs under Deno (native WebGPU), not Node -- the webgpu npm package could not be trusted for render-pass execution under Node. render has full automated coverage under gleam test.

    See decision 0008. decision 0008

  15. RM-015

    Module dependency direction

    Completed

    pe remains the one dependency-free, dual-target core; render and game depend on it, never the reverse.

    Reference maintained in ARCHITECTURE.md's Core/Shell section. ARCHITECTURE.md

  16. RM-016

    Decouple game-rule outcomes from pe via GameEvent

    Completed

    Game-rule outcomes specific to a given genre (tier progression, merge results, scoring) are to be represented as GameEvent data returned from pe.tick, rather than embedded as first-class concepts inside genre-agnostic physics logic -- the prerequisite for a second, genre-distinct game to reuse pe without 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

  17. RM-017

    Persisted state schema versioning policy

    Planned

    Established as standing policy ahead of any persistence implementation: every persisted record carries an explicit schema_version field, loaded via stepwise migration functions, represented as save data-transfer structures distinct from runtime Model types.

    decision 0042docs/technical-architecture.md#4.4

  18. RM-029

    Formalize the Core API surface and the games/<name>/ convention

    Completed

    Define 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

  19. RM-030

    Core API cleanup: rename pe to physics, fix the render colors/entities pairing and Canvas construction gaps

    Completed

    Follow-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

  20. RM-031

    Extend the Core API with cooldown/stopwatch/random utilities and simplify games/tiers on top of them

    Completed

    Proactive 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

  21. RM-032

    Add physics/bounds.clamp_x and physics.settle_all, simplify games/tiers.tick further

    Completed

    Second 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

  1. RM-018

    Game design and initial implementation

    Completed

    A 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

  2. RM-019

    Playtesting-driven fixes and feature polish

    Completed

    A 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 dt clamping against tunneling were added.

    See decisions 0025-0037. decision 0025decision 0026decision 0027decision 0028decision 0029decision 0030decision 0031decision 0032decision 0033decision 0034decision 0035decision 0036decision 0037

  3. RM-020

    Hands-on feel-tuning

    Deferred

    Cooldown 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

  1. RM-021

    Public website scaffolded and deployed

    Completed

    glemy-website, built with lustre_ssg, deployed to GitHub Pages via a GitHub Actions workflow that checks out both repositories, builds glemy's JavaScript target, and publishes the result. Runs on push, daily schedule, and manual dispatch.

    See decision 0041. decision 0041

  2. RM-022

    Internal link/asset resolution under a project-site subpath

    Completed

    Root-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

  3. RM-023

    Site-wide SEO, sharing metadata, and content-derived pages

    Completed

    Canonical/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

  4. RM-024

    Content-hashed static asset filenames

    Completed

    Built 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

  5. RM-025

    Stable and edge release channels for the live demonstration

    Completed

    /play is to remain a pinned, deliberately promoted build; a separate /play-edge path (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

  6. RM-026

    Project-level Technical Architecture Document

    Completed

    docs/technical-architecture.md established as the system-level architecture reference spanning both repositories, distinct in scope from ARCHITECTURE.md's in-repository module placement rules.

    See decision 0042. decision 0042

  7. RM-035

    Continuous integration for glemy itself

    Completed

    glemy 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

  1. RM-027

    Engine module extraction trigger

    Completed

    pe, render, and io remain part of the glemy repository 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 for glemy'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

  1. RM-028

    Expand tools/decisions to also manage docs/development-plan.jsonl

    Completed

    tools/decisions (a type-safe Gleam CLI) currently only appends to docs/decisions.jsonl. docs/development-plan.jsonl is 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 new appends 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 via roadmap 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

  1. RM-033

    Second reference game: Breakout/Arkanoid-style paddle-and-brick game

    Completed

    A 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

  1. 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

    Completed

    Decision 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

  1. RM-036

    Detach games into glemy-games, clean up the Core API, release glemy on Hex

    Completed

    RM-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