This documents framework v1, which is deprecated. Games already on it keep running and their authors can keep editing them, but new games are created on the current framework — start with React frontends.
Source files
A Boardweaver game is a tree of TypeScript files under /src, entered at /src/game.ts. The entry point imports from "boardweaver" and exports gameConfig plus the game-logic hooks; everything else (pieces, spaces, shared helpers) lives in sibling files it imports. See core-concepts for the full layout.
The build bundles /src as a real module graph from the entry point, so files import each other by relative path (./pieces/XToken) or absolute /src path (/src/pieces/XToken). The only off-tree import allowed is the "boardweaver" runtime — bare imports (there's no node_modules) fail the build.
Editing these files
Two surfaces write the same tree. Both edit any /src/* file plus /theme.json, and both can also rename or delete gameplay images at /art/<Key>, as the images section describes.
- The MCP server:
write_file/patch_fileto edit,rename_fileto rename or move a file, anddelete_fileto remove one, driven by an agent.rename_filedoes not rewrite imports of the old path. - The studio's Code view:
/studio/game/<slug>/code, a file tree and editor for writing the game by hand. Edits autosave into the worktree as you type and the tree rebuilds after each save, so build errors surface while you're still in the file that caused them. Double-click a file in the tree to rename it, and use the trash button above the editor to delete the open file./src/game.tsand/src/frontend.tsxcannot be renamed or deleted there.
Either way, edits land as pending worktree changes. They don't reach players until you commit.
// /src/pieces/XToken.ts
import { PieceDef } from "boardweaver";
export class XToken extends PieceDef { /* see `pieces` */ }
// /src/spaces/GridCell.ts
import { SpaceDef } from "boardweaver";
export class GridCell extends SpaceDef { /* see `spaces` */ }
// /src/game.ts — the required entry point
import {
ApplyActionsFn,
GameStateConfigFn,
GetPlayerScoresFn,
GetSelectableItemsFn,
IsGameOverFn,
} from "boardweaver";
import { XToken } from "./pieces/XToken";
import { GridCell } from "./spaces/GridCell";
export const gameConfig: GameStateConfigFn = () => ({
startingPlayerStrategy: "Random",
spaces: { "0": new GridCell({ x: 0, y: 0 }) },
pieces: {},
pieceDefs: { "x-token": new XToken({ order: 1 }) },
});
export const getSelectableItems: GetSelectableItemsFn = (state) => [];
export const applyActions: ApplyActionsFn = (state, action) => {};
export const getPlayerScores: GetPlayerScoresFn = (state) => ({});
export const isGameOver: IsGameOverFn = (state, scores) => false;
Pieces and spaces follow a strict file layout:
- One
PieceDefsubclass per file under/src/pieces/. OneSpaceDefsubclass per file under/src/spaces/. Do not bundle multiple defs into a single file. - Filename matches the class name (e.g. class
XToken→/src/pieces/XToken.ts, classGridCell→/src/spaces/GridCell.ts), as in the examples above. - No barrel files. Do not create
/src/pieces/index.ts,/src/spaces/index.ts, or any other re-export aggregator./src/game.tsand other files import each def directly by its path:import { XToken } from "./pieces/XToken".
Required exports
| Export | Type | Notes |
|---|---|---|
gameConfig |
GameStateConfigFn |
Returns the initial config. Called once. |
getSelectableItems |
GetSelectableItemsFn |
Returns the viewer-visible click targets. |
applyActions |
ApplyActionsFn |
Mutates state in place. |
getPlayerScores |
GetPlayerScoresFn |
Returns Scores map. |
isGameOver |
IsGameOverFn |
Returns a boolean. |
Optional exports
| Export | Type | Default |
|---|---|---|
preGameInitialization |
PreGameInitializationFn |
not run |
getButtons |
GetButtonsFn |
no buttons |
getLayout |
GetLayoutFn |
{ type: "radial" } |
renderOverlay |
RenderOverlayFn |
no overlay |
See game-logic for each hook's signature and contract; see layout-overlays for getLayout and renderOverlay.
gameConfig return shape
GameStateConfigFn is (params: { numPlayers: number }) => GameStateConfigObject. The returned object:
type GameStateConfigObject = {
startingPlayerStrategy: "Random" | "All";
pieces: Record<string, PieceDef>; // top-level pieces, instantiated at game start
spaces: Record<string, SpaceDef>; // top-level spaces, instantiated at game start
player?: {
spaces?: Record<string, SpaceDef>; // per-player spaces (one set per seat)
publicState?: Record<string, unknown>; // default per-player public state
privateState?: Record<string, unknown>; // default per-player private state
def?: PlayerDef; // optional per-player Def (rare)
};
pieceDefs?: Record<string, PieceDef>; // register kinds used only via state.addPiece
spaceDefs?: Record<string, SpaceDef>; // register kinds used only via state.addSpace
metaData?: Record<string, unknown>; // initial gameState.metaData
scoreLabels?: string[]; // labels for the score columns shown in UI
};
Map keys ("0", "1", ...) become the initial pieceId / spaceId. Per-player spaces synthesize ids ${playerId}/space/${key}.
Gotchas
- Every value in
pieces/spacesMUST be aPieceDef/SpaceDefinstance. Raw object literals are rejected. - A
kindused only at runtime (viastate.addPiece(id, new Foo())) MUST also appear inpieceDefsorspaceDefs, otherwiseaddPiecethrows"PieceDef kind ... is not registered". - Imports resolve only to other
/srcfiles (relative or absolute/srcpath) and the"boardweaver"runtime. There's nonode_modules, so a bare import likeimport x from "pieces/Foo"fails the build — use"./pieces/Foo"or"/src/pieces/Foo".