Skip to content

Latest commit

 

History

History
112 lines (85 loc) · 17.4 KB

File metadata and controls

112 lines (85 loc) · 17.4 KB

Tasks

Foundation — 0.1.0 (released 2026-07-25)

  • Choose the TypeScript package manager and monorepo tool after evaluating publish workflow and Unity/Godot needs. (npm workspaces — ADR-0004)
  • Create the core package with strict TypeScript configuration. (packages/core, @diceforge-sdk/core)
  • Define domain schemas for die definitions, coin flips, RNG, roll requests, and immutable result records. (records.ts, rng/types.ts, notation/ast.ts)
  • Implement seeded and system RNG providers with tests. (xoshiro128** + golden tests — ADR-0005)
  • Implement initial dice notation parser and resolver with tests. (grammar v1 — ADR-0006)
  • Implement coin-flip resolver with tests.
  • Define event serialization and schema-versioning rules. (serialization.ts — ADR-0006)
  • Add lint, formatting, test, coverage, and CI workflows. (Biome, Vitest, .github/workflows/ci.yml)
  • Create one headless usage example. (examples/headless)
  • Record implementation decisions in DECISIONS.md as they are made. (ADR-0004..0006)

Remaining before tagging 0.1.0:

  • Push to a Git host and verify the CI workflow runs green on Node 20 and 24. (github.com/StephanTTM/DiceForgeSDK, public since 2026-07-26)
  • Publish to npm — shipped with the 0.1.0 release (see the 0.2.0 section).

Web proof of integration (complete)

  • Select a web renderer/physics approach through an ADR. (Three.js + outcome-first scripted tumble — ADR-0007)
  • Define renderer plugin contract and presentation lifecycle. (InteractionPresenter in core — ADR-0008)
  • Build a small browser demo with dice and coin flip interactions. (examples/web-demo, npm run demo:web)
  • Add reduced-motion and no-WebGL fallbacks. (DOM tile backend, prefers-reduced-motion, aria-live announcements)

Web presentation — 0.2.0 (released 2026-07-26)

  • Add one popular-framework example (React) consuming @diceforge-sdk/renderer-web. (examples/react-demo; also swaps in @diceforge-sdk/presenter-physics at runtime, showing the presenter contract is what the component depends on)
  • Theme/asset loading as an optional presentation concern. (DiceTheme + lazy glTF models with calibrated face tables, KayKit CC0 pack — ADR-0010)
  • Publish 0.1.0 to npm. (@diceforge-sdk/core + @diceforge-sdk/renderer-web live 2026-07-25; v0.1.0 tagged; trusted publishing configured; release workflow verified green and idempotent — ADR-0009)

Extensibility — 0.4.0 (released 2026-07-27)

  • Add capability discovery so an adapter can ask what a presenter supports instead of feature-detecting it. (PresenterCapabilities + presentationSupport() in core, declared per instance by the web renderer — ADR-0014)
  • Custom dice definitions. (defineDie + createDiceEngine({ dice }), 4d{fate}, any face count 2..1000, event schema v2 with a v1 read path — ADR-0015)
  • Notation extensions beyond grammar v1 (exploding dice, rerolls). (4d6!, 4d6r1, 4d6ro1; any modifier order, capped chains, extras recorded in rolled order — ADR-0016)
  • Replay support: re-present a stored record without re-resolving it. (SessionRecord, createSession, serializeSession/deserializeSession, replaySession; replay draws no randomness — ADR-0017)
  • Plugin-author documentation and a compatibility test kit a third-party renderer can run against. (@diceforge-sdk/testing: runner-agnostic conformance checks + the presenter-authoring guide; renderer-web runs it against itself — ADR-0014)
  • Decide whether the first-party dice ship as an optional @diceforge-sdk/assets-forge package or stay repository-only. (shipped as a package — ADR-0013; forgeTheme(forgeAssets({ color })) needs no copying, baseUrl still serves custom packs)

