system
400.wtf

400.wtf / system / docs · docs/ADR/0005-canvas-kit-functional-core.md

ADR-0005 — Canvas 2D kit: functional core, optional facade

Status: Accepted (documents the existing decision)

Context

Canvas 2D is the workhorse medium for artifacts (weaver today; several roadmap candidates). Every canvas piece needs the same plumbing: DPR-correct sizing, resize handling, a delta-clamped rAF loop with cleanup, and basic draw calls.

Problem

Provide shared canvas plumbing that speeds up artifact authoring without becoming a framework that artifacts must fight.

Decision

src/utils/canvas/ exposes a functional core — plain functions over a Canvas2D struct (createCanvas2D, resizeToViewport, clearCanvas, startLoop, onWindowResize, draw helpers) — plus an optional chainable Sketch facade over the same core. Artifacts may also ignore all of it and touch the raw context; the kit is a convenience, not a contract.

Alternatives considered

  • p5.js / processing-style dependency: huge surface, global-mode footguns, ~1 MB; the kit covers the needed 5% in ~180 lines.
  • Class-based mini-engine (scene graph, entities): premature; artifacts differ too much (IK spider vs. logic puzzle) for a shared object model.
  • Nothing (copy-paste per artifact): where the project started; DPR and loop-cleanup bugs are exactly the kind that copy-paste multiplies.

Tradeoffs

  • (+) Small, tree-shakeable, fully typed; the loop’s delta clamping (MAX_DELTA = 1/60) prevents tab-switch physics explosions by default.
  • (+) Functional core is trivially testable and framework-agnostic.
  • (−) Two APIs for one capability (core + Sketch) is a documentation cost; Sketch currently has zero adopters — it’s speculative (code-quality delete list: adopt in the next artifact or delete).
  • (−) elapsed in the loop is computed from frameIndex * MAX_DELTA (fixed-step assumption) — drifts from wall-clock time under throttling; fine for art, worth a comment.

Consequences

  • The scaffold (artifacts.md R2) stamps canvas-kit boilerplate, making the kit the default authoring path.
  • The lifecycle composable (artifacts.md R3) will wrap startLoop with visibility/intersection pausing — extend the kit, don’t fork it.

Future evolution

  • Decide Sketch’s fate with the next canvas artifact (adopt or delete; don’t carry).
  • Open-source extraction is parked in future-ideas.md behind the “two strangers asked” trigger.