chogui-three
v0.2.0
Published
Framework-agnostic 3D game-device engine (dice, spintop, board) that settles physics/tween animations onto a predetermined outcome. The three.js companion to chogui; consumed lazily.
Readme
chogui-three
The three.js companion to chogui — a framework-agnostic, vanilla-TS 3D game-device engine that settles physics/tween animations onto a predetermined outcome. The value is decided first (in the host's story engine); the 3D only animates toward it, so the displayed result is correct by construction and every device degrades gracefully when WebGL is unavailable.
Consumed lazily
The engine is reached only through a dynamic import() (in chogui's use3DDevice
hook), so the heavier scene/physics modules — and their three / cannon-es
deps — stay out of any host's SSR and main bundle. The pure tween + asset-path
helpers carry no 3D dependency.
const { containerRef, failed } = use3DDevice((mod, container) => {
const stage = new mod.Stage(container, /* … */);
const device = mod.createDevice("spintop", { value, actions /* … */ });
stage.add(device);
stage.settle();
return stage; // a Disposable — disposed on unmount / fallback
}, [value]);
// `failed` flips on load/render error → render a non-3D fallback that still
// performs the device's function (show the label / select the choice).Stage
Stage owns one three scene/camera/lights/renderer + an rAF loop, an on-demand
cannon-es physics world, a device registry, IntersectionObserver pause/resume,
raycast picking for interactive devices, and settle detection. Mount devices with
add(device); settle(cb) runs to rest, showImmediately() skips the animation
(the prefers-reduced-motion / sandbox path); dispose() tears everything down.
Device contract
A device implements Device (mount/update/settle/orientToOutcome/dispose,
optional onSelect). Use it one of two ways:
- Directly —
new SpinTopDevice(props)/new BoardDevice(props), thenstage.add(device). This is what chogui's tags do. - Through the string registry (opt-in) —
registerDevice(kind, factory)thencreateDevice(kind, props); alsohasDevice(kind)/listDeviceKinds().
The barrel has no side effects on import — devices are not auto-registered, so
the package stays tree-shakeable (sideEffects: false) and a consumer pulls in only
the devices it uses. (This mirrors chogui, which also composes packs
explicitly rather than registering on import.)
Built-in devices:
spintop— an N-sided spinning top (teetotum) oncannon-es. Spins upright, topples on a random axis, and settles. The tumble is decorative;resolveSegmentIndexmaps the resolved value to a face index and the story conveys the result. Faces show author-supplied labels (or face numbers).board— a grid of clickable cells with a piece that tweens to a target cell (no physics). Layouts:ring/half/track(defaultLayoutpicksring).onSelect(i)reports the chosen cell for choice routing.
Assets
Devices load most assets (surface .webp textures, hit .mp3 sounds, the
pawn piece) via bundler ?url imports, so a Vite host resolves and serves
them automatically. The dice device additionally fetches its theme textures
and sounds at runtime from a served directory, resolved by
resolveAssetPath(): an explicit assetPath prop → DEFAULT_ASSET_PATH
(/assets/chogui-three/) → CDN_ASSET_PATH (jsDelivr) with preferCdn.
Vendor the served assets into a host with the package-owned script (postinstall is a good home):
node node_modules/chogui-three/scripts/copy-assets.mjs # → public/assets/chogui-three/
node node_modules/chogui-three/scripts/copy-assets.mjs --dest static/chogui # custom destHosts that skip the copy can rely on the CDN fallback instead.
Dice (vendored engine)
The MIT dice-box-threejs source is vendored under src/dice-box/ (TypeScript
now), wrapped by DiceDevice on the Device contract — the [dice] tag runs
on this engine; the upstream @3d-dice/dice-box-threejs dependency is gone.
Remaining roadmap: a 2-faced coin riding the die as a d2 face.
License & attribution
This package vendors and adapts dice-box-threejs (MIT) and bundles a CC0 coin
model — see THIRD_PARTY_NOTICES.md. package.json
declares license: MIT and files; the formal top-level LICENSE text and the
private flag flip land with the coordinated publish (the
publishable-story-tag-library change), where chogui and chogui-three ship
together on a publish-together version policy.
