Rename pe to physics, pair render colors with entities instead of a parallel list, give render.Canvas a public constructor, and fix doc-comment drift left by decision 0047
Context
Decision 0047 formalized the Core API boundary (pe/render/io vs. glemy/games/<name>) but was reviewed critically afterward, on request, against the actual concrete API surface rather than just the boundary drawing. Four real problems were found: (1) pe's own module doc comment still described itself as 'Physics Engine & Logic', a name stale as soon as decision 0047 moved the 'Logic' half out -- inaccurate, and confusable with the umbrella term 'Core API' docs/technical-architecture.md already uses for the whole pe+render+io surface. (2) render_entities_to_bytes/render_entities_to_canvas took entities and colors as two separate same-length lists kept in sync only by caller convention -- a real, reproducible bug, not a hypothetical: render_ffi.mjs's drawEntities indexed colors[i] alongside entities.forEach, so a caller-side length mismatch type-checked fine in Gleam and only surfaced as an uncaught JS exception at runtime. (3) render.gleam declared pub type Canvas with zero public constructor -- the only function that could build one from a real <canvas> element, get_canvas_element, was private inside glemy/game.gleam, making render unusable as a standalone Core API module by any consumer other than glemy/game. (4) io.gleam's header doc comment still said input was read 'for glemy/pe to read once per frame', inaccurate since pe never read input directly even before this session -- glemy/game's loop always did.
Options considered
Decision
Renamed glemy/pe (module, directory, and every test file) to glemy/physics throughout src/ and test/, and updated every import path and qualified reference accordingly (mechanical, ~24 files) plus every doc comment that named the module by its old name -- decisions.jsonl and development-plan.jsonl entries predating this rename were left untouched as accurate historical record. Replaced render_entities_to_bytes/render_entities_to_canvas's separate entities/colors parameters with a single List(ColoredEntity) (ColoredEntity = #(Entity, #(Float, Float, Float))), updated render_ffi.mjs's drawEntities to destructure [entity, [r, g, b]] pairs directly (Gleam tuples compile to plain JS arrays, confirmed by inspecting the compiled runtime, so this needed no wrapper), and renamed games/tiers.entity_colors to games/tiers.colored_entities to build the zipped list at the source. Added render.get_canvas(id: String) -> Canvas (@target(javascript), wrapping a getCanvasElement FFI function moved from game_ffi.mjs into render_ffi.mjs, where Canvas's owning module also owns its construction), and updated glemy/game.gleam to call it instead of its own former private copy. Fixed io.gleam's stale header doc comment to describe input as read by a runner (glemy/game's loop) and folded into a game's own Input, not read by glemy/physics directly.
Verification
gleam test and gleam test --target javascript both pass with identical counts to before this change (164 Erlang / 187 JavaScript) -- the rename and API reshaping changed no runtime behavior, only structure and signatures, confirmed by every existing assertion still passing unchanged in substance (render_test.gleam's assertions were rewritten to the new call shape but assert the identical pixel outputs). deno task check-warnings passes after updating the baseline (51/51, down from 52 -- one warning genuinely disappeared: render.gleam's Entity import is no longer flagged unused on the Erlang target, since ColoredEntity's type alias uses it ungated, a real incidental improvement, not just a renamed path) -- every other diff was confirmed as the same already-documented @target(javascript) target-gating artifact, just under renamed paths. deno task browser-check passes for real (182 real requestAnimationFrame frames, a real click-and-hold grew the entity count 4 -> 5, a real entity spawned near the expected world-x, zero page errors) -- this also directly exercises render.get_canvas for the first time as a public entry point, since glemy/game's main() now calls it exclusively to obtain its canvas.
Consequences
glemy/physics is now an accurate, unambiguous name that won't collide with 'Core API' as a term. render_entities_to_bytes/render_entities_to_canvas's caller-side mismatch bug class is now unrepresentable rather than merely undocumented -- every existing and future caller (currently only glemy/game.gleam) must construct the pairing correctly by construction. render.gleam is now actually usable as a standalone module by a future runner: it owns both Canvas and the one way to construct one, with no dependency on glemy/game's private FFI. This is the same category of work as decision 0047 (in-repo boundary hardening of already-real code, not new capability) and does not itself change docs/technical-architecture.md §2.3's extraction trigger.
References
- src/glemy/physics.gleam
- src/glemy/physics/entity.gleam
- src/glemy/render.gleam
- src/glemy/render_ffi.mjs
- src/glemy/game.gleam
- src/glemy/game_ffi.mjs
- src/glemy/io.gleam
- src/glemy/games/tiers.gleam
- test/glemy/render_test.gleam
- docs/technical-architecture.md
- ARCHITECTURE.md
- https://rust-lang.github.io/api-guidelines/