@enigma-lake/forge-play-controller-sdk
v1.0.0
Published
The ZOOT play controller: one package, modular parts, per-game adapters.
Keywords
Readme
@enigma-lake/forge-play-controller-sdk
The ZOOT play controller: one package, modular parts, per-game adapters.
It replaces the seven forked @enigma-lake/*-play-controller-sdk packages
(dice, tower, mines, multi, plinko, crash, beerpong-v2). Those were copy-paste
forks of each other — the buttons, inputs, currency selector, coin icons and
free-round modals were identical, and only the state machine and the bar's
composition differed. Here the shared part is core/, the differing part is an
adapter, and the bar itself is a list of parts.
See docs/design.md for the design this is built from.
Using it
import { PlayController } from "@enigma-lake/forge-play-controller-sdk";
import { tower } from "@enigma-lake/forge-play-controller-sdk/adapters/tower";
import "@enigma-lake/forge-play-controller-sdk/style.css";
<PlayController adapter={tower} config={CONTROLLER} model={model} handlers={handlers}>
{scene}
</PlayController>;adapteris imported by subpath and bound statically, so the six you do not bind — and the parts only they use — leave your bundle.configis plain data (below).modelandhandlersare the game's runtime.- Children are the scene. The bar renders as a sibling layer over it.
Adapters
| id | shape | autoplay | collect | selectors |
| --- | --- | --- | --- | --- |
| bare | one play button | — | — | — |
| dice | one button, press and hold | — | — | — |
| multi | two sides (LEFT / RIGHT) | fire-and-forget, 100 × 1000ms | — | yes |
| tower | multi-step, bank or continue | selection, 10 × 1500ms | yes | yes |
| mines | grid reveal, pre-picked tiles | selection, 25 × 1500ms | yes | yes (bomb count) |
| plinko | rapid fire, overlapping rounds | fire-and-forget, 99 × 500ms | — | yes (risk + rows) |
| crash | the game draws its own button | — | game's | — |
beerpong-v2 is multi plus behaviour flags, not an eighth adapter.
Parts
quickPick · amount · selector · autoplay · play · cashout ·
doubleOrNothing · freeRounds · widgets.
A game turns one off, re-orders them, or registers its own:
import { definePart, registerPart } from "@enigma-lake/forge-play-controller-sdk";
registerPart(definePart({ id: "ticket", Component: TicketBar }));
// then: config.parts = { ticket: {} }Config
const CONTROLLER: ControllerConfig = {
layout: { placement: "auto", panelBottom: "0px" },
parts: { doubleOrNothing: false, autoplay: { maxCount: 20 } },
behaviour: { simplified: false, minPlayIntervalMs: 500 },
theme: { panelBg: "#000637", dropdownBg: "#150C47", selectorColors: RISK_COLORS },
i18n: { language: "en", overrides: { cashout: "CASH OUT" } },
onLayout: (box) => reserve(box),
};placement is bottom (centred pill), bottom-full (mobile sheet),
left-rail (374px, full height), centered, or auto — the classic layout: a
rail on a wide landscape window, a sheet otherwise.
Three rules a consumer must know
Everything the bar renders or edits is whole currency units. $1.50 is
1.5, never150. The only cents-shaped field isfreeRounds.stakeCents, which is hundredths of the displayed unit and which the bar divides by 100. The wire conversion belongs to the game's platform layer, not here — and for a fun currency (usd_exchange_rate = 0) the factor is 1, not 100.data-roleanddata-partare the targeting surface. Every interactive element carries adata-role; every part's root carriesdata-part. Class names are hashed and will change. Games used to match on[class*="UI-module__betForm"], and broke on every release.The bar reports its own box.
config.onLayoutanduseControllerBox()give the placement and what to reserve, so nothing has to measure the DOM or hard-code the 1074px breakpoint.
Development
npm install
npm test # vitest
npm run typecheck
npm run lint
npm run build # dist/: ESM + CJS + .d.ts + style.css
npm run playground # every adapter against every lock scenarioThe playground is the review surface: pick an adapter, pick a scenario (guest, in flight, invalid amount, round live, collect in flight, free rounds offered and engaged, external operator…), and watch the reported box and the host events.
