← All decisions

Establish a project-level Technical Architecture Document: engine/game boundary, deployment/live-demo strategy, and versioning policy

architecturedocumentationdeploymentversioningevent-drivenpolicy

Context

With glemy positioned as a general-purpose engine (the current merge puzzler being a proving-ground game, not the end goal) and a public website now live, the user requested a researched, cross-checked technical architecture covering four questions: whether the system should be built around a core engine generating sub-games versus a modular/decoupled approach; how continuous deployment interacts with live-streamed/live demo sessions; what prevents backward-compatibility breakage during early development; and other material architectural/operational risks. The user required real, cited research (not memory) cross-checked across multiple independent sources, presented in chat for review before any formal document was written (two-phase workflow), then compiled into a public-ready, third-person Technical Architecture Document once approved.

Options considered

Split glemy into a separate 'engine' repository and 'game' repository now, ahead of a second game existing — rejected
Would repeat the exact failure mode already identified and reversed in this project: glemy/cs (decision 0031) was built as speculative structure ahead of a real caller, sat unused, and was deleted. Every real precedent checked (Bevy, Godot, Phaser) does separate engine from games, but always because a second, real, independent consumer exists -- not preemptively. The extraction trigger is defined explicitly (a second, genre-distinct game entering active development) rather than left implicit, so the decision of *when* isn't re-litigated later.
Keep Suika-specific game rules (tier, merge, scoring) embedded directly inside pe alongside genre-agnostic physics, deferring any decoupling until a second game actually needs it — rejected
Unlike the repo-split question, this decoupling is cheap to apply now (a return-type change, not new infrastructure) and expensive to retrofit later once genre-specific logic is threaded through pe's internals. Every comparable engine researched (Godot's signal system, Phaser's EventEmitter, Bevy's ECS event entities) uses an event-driven mechanism for exactly this kind of decoupling. Chose a Gleam-native form of the same principle -- pe.tick returning List(GameEvent) as pure data -- over a callback/subscriber runtime, since pattern-matching on returned data is a closer fit to Gleam's style than porting an OOP observer pattern.
Treat continuous deployment of glemy-website as low-risk by default, since static-site redeploys don't push to already-open clients — rejected
True but incomplete -- research confirmed already-open sessions are safe (browsers don't hot-swap loaded JS/WASM/WebGPU state), but a visitor reloading during the ~20-30 second deploy window (measured directly from this project's own CI run durations) can hit a stale/new asset mismatch, since build output currently uses fixed, non-content-hashed filenames. Accepting the incomplete picture would have left a real, currently-open gap unaddressed.
Defer any save-data versioning policy until persistence is actually implemented — rejected
Research into save-game schema versioning patterns showed the standard fix (schema_version field, stepwise migration functions, dedicated save DTOs distinct from runtime state) is cheap to adopt as policy before real save files exist, and expensive to retrofit once they do (existing save files in the wild constrain the fix). Decided to establish the requirement now, matching this project's general practice of deciding policy ahead of the code that will need it (e.g., decisions.jsonl itself, adopted before very many decisions existed).

Decision

Wrote docs/technical-architecture.md as the project-level (multi-repo) architecture document, distinct in scope from ARCHITECTURE.md (which stays scoped to in-repo module placement rules) -- cross-referenced from both directions. Established, as explicit policy: (1) game-rule outcomes are represented as GameEvent data returned from pe.tick rather than embedded in genre-agnostic physics logic; (2) shared engine modules (pe, render, io) stay in a single repository until a second, genre-distinct game begins active development, at which point they are extracted into an independently versioned, consumed package -- mirroring Bevy/Godot/Phaser's real structure; (3) built static assets require content-hashed filenames (not yet implemented); (4) the live demo is split into a pinned /play (stable) and directly-tracking /play-edge (latest) channel, promoted deliberately, following the stable/beta/nightly pattern documented in Rust's own release-channel RFC; (5) docs/decisions.jsonl continues serving as the system's migration record, matching Bevy's own migration-guide practice for a pre-1.0 engine; (6) Semantic Versioning is adopted for glemy's own version number once engine modules become an independently consumed package; (7) any future persisted game state requires an explicit schema_version field, stepwise migration functions, and dedicated save DTOs distinct from runtime Model types, established as policy ahead of any persistence implementation.

Verification

Every research question was answered against 2-4 independent, cited real sources (Bevy's own docs/migration guides, Godot's signal-system docs, Phaser framework docs, Martin Fowler's micro-frontends/feature-flag writeups, semver.org, caniuse.com, GitHub's own Pages limits documentation, Rust's release-channel RFC, and general cache-busting/save-versioning industry references), not asserted from memory. Findings were presented in chat for review before the formal document was written (the user's required two-phase workflow), and the user confirmed three specific points be explicitly highlighted (the GameEvent decoupling decision, the content-hashing + /play-edge channel requirements, and the save schema_version policy) before Phase 2 proceeded -- all three are present as explicit, separately-labeled sections in the resulting document.

Consequences

The project now has a written, cited, public-readable answer to 'how does this scale beyond one game' and 'what happens to a live demo during a deploy' -- questions that were previously only implicitly answered by the current single-game, single-repo state. Two concrete open action items follow directly from this decision and are not yet implemented: content-hashed static asset filenames, and the /play vs /play-edge channel split. Both are tracked in the new document's own requirements summary table, not just narrated in prose, so they don't require re-deriving from this decision text later.

References