Motion — 0.5.0 (released 2026-07-28)

  • Physics-based presenter plugin. Record the trajectory headlessly, then remap the mesh inside the collider by a symmetry so the recorded face lands where the simulation's did (ADR-0018, accepted). Every face pair on all six shapes admits such a remap (symmetry.test.ts), and the shipped rotation tables cannot supply it.
    • Decide the engine and measure a real roll. (cannon-es; npm run physics — every shape settles every time in ~0.75 s, scatters 123–269 mm at p95, remaps at 0.0000° error, 28–37 kB of trajectory per roll)
    • Measure a bevelled hull from the shipped .glb. (npm run physics -- --hull=glb: simulates fine, but the remap fails on all 180 poses and it costs up to 500x more — the collider is the idealised solid and the model is cosmetic, which also frees custom art to have holes or missing faces)
    • Decide how the camera frames the roll, and what the simulation costs. (--tray=<mm>: walls cap the scatter, and dieWidth × (5 + 0.8√n) settles in ~1s at 96–100% seated. There is no pre-roll latency — simulating a whole roll costs 4–44 ms for 1–40 dice; the 0.7–1.5 s is the animation's length. The first measurement claimed a fixed tray meant the camera never moves; superseded by the framing item below.)
    • Ship it as @diceforge-sdk/presenter-physics. (simulateRoll + the symmetry remap, cannon-es, tray-framed, 10 tests; the collider is the idealised solid and the model is cosmetic, so a theme needs no symmetry data of its own)
    • Play the trajectory. (createPhysicsPresenter — three.js playback, remap applied before the first frame, dropped dice revealed on landing; coins, custom dice and no-WebGL delegate to renderer-web, and it passes the conformance suite with no skips)
    • Framing polish. (Rectangular tray shaped to the viewport — measured to cost nothing in settling — and the camera frames where the dice came to rest, centred on them, instead of the walls. Shaping the tray alone did not help: it makes the tray bigger, so the camera pulls back by the same amount.)
  • Run the visual regression suite in CI against platform-stable baselines. (a visual job inside the pinned mcr.microsoft.com/playwright image; npm run vrt:docker reproduces it, and a run from anywhere else is advisory rather than red)

Coin and guard-rails — 0.6.0 (released 2026-07-28)

  • Tie each die value to the numeral printed on the model, in CI. (forge-models.test.ts walks rotation → face → UV → atlas tile, and checks every tile is inked and distinct; proved by a generator-style fault that no other test caught)
  • Make the pre-publish tarball check repeatable. (npm run smoke — packs, installs and uses all five packages, ~20 s)
  • Physics coin flips. (simulateCoinFlip — cylinder collider whose faces are the outcomes, thrown into the shared tray; the model's calibrated pair seats it and a half-turn symmetry lands the recorded face; radius and thickness measured from the loaded model; rim landings re-thrown. 60/60 seeded flips flat on the recorded outcome at ~5 ms each; the presenter no longer swaps canvases for a coin)

Sound, a second engine, and a stable core — 0.7.0 (released 2026-07-31)

  • Record impacts in the simulation. (PhysicsImpact on PhysicsRoll/PhysicsFlip from cannon's collide events — time, body, felt/wall/die, closing speed in m/s at real scale; deterministic per seed, asserted)
  • Impact-driven synthesized knocks. (impactSchedule — pure, measured thresholds: 0.12 m/s floor, 55 ms per-body merge; Web Audio synthesis from filtered noise, lazy context in the click chain, silent without Web Audio — ADR-0020)
  • sound option on the physics presenter, default off, and a demo checkbox. (verified in-browser: sound off schedules nothing; a 3d6 roll schedules 7 knocks on one lazily-created context; a coin flip 2)
  • Tune the knock voices down. 1.0 blocker (ROADMAP's readiness checklist). First listen (product owner, 2026-07-28): the sound works but reads a bit high pitched. Fix shape: lower the material bandpass centres in audio.ts (MATERIALS — felt 950 / wall 2100 / die 3100 Hz today), keeping their order so felt stays dullest and die-on-die brightest; a longer decay would also soften the read. Constants are internal (ADR-0020), so retuning is free — but tune with the product owner listening, not ahead of them.

Story playback — web parity (0.7.0, shipped)

  • Reroll and explosion stories in both web presenters, matching Godot. (visualDiceForEvent returns the settled stage with rerolledFaces/exploded/bornOf; the WebGL renderer's authored story lives in webgl/story.ts, the physics presenter's multi-throw plan in presenter-physics/src/story.ts — both pure and headlessly tested; three VRT scenes pin the settled stages; the React demo grew one-click story buttons)
  • Custom-dice stories: a custom die falls back to tiles, which show only the settled stage — its rerolls and explosions have no motion anywhere yet.
  • Physics follow-up throws run in their own worlds, so a re-toss or a born die does not collide with resting dice and can settle closer than a real die could (or clip one in flight). Fix shape: seed the follow-up world with the resting dice as static bodies, and re-throw when the landing spot overlaps one.
  • Product owner's live look at the web stories (throw feel of the physics re-toss pickup, celebration timing) — constants are STORY in each story module.

Engine adapters (in progress — Godot first, per the product owner)

  • Decide how a non-TypeScript platform gets the core: a native port held to exported conformance vectors, bit for bit. (tools/conformance/export-vectors.mjspackages/testing/vectors/core-vectors.json, freshness-tested against the live core every run — ADR-0021)
  • Godot: headless engine as a GDScript addon. (adapters/godot/addons/diceforge — RNG, grammar v1.2, resolution, custom dice, coin; 57/57 vectors pass in Godot 4.7.1 — extended 2026-07-30 with degenerate seeds and hostile parse errors from the core's adversarial probes — and one flipped rotate constant fails 16, so the gate is real. A lone-surrogate seed is deliberately vector-exempt: JSON cannot carry unpaired surrogates in interchange, measured against Godot's parser)
  • Godot: posed presentation — the forge models in a scene, faces from the calibrated manifest. (presenter_3d.gd + demo/dice_demo.tscn: runtime-loads models and textures from packages/assets-forge, poses every die on its recorded value, darkens dropped dice, shows the coin; face_up() mechanically verifies every pose against the record — 4 seeded shots, 0 failures, screenshots reviewed)
  • Godot: rolling motion. (authored tumble per ADR-0007 — drop, bounce, free tumble easing into exactly the calibrated pose; dropped dice dim on landing; seedable motion for reproducible captures. Real physics measured impossible in GDScript: no manual stepping on PhysicsServer3Dtests/capability.gd asks the engine — so ADR-0018's record-then-replay waits on engine support or a GDExtension)
  • Godot: camera/theming polish for a real game scene, and percentile-pair + custom-dice presentation.
  • Godot: distribution bundle. (npm run godot:bundle composes addon + forge dice into the Asset Library layout with a zero-config presenter default; verified by unzipping into a fresh project and rolling the golden-vector record with no configuration. The Godot bundle workflow publishes it as an artifact and force-pushes the orphan godot-asset branch)
  • Godot: Asset Library submission. (needs the human owner: an assetlib account, the listing form pointed at the godot-asset branch, an icon, and screenshots — the bundle branch is already the exact layout it serves)
  • Run the Godot conformance scene in CI. (needs a Godot binary on the runner; the scene already exits nonzero on mismatch)
  • Unity: C# port of the core against the same vectors. (adapters/unity/Runtime — RNG, grammar v1.2, resolution, custom dice, coin, schema v2 records; 57/57 vectors pass in .NET 10, and the gate is real: one changed rotate constant fails every RNG vector, and reversing the keep/drop tie-break fails the one record vector with a tie. Pure C#, no UnityEngine types, so it is tested headlessly with dotnet run — no Unity licence needed)
  • Unity: package layout and an end-to-end sample scene. (.asmdef, UPM package.json, and a real import into a Unity project — needs Unity itself and the product owner at the desk. The engine is done and verified; this is the last mile.)
  • Unity: presentation — the forge models in a scene, posed from the calibrated manifest, as the Godot presenter does.

Next

  • Declare the core stable. (ADR-0022, 2026-07-30: grammar v1.2, event schema v2, the RNG and presenter contracts, and the conformance vectors are frozen — changes are additive-by-ADR only. Success-counting pools deferred by decision, recorded in the ADR, so the Unity port targets a fixed contract)

  • Harden the six-times-rejected fallback pose in simulateRoll/simulateCoinFlip. (2026-07-31. Reproduced deterministically instead of soaking for the 1-in-~300 tail: a tray far too small for its dice rejects every throw, so the fallback is reached on purpose — worst up-face margin measured at 7.6e-6, below the calibrated table's own 1e-6 precision, exactly as diagnosed. Fixed on both counts: the fallback now ranks settled throws above still-moving ones, and a die still propped up is eased onto its resting face over the last 12 frames, position following to the height a flat die rests at. Worst non-d4 margin after: 0.238, a 31,000x improvement; the coin gets the same treatment so it is never left on its rim. Five tests pin it, and reverting the one-character axis sign reproduces both old numbers exactly. Every VRT physics scene verified to take the unchanged success path, so no baseline moved.)

  • Guarantee the coin tumbles on entry. (the diagnosis held — isotropic spin reads as a drop 51 times in 60 — but flooring the diameter spin only got it to 40/60: the real lever was air time, so the coin is now tossed upward as well. PhysicsCoin.turnovers counts horizon crossings, the retry rejects fewer than two, and a rim-spinner longer than 3 s is re-thrown instead of watched. Measured: 120/120 seeds at ≥2 turnovers, worst duration 1.5 s, ~5.5 ms per flip)

  • Decide how a physics d4 reads. Found while fixing the fallback pose (2026-07-31), and it is a product question, not a bug to fix quietly. A tetrahedron resting on a face leaves its other three faces at identical elevation (Y = +1/3) by symmetry, so "which face is up" has no answer — measured, every d4 roll settles with an up-face margin of 1e-6 to 4e-6, which is the calibrated table's 6-decimal rounding, not physics. The rendered die is still correct: the remap puts the recorded numeral where the simulation's landed face was, so the numeral shown is the recorded one, and the smoke test passes because the recorded face inherits that slot structurally. But it is decided at the level of arithmetic noise, and a player looking at a settled d4 sees three slanted numerals with nothing marking which one counts. The non-physics renderer sidesteps this by posing the d4 in its calibrated orientation (a face straight up, balanced on a vertex) and viewing from a low angle — a pose gravity never produces. Options: adopt a real d4 reading convention (apex numeral, or the face resting down) and recalibrate; keep the current behaviour and document it; or have the physics presenter delegate d4s to the renderer's posed view. Needs the product owner. Note there is no physics d4 VRT scene today, so this has never been visually reviewed — add one alongside whatever is decided.

  • Extend the plugin contracts to the categories beyond presentation (physics, audio, transport) once a second implementation exists to shape them — ARCHITECTURE lists them, but nothing implements them yet.

Backlog

  • Theme/asset pack policy and licensing checklist. (ADR-0010, assets/LICENSES.md, CONTRIBUTING)
  • Add the missing boardgame_bits_texture.png so the KayKit D6_A/D6_B pip styles can be offered. (d6Style: "pips-a" | "pips-b")
  • Calibrate d10/d12 models so themes can cover every shape. (superseded: first-party set generated for every shape, tables exact by construction — ADR-0011)
  • Texture the first-party dice, then ship forgeTheme(). (five colours, textured coin, tools/blender/build_textures.py)
  • Give the percentile tens die its own 00–90 texture. (DieModelSet.tensTextureUrl, generated per colour)
  • Even out apparent die sizes. (modelSilhouetteScale equalizes each die's on-screen silhouette, measured from the loaded mesh)
  • Give procedural dice beveled edges, or retire them. (retired, along with the KayKit pack — ADR-0012)
  • Optional multiplayer transport plugin research. (beyond 1.0)
  • Success-counting pools (7d10>=8 — count dice that pass a target instead of summing; World of Darkness, Shadowrun, Year Zero). Post-1.0 by decision (ADR-0022): waits for a real integration to shape the dialect, then lands additively — target syntax errors today, per-die success flags and a group success count ride a schema bump, keep/drop rejected in pool groups, vectors grow with it.
  • Browser-based visual regression testing for the renderer. (npm run vrt, 17 scenes including six for the physics presenter, Playwright + committed baselines)

Task maintenance

Move work between sections as it changes. Mark completed tasks only after code, tests, and relevant documentation are present. Add scoped tasks rather than leaving vague implementation notes.