dreamboard 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EYYWGWTO.js → chunk-2RCUHMGL.js} +343 -1911
- package/dist/chunk-2RCUHMGL.js.map +1 -0
- package/dist/{chunk-MOVHYB6E.js → chunk-GN7232BY.js} +1025 -695
- package/dist/chunk-GN7232BY.js.map +1 -0
- package/dist/{chunk-BMYC6772.js → chunk-H3O43F5P.js} +9765 -3068
- package/dist/chunk-H3O43F5P.js.map +1 -0
- package/dist/dev-host/dev-api-proxy-plugin.ts +330 -0
- package/dist/dev-host/dev-diagnostics.ts +62 -0
- package/dist/dev-host/dev-fallback-stylesheet.ts +50 -0
- package/dist/dev-host/dev-host-controller.ts +686 -0
- package/dist/dev-host/dev-host-player-query.ts +17 -0
- package/dist/dev-host/dev-host-session-transport.ts +52 -0
- package/dist/dev-host/dev-host-storage.ts +56 -0
- package/dist/dev-host/dev-log-relay-plugin.ts +469 -0
- package/dist/dev-host/dev-runtime-config.ts +14 -0
- package/dist/dev-host/dev-runtime-platform.ts +419 -0
- package/dist/dev-host/dev-virtual-modules-plugin.ts +63 -0
- package/dist/dev-host/host-main.css +182 -0
- package/dist/dev-host/host-main.tsx +754 -0
- package/dist/dev-host/index.html +56 -0
- package/dist/dev-host/plugin-main.ts +55 -0
- package/dist/dev-host/plugin.html +24 -0
- package/dist/dev-host/start-dev-server.ts +138 -0
- package/dist/dev-host/virtual-modules.d.ts +27 -0
- package/dist/dist-FEPN3BDN.js +16543 -0
- package/dist/dist-FEPN3BDN.js.map +1 -0
- package/dist/{dist-WJRJNFLI.js → dist-FRURQI7Q.js} +4 -2
- package/dist/index.js +3 -3
- package/dist/internal.js +159 -124
- package/dist/internal.js.map +1 -1
- package/dist/runtime-packages/tailwind-config/shared-styles.css +146 -0
- package/dist/runtime-packages/ui/src/components/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/components/accordion.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/actions.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/artifact.tsx +147 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/branch.tsx +215 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/canvas.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/chain-of-thought.tsx +228 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/code-block.tsx +179 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/confirmation.tsx +158 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/connection.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/context.tsx +408 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/controls.tsx +18 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/conversation.tsx +97 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/edge.tsx +140 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/image.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/inline-citation.tsx +287 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/loader.tsx +96 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/message.tsx +463 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/node.tsx +71 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/open-in-chat.tsx +365 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/panel.tsx +15 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/plan.tsx +142 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/prompt-input.tsx +1380 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/queue.tsx +274 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/reasoning.tsx +182 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/response.tsx +22 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/shimmer.tsx +64 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/sources.tsx +77 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/suggestion.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/task.tsx +87 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/tool.tsx +165 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/toolbar.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/ai-elements/web-preview.tsx +263 -0
- package/dist/runtime-packages/ui/src/components/alert-dialog.tsx +157 -0
- package/dist/runtime-packages/ui/src/components/alert.tsx +66 -0
- package/dist/runtime-packages/ui/src/components/avatar.tsx +53 -0
- package/dist/runtime-packages/ui/src/components/badge.tsx +46 -0
- package/dist/runtime-packages/ui/src/components/button-group.tsx +83 -0
- package/dist/runtime-packages/ui/src/components/button.tsx +65 -0
- package/dist/runtime-packages/ui/src/components/card.tsx +92 -0
- package/dist/runtime-packages/ui/src/components/carousel.tsx +241 -0
- package/dist/runtime-packages/ui/src/components/collapsible.tsx +33 -0
- package/dist/runtime-packages/ui/src/components/command.tsx +184 -0
- package/dist/runtime-packages/ui/src/components/context-menu.tsx +252 -0
- package/dist/runtime-packages/ui/src/components/counter.tsx +11 -0
- package/dist/runtime-packages/ui/src/components/dialog.tsx +146 -0
- package/dist/runtime-packages/ui/src/components/drawer.tsx +132 -0
- package/dist/runtime-packages/ui/src/components/dropdown-menu.tsx +257 -0
- package/dist/runtime-packages/ui/src/components/form.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/header.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/hover-card.tsx +44 -0
- package/dist/runtime-packages/ui/src/components/input-group.tsx +178 -0
- package/dist/runtime-packages/ui/src/components/input.tsx +21 -0
- package/dist/runtime-packages/ui/src/components/item.tsx +193 -0
- package/dist/runtime-packages/ui/src/components/label.tsx +24 -0
- package/dist/runtime-packages/ui/src/components/navigation-menu.tsx +168 -0
- package/dist/runtime-packages/ui/src/components/popover.tsx +55 -0
- package/dist/runtime-packages/ui/src/components/progress.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/radio-group.tsx +45 -0
- package/dist/runtime-packages/ui/src/components/resizable.tsx +56 -0
- package/dist/runtime-packages/ui/src/components/scroll-area.tsx +58 -0
- package/dist/runtime-packages/ui/src/components/select.tsx +187 -0
- package/dist/runtime-packages/ui/src/components/separator.tsx +28 -0
- package/dist/runtime-packages/ui/src/components/skeleton.tsx +13 -0
- package/dist/runtime-packages/ui/src/components/sonner.tsx +25 -0
- package/dist/runtime-packages/ui/src/components/spinner.tsx +16 -0
- package/dist/runtime-packages/ui/src/components/switch.tsx +31 -0
- package/dist/runtime-packages/ui/src/components/table.tsx +137 -0
- package/dist/runtime-packages/ui/src/components/tabs.tsx +74 -0
- package/dist/runtime-packages/ui/src/components/textarea.tsx +23 -0
- package/dist/runtime-packages/ui/src/components/theme-provider.tsx +73 -0
- package/dist/runtime-packages/ui/src/components/tooltip.tsx +61 -0
- package/dist/runtime-packages/ui/src/components/ui/file-upload.tsx +195 -0
- package/dist/runtime-packages/ui/src/hooks/.gitkeep +0 -0
- package/dist/runtime-packages/ui/src/index.ts +68 -0
- package/dist/runtime-packages/ui/src/lib/utils.ts +6 -0
- package/dist/runtime-packages/ui-host-runtime/src/actor-principal.ts +68 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-controls.tsx +359 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback-toaster.tsx +274 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-feedback.tsx +214 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/host-session-metadata.tsx +135 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/index.ts +5 -0
- package/dist/runtime-packages/ui-host-runtime/src/components/perf-overlay.tsx +194 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-controls.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-feedback.tsx +1 -0
- package/dist/runtime-packages/ui-host-runtime/src/host-session-transport.ts +162 -0
- package/dist/runtime-packages/ui-host-runtime/src/index.ts +2 -0
- package/dist/runtime-packages/ui-host-runtime/src/logger.ts +11 -0
- package/dist/runtime-packages/ui-host-runtime/src/perf.ts +253 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-bridge.ts +195 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-health-check.ts +138 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-messages.ts +159 -0
- package/dist/runtime-packages/ui-host-runtime/src/plugin-session-gateway.ts +524 -0
- package/dist/runtime-packages/ui-host-runtime/src/runtime/index.ts +12 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/projection-to-snapshot.ts +122 -0
- package/dist/runtime-packages/ui-host-runtime/src/screenshot/static-store-api.ts +26 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress-controller.ts +477 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-ingress.ts +209 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-live-runtime.ts +112 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-model.ts +318 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-projection.ts +211 -0
- package/dist/runtime-packages/ui-host-runtime/src/session-state-reducer.ts +814 -0
- package/dist/runtime-packages/ui-host-runtime/src/sse-manager.ts +334 -0
- package/dist/runtime-packages/ui-host-runtime/src/unified-session-store.ts +180 -0
- package/dist/scaffold/assets/static/app/tsconfig.framework.json +3 -1
- package/dist/scaffold/assets/static/ui/tsconfig.framework.json +7 -19
- package/package.json +52 -17
- package/skills/dreamboard/SKILL.md +41 -75
- package/LICENSE +0 -89
- package/NOTICE +0 -1
- package/dist/chunk-BMYC6772.js.map +0 -1
- package/dist/chunk-EYYWGWTO.js.map +0 -1
- package/dist/chunk-MOVHYB6E.js.map +0 -1
- package/dist/scaffold/assets/static/ui/App.tsx +0 -22
- package/dist/src-CUL7EGGG.js +0 -634
- package/dist/src-CUL7EGGG.js.map +0 -1
- package/skills/dreamboard/references/authoring-lifecycle.md +0 -102
- package/skills/dreamboard/references/board-surfaces.md +0 -36
- package/skills/dreamboard/references/board-topology.md +0 -443
- package/skills/dreamboard/references/boards-and-topology.md +0 -100
- package/skills/dreamboard/references/card-actions.md +0 -107
- package/skills/dreamboard/references/cli.md +0 -120
- package/skills/dreamboard/references/core-concepts.md +0 -514
- package/skills/dreamboard/references/custom-renderers.md +0 -26
- package/skills/dreamboard/references/derived-values.md +0 -55
- package/skills/dreamboard/references/effects.md +0 -111
- package/skills/dreamboard/references/game-contract.md +0 -89
- package/skills/dreamboard/references/game-definition.md +0 -89
- package/skills/dreamboard/references/game-shell.md +0 -80
- package/skills/dreamboard/references/hand-surfaces.md +0 -33
- package/skills/dreamboard/references/index.md +0 -112
- package/skills/dreamboard/references/inputs-and-targets.md +0 -160
- package/skills/dreamboard/references/interactions.md +0 -158
- package/skills/dreamboard/references/manifest-fields.md +0 -137
- package/skills/dreamboard/references/manifest.md +0 -81
- package/skills/dreamboard/references/package-surfaces.md +0 -69
- package/skills/dreamboard/references/phases.md +0 -145
- package/skills/dreamboard/references/prompts-and-choices.md +0 -55
- package/skills/dreamboard/references/quickstart.md +0 -71
- package/skills/dreamboard/references/rule-authoring.md +0 -144
- package/skills/dreamboard/references/setup-bootstrap.md +0 -78
- package/skills/dreamboard/references/stages-and-zones.md +0 -59
- package/skills/dreamboard/references/static-views.md +0 -67
- package/skills/dreamboard/references/table-queries-and-ops.md +0 -103
- package/skills/dreamboard/references/testing-bases.md +0 -147
- package/skills/dreamboard/references/testing-generated-contracts.md +0 -142
- package/skills/dreamboard/references/testing-runtime-assertions.md +0 -124
- package/skills/dreamboard/references/testing-scenarios.md +0 -148
- package/skills/dreamboard/references/testing-ui-tests.md +0 -174
- package/skills/dreamboard/references/testing.md +0 -161
- package/skills/dreamboard/references/ui-architecture.md +0 -137
- package/skills/dreamboard/references/ui-components.md +0 -34
- package/skills/dreamboard/references/views.md +0 -72
- package/skills/dreamboard/references/workspace-layout.md +0 -136
- /package/dist/{dist-WJRJNFLI.js.map → dist-FRURQI7Q.js.map} +0 -0
package/dist/src-CUL7EGGG.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../packages/testing/src/definitions.ts","../../../packages/testing/src/create-expect-api.ts","../../../packages/testing/src/create-test-runtime.ts"],"sourcesContent":["export type TestRunner = \"reducer\" | \"embedded\" | \"browser\";\n\nexport type InteractionDescriptorLike = {\n interactionId?: string;\n surface?: string;\n kind?: string;\n available?: boolean;\n unavailableReason?: string;\n context?: {\n to?: string;\n };\n} & Record<string, unknown>;\n\nexport type SnapshotMatcherHandler = (\n name: string | undefined,\n actual: unknown,\n) => void;\n\nexport type RejectionExpectation = {\n errorCode?: string;\n message?: string | RegExp;\n};\n\nexport type ExpectMatchers = {\n toBe: (expected: unknown) => void;\n toEqual: (expected: unknown) => void;\n toMatchObject: (expected: Record<string, unknown>) => void;\n toBeDefined: () => void;\n toBeUndefined: () => void;\n toBeNull: () => void;\n toContain: (expected: unknown) => void;\n toContainEqual: (expected: unknown) => void;\n toHaveLength: (expected: number) => void;\n toBeGreaterThan: (expected: number) => void;\n toBeGreaterThanOrEqual: (expected: number) => void;\n toThrow: (predicate?: string | RegExp | ((error: Error) => boolean)) => void;\n toMatchSnapshot: (filename?: string) => void;\n toRejectWith: (expected: RejectionExpectation) => Promise<void>;\n toHaveInteraction: (\n interactionId: string,\n opts?: Partial<InteractionDescriptorLike>,\n ) => void;\n toBeGatedBy: (reason: string, opts?: { interactionId?: string }) => void;\n toBeActiveFor: (playerId: string, opts?: { interactionId?: string }) => void;\n not: {\n toHaveInteraction: (interactionId: string) => void;\n };\n};\n\nexport type ExpectFn = (actual: unknown) => ExpectMatchers;\n\nexport interface BaseContext<PlayerId extends string = string> {\n game: {\n start(): Promise<void>;\n submit(\n playerId: PlayerId,\n interactionId: string,\n params?: unknown,\n ): Promise<void>;\n };\n players(): readonly PlayerId[];\n seat(index: number): PlayerId;\n}\n\nexport interface SharedScenarioContext<\n PlayerId extends string = string,\n StateName extends string = string,\n View = unknown,\n Descriptor extends InteractionDescriptorLike = InteractionDescriptorLike,\n> extends BaseContext<PlayerId> {\n state(): StateName;\n view(playerId: PlayerId): View;\n interactions(playerId: PlayerId): readonly Descriptor[];\n expect: ExpectFn;\n}\n\nexport interface BaseDefinition {\n id: string;\n seed?: number;\n players?: number;\n setupProfileId?: string | null;\n extends?: string;\n setup: (ctx: BaseContext) => void | Promise<void>;\n}\n\nexport interface ScenarioDefinition<\n Runners extends readonly TestRunner[] = readonly [\"reducer\"],\n PhaseName extends string = string,\n StageName extends string = string,\n> {\n id: string;\n description?: string;\n from: string;\n runners?: Runners;\n phase?: PhaseName;\n stage?: StageName;\n when: (ctx: SharedScenarioContext) => void | Promise<void>;\n then: (ctx: SharedScenarioContext) => void | Promise<void>;\n}\n\nexport function defineBase<const Definition extends BaseDefinition>(\n definition: Definition,\n): Definition {\n return definition;\n}\n\nexport function defineScenario<\n const Runners extends readonly TestRunner[] = readonly [\"reducer\"],\n const PhaseName extends string = string,\n const StageName extends string = string,\n>(\n definition: ScenarioDefinition<Runners, PhaseName, StageName>,\n): ScenarioDefinition<Runners, PhaseName, StageName> {\n return definition;\n}\n","import { isDeepStrictEqual } from \"node:util\";\nimport type {\n ExpectFn,\n ExpectMatchers,\n InteractionDescriptorLike,\n RejectionExpectation,\n SnapshotMatcherHandler,\n} from \"./definitions.js\";\n\ntype CreateExpectApiOptions = {\n matchSnapshot?: SnapshotMatcherHandler;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction matchPartial(\n actual: unknown,\n expected: unknown,\n path: string = \"value\",\n): string | null {\n if (!isRecord(expected)) {\n return isDeepStrictEqual(actual, expected)\n ? null\n : `${path} does not match`;\n }\n if (!isRecord(actual)) {\n return `${path} is not an object`;\n }\n for (const [key, expectedValue] of Object.entries(expected)) {\n const actualValue = actual[key];\n if (!(key in actual)) {\n return `${path}.${key} is missing`;\n }\n const mismatch = matchPartial(actualValue, expectedValue, `${path}.${key}`);\n if (mismatch) {\n return mismatch;\n }\n }\n return null;\n}\n\nfunction asDescriptorList(actual: unknown): InteractionDescriptorLike[] {\n if (!Array.isArray(actual)) {\n throw new Error(\"Expected interaction descriptor array.\");\n }\n return actual as InteractionDescriptorLike[];\n}\n\nfunction findInteraction(\n descriptors: readonly InteractionDescriptorLike[],\n interactionId: string,\n): InteractionDescriptorLike | null {\n return (\n descriptors.find(\n (descriptor) => descriptor.interactionId === interactionId,\n ) ?? null\n );\n}\n\nfunction assertDescriptorMatches(\n descriptor: InteractionDescriptorLike,\n opts: Partial<InteractionDescriptorLike> | undefined,\n): void {\n if (!opts) {\n return;\n }\n const mismatch = matchPartial(descriptor, opts, \"interaction\");\n if (mismatch) {\n throw new Error(mismatch);\n }\n}\n\nfunction createSubmissionErrorMatcher(\n actual: unknown,\n expected: RejectionExpectation,\n): Promise<void> {\n const resolvePromise = (): Promise<unknown> => {\n if (typeof actual === \"function\") {\n return Promise.resolve().then(() => (actual as () => unknown)());\n }\n return Promise.resolve(actual);\n };\n\n return resolvePromise()\n .then(() => {\n throw new Error(\"Expected promise to reject.\");\n })\n .catch((error: unknown) => {\n if (!(error instanceof Error)) {\n throw new Error(\"Expected rejection to be an Error.\");\n }\n if (\n expected.errorCode !== undefined &&\n (error as Error & { errorCode?: string }).errorCode !==\n expected.errorCode\n ) {\n throw new Error(\n `Expected rejection errorCode '${expected.errorCode}', received '${\n (error as Error & { errorCode?: string }).errorCode ?? \"undefined\"\n }'.`,\n );\n }\n if (\n typeof expected.message === \"string\" &&\n error.message !== expected.message\n ) {\n throw new Error(\n `Expected rejection message '${expected.message}', received '${error.message}'.`,\n );\n }\n if (\n expected.message instanceof RegExp &&\n !expected.message.test(error.message)\n ) {\n throw new Error(\n `Expected rejection message '${error.message}' to match ${String(expected.message)}.`,\n );\n }\n });\n}\n\nfunction assertLength(actual: unknown, expected: number): void {\n if (\n actual === null ||\n actual === undefined ||\n typeof (actual as { length?: unknown }).length !== \"number\"\n ) {\n throw new Error(\"toHaveLength expects a value with a numeric length.\");\n }\n const length = (actual as { length: number }).length;\n if (length !== expected) {\n throw new Error(`Expected length ${expected}, received ${length}.`);\n }\n}\n\nfunction assertThrown(\n actual: unknown,\n predicate?: string | RegExp | ((error: Error) => boolean),\n): void {\n if (typeof actual !== \"function\") {\n throw new Error(\"toThrow expects a function.\");\n }\n try {\n (actual as () => unknown)();\n } catch (error) {\n if (!(error instanceof Error)) {\n throw new Error(\"Thrown value is not an Error.\");\n }\n if (predicate === undefined) {\n return;\n }\n if (typeof predicate === \"string\" && error.message !== predicate) {\n throw new Error(\n `Expected thrown message '${predicate}', received '${error.message}'.`,\n );\n }\n if (predicate instanceof RegExp && !predicate.test(error.message)) {\n throw new Error(\n `Expected thrown message '${error.message}' to match ${String(predicate)}.`,\n );\n }\n if (typeof predicate === \"function\" && !predicate(error)) {\n throw new Error(\"Thrown error did not satisfy predicate.\");\n }\n return;\n }\n throw new Error(\"Expected function to throw.\");\n}\n\nexport function createExpectApi(\n options: CreateExpectApiOptions = {},\n): ExpectFn {\n const buildMatchers = (actual: unknown): ExpectMatchers => ({\n toBe: (expected: unknown) => {\n if (actual !== expected) {\n throw new Error(\n `Expected '${String(actual)}' to be '${String(expected)}'.`,\n );\n }\n },\n toEqual: (expected: unknown) => {\n if (!isDeepStrictEqual(actual, expected)) {\n throw new Error(\"Expected values to be deeply equal.\");\n }\n },\n toMatchObject: (expected: Record<string, unknown>) => {\n const mismatch = matchPartial(actual, expected);\n if (mismatch) {\n throw new Error(mismatch);\n }\n },\n toBeDefined: () => {\n if (actual === undefined) {\n throw new Error(\"Expected value to be defined.\");\n }\n },\n toBeUndefined: () => {\n if (actual !== undefined) {\n throw new Error(\n `Expected value to be undefined, but received '${String(actual)}'.`,\n );\n }\n },\n toBeNull: () => {\n if (actual !== null) {\n throw new Error(\n `Expected value to be null, but received '${String(actual)}'.`,\n );\n }\n },\n toContain: (expected: unknown) => {\n if (Array.isArray(actual)) {\n if (!actual.includes(expected)) {\n throw new Error(\"Expected array to contain value.\");\n }\n return;\n }\n if (typeof actual === \"string\") {\n if (!actual.includes(String(expected))) {\n throw new Error(\"Expected string to contain value.\");\n }\n return;\n }\n throw new Error(\"toContain expects an array or string actual value.\");\n },\n toContainEqual: (expected: unknown) => {\n if (!Array.isArray(actual)) {\n throw new Error(\"toContainEqual expects an array actual value.\");\n }\n if (!actual.some((value) => isDeepStrictEqual(value, expected))) {\n throw new Error(\"Expected array to contain an equal value.\");\n }\n },\n toHaveLength: (expected: number) => {\n assertLength(actual, expected);\n },\n toBeGreaterThan: (expected: number) => {\n if (typeof actual !== \"number\") {\n throw new Error(\"toBeGreaterThan expects a number actual value.\");\n }\n if (actual <= expected) {\n throw new Error(`Expected ${actual} to be > ${expected}.`);\n }\n },\n toBeGreaterThanOrEqual: (expected: number) => {\n if (typeof actual !== \"number\") {\n throw new Error(\n \"toBeGreaterThanOrEqual expects a number actual value.\",\n );\n }\n if (actual < expected) {\n throw new Error(`Expected ${actual} to be >= ${expected}.`);\n }\n },\n toThrow: (predicate) => {\n assertThrown(actual, predicate);\n },\n toMatchSnapshot: (filename) => {\n if (!options.matchSnapshot) {\n throw new Error(\n \"Snapshot matching is not configured for this expect API.\",\n );\n }\n options.matchSnapshot(filename, actual);\n },\n toRejectWith: (expected) => createSubmissionErrorMatcher(actual, expected),\n toHaveInteraction: (interactionId, opts) => {\n const descriptors = asDescriptorList(actual);\n const descriptor = findInteraction(descriptors, interactionId);\n if (!descriptor) {\n throw new Error(`Expected interaction '${interactionId}' to exist.`);\n }\n assertDescriptorMatches(descriptor, opts);\n },\n toBeGatedBy: (reason, opts) => {\n const descriptor = Array.isArray(actual)\n ? (() => {\n if (!opts?.interactionId) {\n throw new Error(\n \"toBeGatedBy on a descriptor array requires opts.interactionId.\",\n );\n }\n return findInteraction(\n asDescriptorList(actual),\n opts.interactionId,\n );\n })()\n : (actual as InteractionDescriptorLike | null);\n if (!descriptor) {\n throw new Error(\"Expected interaction descriptor to exist.\");\n }\n if (descriptor.available !== false) {\n throw new Error(\"Expected interaction to be unavailable.\");\n }\n if (descriptor.unavailableReason !== reason) {\n throw new Error(\n `Expected unavailableReason '${reason}', received '${\n descriptor.unavailableReason ?? \"undefined\"\n }'.`,\n );\n }\n },\n toBeActiveFor: (playerId, opts) => {\n const descriptor = Array.isArray(actual)\n ? (() => {\n if (!opts?.interactionId) {\n throw new Error(\n \"toBeActiveFor on a descriptor array requires opts.interactionId.\",\n );\n }\n return findInteraction(\n asDescriptorList(actual),\n opts.interactionId,\n );\n })()\n : (actual as InteractionDescriptorLike | null);\n if (!descriptor) {\n throw new Error(\"Expected interaction descriptor to exist.\");\n }\n if (descriptor.context?.to !== playerId) {\n throw new Error(\n `Expected interaction to target '${playerId}', received '${\n descriptor.context?.to ?? \"undefined\"\n }'.`,\n );\n }\n if (descriptor.available !== true) {\n throw new Error(\"Expected interaction to be available.\");\n }\n },\n not: {\n toHaveInteraction: (interactionId) => {\n const descriptors = asDescriptorList(actual);\n if (findInteraction(descriptors, interactionId)) {\n throw new Error(\n `Expected interaction '${interactionId}' to be absent.`,\n );\n }\n },\n },\n });\n\n return (actual: unknown) => buildMatchers(actual);\n}\n","import type {\n HostPlayerGameplayView,\n HostSessionContext,\n HostSessionSnapshot,\n SeatAssignment,\n} from \"@dreamboard/api-client\";\nimport type { ReducerBundleContract } from \"@dreamboard/reducer-contract/bundle\";\nimport type * as Wire from \"@dreamboard/reducer-contract/wire\";\nimport type { PluginStateSnapshot } from \"@dreamboard/ui-sdk/reducer\";\nimport type {\n PluginSessionState,\n RuntimeAPI,\n SubmissionError,\n ValidationResult,\n} from \"@dreamboard/ui-sdk\";\nimport {\n createUnifiedSessionStore,\n type GameplayViewport,\n type SessionContext,\n type SSEManagerLike,\n} from \"@dreamboard/ui-host-runtime/runtime\";\n\ntype ReducerStaticProjection = Wire.BoardStaticProjection;\ntype ReducerBundleLike = Pick<\n ReducerBundleContract,\n \"projectSeatsDynamic\" | \"projectStatic\" | \"validateInput\" | \"dispatch\"\n>;\n\ntype BaseStateArtifact = {\n snapshot: Wire.ReducerSessionState;\n fingerprint: {\n players: number;\n };\n};\n\nexport type CreateTestRuntimeOptions = {\n baseId: string;\n baseStates: Record<string, BaseStateArtifact>;\n bundle: ReducerBundleLike;\n phase?: string;\n playerIds?: readonly string[];\n sessionId?: string;\n userId?: string | null;\n gameId?: string;\n displayNameByPlayerId?: Record<string, string>;\n};\n\nexport type CreatedTestRuntime = {\n runtime: RuntimeAPI & {\n getSnapshot(): PluginStateSnapshot;\n subscribeToState(\n listener: (state: PluginStateSnapshot) => void,\n ): () => void;\n _subscribeToSessionState(\n listener: (state: PluginSessionState) => void,\n ): () => void;\n };\n getSnapshot(): PluginStateSnapshot;\n players(): readonly string[];\n seat(index: number): string;\n submit(\n playerId: string,\n interactionId: string,\n params?: unknown,\n ): Promise<void>;\n validate(\n playerId: string,\n interactionId: string,\n params?: unknown,\n ): Promise<ValidationResult>;\n setControllingPlayer(playerId: string): void;\n};\n\nfunction cloneState<T>(value: T): T {\n return structuredClone(value);\n}\n\nfunction createSubmissionError(\n errorCode: string | undefined,\n message: string | undefined,\n): SubmissionError {\n const error = new Error(message ?? \"Interaction rejected\") as SubmissionError;\n error.name = \"SubmissionError\";\n error.errorCode = errorCode;\n return error;\n}\n\nfunction readFlowState(state: Wire.ReducerSessionState): {\n currentPhase: string | null;\n activePlayers: string[];\n} {\n const flow = ((\n state.domain as\n | { flow?: { currentPhase?: string; activePlayers?: string[] } }\n | undefined\n )?.flow ?? {}) as {\n currentPhase?: string;\n activePlayers?: string[];\n };\n return {\n currentPhase: flow.currentPhase ?? null,\n activePlayers: Array.isArray(flow.activePlayers) ? flow.activePlayers : [],\n };\n}\n\nfunction buildSeatAssignments(\n playerIds: readonly string[],\n userId: string | null,\n displayNameByPlayerId: Record<string, string> | undefined,\n): SeatAssignment[] {\n const actor =\n userId != null ? { kind: \"AUTH_USER\" as const, id: userId } : undefined;\n return playerIds.map((playerId) => ({\n playerId,\n controllerActor: actor,\n displayName: displayNameByPlayerId?.[playerId] ?? playerId,\n }));\n}\n\nfunction resolvePlayerIds(options: {\n baseState: BaseStateArtifact;\n explicitPlayerIds?: readonly string[];\n}): string[] {\n if (options.explicitPlayerIds && options.explicitPlayerIds.length > 0) {\n return [...options.explicitPlayerIds];\n }\n return Array.from(\n { length: options.baseState.fingerprint.players },\n (_, index) => `player-${index + 1}`,\n );\n}\n\n// Stub SSE manager for the in-memory store. `createTestRuntime` never\n// connects to a live backend; updates are delivered through\n// `applyGameplaySnapshotLocal(...)` instead.\nfunction createStubSseManager(): SSEManagerLike {\n return {\n connect: () => undefined,\n disconnect: () => undefined,\n on: () => () => undefined,\n onAnyMessage: () => () => undefined,\n };\n}\n\nfunction buildGameplaySnapshot(options: {\n state: Wire.ReducerSessionState;\n bundle: ReducerBundleLike;\n staticProjection: ReducerStaticProjection | null;\n playerId: string;\n version: number;\n expectedPhase: string | undefined;\n baseId: string;\n}): HostPlayerGameplayView {\n const projection = options.bundle.projectSeatsDynamic({\n state: options.state,\n playerIds: [options.playerId],\n });\n\n const flow = readFlowState(options.state);\n if (options.expectedPhase && flow.currentPhase !== options.expectedPhase) {\n throw new Error(\n `Expected base '${options.baseId}' to be in phase '${options.expectedPhase}', received '${\n flow.currentPhase ?? \"null\"\n }'.`,\n );\n }\n\n const seats = projection.seats ?? {};\n const seat = seats[options.playerId];\n\n return {\n version: options.version,\n actionSetVersion: `${options.version}:test`,\n playerId: options.playerId,\n activePlayers: flow.activePlayers,\n currentPhase: flow.currentPhase ?? \"\",\n currentStage: projection.currentStage ?? \"\",\n stageSeats: projection.stageSeats ?? [],\n view: JSON.stringify(seat?.view ?? null),\n availableInteractions:\n (seat?.availableInteractions as HostPlayerGameplayView[\"availableInteractions\"]) ??\n [],\n zones: (seat?.zones as HostPlayerGameplayView[\"zones\"]) ?? {},\n boardStatic: options.staticProjection\n ? JSON.stringify(options.staticProjection.view)\n : undefined,\n boardStaticHash: options.staticProjection?.hash,\n };\n}\n\nfunction createHostContext(options: {\n sessionId: string;\n userId: string;\n gameId: string;\n phase: HostSessionContext[\"phase\"];\n hostActor: { kind: \"AUTH_USER\"; id: string };\n switchablePlayerIds: string[];\n}): HostSessionContext {\n return {\n sessionId: options.sessionId,\n shortCode: options.sessionId,\n phase: options.phase,\n status: \"active\",\n hostActor: options.hostActor,\n gameSource: {\n kind: \"USER_COMPILED\",\n ownerUserId: options.userId,\n gameId: options.gameId,\n compiledResultId: \"00000000-0000-0000-0000-000000000000\",\n },\n switchablePlayerIds: options.switchablePlayerIds,\n history: {\n entries: [],\n currentIndex: -1,\n canGoBack: false,\n canGoForward: false,\n },\n };\n}\n\nfunction createRuntimeSessionContext(options: {\n sessionId: string;\n userId: string;\n gameId: string;\n hostActor: { kind: \"AUTH_USER\"; id: string };\n switchablePlayerIds: string[];\n seats: SeatAssignment[];\n canStart: boolean;\n}): SessionContext {\n return {\n identity: {\n sessionId: options.sessionId,\n shortCode: options.sessionId,\n gameId: options.gameId,\n },\n userId: options.userId,\n seats: options.seats,\n canStart: options.canStart,\n hostActor: options.hostActor,\n switchablePlayerIds: options.switchablePlayerIds,\n history: {\n entries: [],\n currentIndex: -1,\n canGoBack: false,\n canGoForward: false,\n },\n };\n}\n\nfunction gameplayViewportFromSnapshot(\n snapshot: HostPlayerGameplayView,\n): GameplayViewport {\n return {\n version: snapshot.version,\n actionSetVersion: snapshot.actionSetVersion,\n activePlayers: snapshot.activePlayers,\n currentPhase: snapshot.currentPhase,\n currentStage: snapshot.currentStage,\n stageSeats: snapshot.stageSeats,\n simultaneousPhase: snapshot.simultaneousPhase ?? null,\n view: JSON.parse(snapshot.view) as GameplayViewport[\"view\"],\n availableInteractions: snapshot.availableInteractions,\n zones: snapshot.zones,\n boardStatic: snapshot.boardStatic\n ? (JSON.parse(snapshot.boardStatic) as Record<string, unknown>)\n : null,\n boardStaticHash: snapshot.boardStaticHash ?? null,\n };\n}\n\n// Reuses `packages/ui-host-runtime`'s unified session store so the\n// `getPluginSnapshot()` projection and `applyGameplaySnapshotLocal(...)`\n// reducer running inside workspace tests are the same code paths\n// running inside the host app. Prevents snapshot-shape drift between\n// host plugin UI and authored UI tests.\nexport function createTestRuntime(\n options: CreateTestRuntimeOptions,\n): CreatedTestRuntime {\n const baseState = options.baseStates[options.baseId];\n if (!baseState) {\n throw new Error(`Unknown test base '${options.baseId}'.`);\n }\n\n let currentState = cloneState(baseState.snapshot);\n const playerIds = resolvePlayerIds({\n baseState,\n explicitPlayerIds: options.playerIds,\n });\n const userId = options.userId ?? \"test-user\";\n const sessionId = options.sessionId ?? \"test-session\";\n const gameId = options.gameId ?? \"test-game\";\n const seats = buildSeatAssignments(\n playerIds,\n userId,\n options.displayNameByPlayerId,\n );\n const storeApi = createUnifiedSessionStore({\n createSseManager: createStubSseManager,\n // Test runtimes keep `userId` present so the unified store derives\n // controllable player ids from seat assignments whenever the\n // incoming snapshot omits `controllablePlayerIds`.\n fallbackToAllSeatsWhenUserIdMissing: false,\n });\n\n const hostActor = { kind: \"AUTH_USER\" as const, id: userId };\n const lobbySnapshot: HostSessionSnapshot = {\n type: \"lobby\",\n context: createHostContext({\n sessionId,\n userId,\n gameId,\n phase: \"lobby\",\n hostActor,\n switchablePlayerIds: playerIds,\n }),\n lobby: {\n seats,\n canStart: true,\n hostActor,\n },\n };\n const setSessionState = (input: {\n selectedPlayerId: string;\n gameplay?: HostPlayerGameplayView;\n }): void => {\n const context = createRuntimeSessionContext({\n sessionId,\n userId,\n gameId,\n hostActor,\n switchablePlayerIds: playerIds,\n seats,\n canStart: true,\n });\n storeApi.setState((state) => ({\n ...state,\n session: input.gameplay\n ? {\n type: \"gameplay\",\n context,\n perspective: { playerId: input.selectedPlayerId },\n gameplay: gameplayViewportFromSnapshot(input.gameplay),\n }\n : {\n type: \"lobby\",\n context,\n preferredPlayerId: input.selectedPlayerId || null,\n },\n connection: { ...state.connection, error: null },\n activity: {\n ...state.activity,\n syncId: state.activity.syncId + 1,\n lastSyncTimestamp: Date.now(),\n },\n }));\n };\n\n setSessionState({\n selectedPlayerId: playerIds[0] ?? \"\",\n });\n\n let version = 0;\n let currentPlayerId = playerIds[0] ?? \"\";\n const staticProjection = options.bundle.projectStatic?.() ?? null;\n\n const applyCurrentState = (): void => {\n version += 1;\n const snapshot = buildGameplaySnapshot({\n state: currentState,\n bundle: options.bundle,\n staticProjection,\n playerId: currentPlayerId,\n version,\n expectedPhase: version === 1 ? options.phase : undefined,\n baseId: options.baseId,\n });\n setSessionState({\n selectedPlayerId: currentPlayerId,\n gameplay: snapshot,\n });\n };\n\n applyCurrentState();\n\n const stateListeners = new Set<(state: PluginStateSnapshot) => void>();\n const sessionListeners = new Set<(state: PluginSessionState) => void>();\n\n let lastPluginSnapshot = storeApi.getState().getPluginSnapshot();\n let lastSessionState: PluginSessionState = {\n ...lastPluginSnapshot.session,\n status: \"ready\",\n };\n\n storeApi.subscribe((state, previous) => {\n const nextPluginSnapshot = state.getPluginSnapshot();\n if (nextPluginSnapshot !== lastPluginSnapshot) {\n lastPluginSnapshot = nextPluginSnapshot;\n for (const listener of stateListeners) {\n listener(nextPluginSnapshot);\n }\n }\n const previousSession = previous.getPluginSnapshot().session;\n const nextSession = nextPluginSnapshot.session;\n const controllingPlayerChanged =\n nextSession.controllingPlayerId !== previousSession.controllingPlayerId;\n const controllableIdsChanged =\n nextSession.controllablePlayerIds.join(\"\\0\") !==\n previousSession.controllablePlayerIds.join(\"\\0\");\n if (controllingPlayerChanged || controllableIdsChanged) {\n lastSessionState = {\n ...nextPluginSnapshot.session,\n status: \"ready\",\n };\n for (const listener of sessionListeners) {\n listener(lastSessionState);\n }\n }\n });\n\n const validate = async (\n playerId: string,\n interactionId: string,\n params: unknown = {},\n ): Promise<ValidationResult> => {\n const result = await options.bundle.validateInput({\n state: currentState,\n input: {\n kind: \"interaction\",\n playerId,\n interactionId,\n params: params as Wire.JsonValue,\n },\n });\n return {\n valid: result.valid,\n errorCode: result.errorCode,\n message: result.message,\n };\n };\n\n const submit = async (\n playerId: string,\n interactionId: string,\n params: unknown = {},\n ): Promise<void> => {\n const validation = await validate(playerId, interactionId, params);\n if (!validation.valid) {\n throw createSubmissionError(validation.errorCode, validation.message);\n }\n const result = await options.bundle.dispatch({\n state: currentState,\n input: {\n kind: \"interaction\",\n playerId,\n interactionId,\n params: params as Wire.JsonValue,\n },\n });\n if (result.kind === \"reject\") {\n throw createSubmissionError(result.errorCode, result.message);\n }\n currentState = cloneState(result.state);\n applyCurrentState();\n };\n\n const setControllingPlayer = (playerId: string): void => {\n if (!playerIds.includes(playerId)) {\n throw new Error(`Unknown controlling player '${playerId}'.`);\n }\n currentPlayerId = playerId;\n storeApi.getState().selectPlayer(playerId);\n applyCurrentState();\n };\n\n const runtime = {\n validateInteraction: validate,\n submitInteraction: submit,\n getSessionState: (): PluginSessionState => ({ ...lastSessionState }),\n disconnect: () => undefined,\n switchPlayer: (playerId: string) => {\n setControllingPlayer(playerId);\n },\n getSnapshot: (): PluginStateSnapshot => lastPluginSnapshot,\n subscribeToState: (listener: (state: PluginStateSnapshot) => void) => {\n stateListeners.add(listener);\n return () => {\n stateListeners.delete(listener);\n };\n },\n _subscribeToSessionState: (\n listener: (state: PluginSessionState) => void,\n ) => {\n sessionListeners.add(listener);\n return () => {\n sessionListeners.delete(listener);\n };\n },\n };\n\n return {\n runtime,\n getSnapshot: () => lastPluginSnapshot,\n players: () => [...playerIds],\n seat: (index: number) => {\n if (!Number.isInteger(index) || index < 0 || index >= playerIds.length) {\n throw new Error(\n `seat(${index}) is out of range; base '${options.baseId}' has ${playerIds.length} player(s).`,\n );\n }\n return playerIds[index]!;\n },\n submit,\n validate,\n setControllingPlayer,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# Authoring lifecycle
|
|
2
|
-
|
|
3
|
-
See how authored Dreamboard files flow through sync, compile, runtime, UI projection, and tests.
|
|
4
|
-
|
|
5
|
-
Dreamboard has one recommended lifecycle: author static structure, bind it into a reducer contract, register runtime behavior, project UI state, then verify it with generated tests.
|
|
6
|
-
|
|
7
|
-
```mermaid
|
|
8
|
-
flowchart TD
|
|
9
|
-
Rule["rule.md"]
|
|
10
|
-
Manifest["manifest.ts"]
|
|
11
|
-
Contract["app/game-contract.ts"]
|
|
12
|
-
Game["app/game.ts"]
|
|
13
|
-
Sync["dreamboard sync"]
|
|
14
|
-
Generated["shared/manifest-contract.ts and shared/generated/ui-contract.ts"]
|
|
15
|
-
Compile["dreamboard compile"]
|
|
16
|
-
Runtime["Reducer runtime"]
|
|
17
|
-
View["defineView / defineStaticView"]
|
|
18
|
-
UI["Generated UI primitives"]
|
|
19
|
-
Tests["defineBase / defineScenario"]
|
|
20
|
-
|
|
21
|
-
Rule --> Manifest
|
|
22
|
-
Manifest --> Sync
|
|
23
|
-
Sync --> Generated
|
|
24
|
-
Generated --> Contract
|
|
25
|
-
Contract --> Game
|
|
26
|
-
Game --> Compile
|
|
27
|
-
Compile --> Runtime
|
|
28
|
-
Runtime --> View
|
|
29
|
-
View --> UI
|
|
30
|
-
Generated --> Tests
|
|
31
|
-
Game --> Tests
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## 1. Author the manifest
|
|
35
|
-
|
|
36
|
-
Start with `manifest.ts`. The manifest names the table pieces that the rest of the project should refer to: player ids, card ids, zone ids, board ids, resource ids, setup options, and setup profiles.
|
|
37
|
-
|
|
38
|
-
After a manifest change, run:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
dreamboard sync
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
`sync` regenerates the manifest contract. If you renamed a zone, any reducer/UI/test code still using the old zone id should fail to compile.
|
|
45
|
-
|
|
46
|
-
## 2. Bind the reducer contract
|
|
47
|
-
|
|
48
|
-
`app/game-contract.ts` imports `manifestContract` and calls `defineGameContract`.
|
|
49
|
-
|
|
50
|
-
This is where authored state starts:
|
|
51
|
-
|
|
52
|
-
- `public` for state every player can see
|
|
53
|
-
- `private` for per-player secrets
|
|
54
|
-
- `hidden` for game-owned secrets
|
|
55
|
-
- `phaseNames` for the legal phase-name union
|
|
56
|
-
|
|
57
|
-
Use manifest-backed schemas from `gameContract.schemas` or generated `ids` so state and params stay tied to the manifest.
|
|
58
|
-
|
|
59
|
-
## 3. Define phases and interactions
|
|
60
|
-
|
|
61
|
-
Phases define game flow. Interactions define what players can submit.
|
|
62
|
-
|
|
63
|
-
The runtime path for an interaction is:
|
|
64
|
-
|
|
65
|
-
1. Find the active phase.
|
|
66
|
-
2. Check actor authorization.
|
|
67
|
-
3. Parse client params from collectors.
|
|
68
|
-
4. Validate board/card/choice targets from target rules.
|
|
69
|
-
5. Run `available` and `validate`.
|
|
70
|
-
6. Call `reduce`.
|
|
71
|
-
7. Apply returned state and queued runtime instructions.
|
|
72
|
-
|
|
73
|
-
The UI should not reimplement this. It reads descriptors and eligible targets generated from the same interaction definition.
|
|
74
|
-
|
|
75
|
-
## 4. Project views
|
|
76
|
-
|
|
77
|
-
Use `defineView` for dynamic per-player payloads. Use `defineStaticView` for immutable session payloads.
|
|
78
|
-
|
|
79
|
-
Dynamic views can see state and `playerId`; static views cannot. That split keeps large static topology out of every runtime update.
|
|
80
|
-
|
|
81
|
-
## 5. Render UI primitives
|
|
82
|
-
|
|
83
|
-
The generated UI contract creates typed interaction keys, input keys, prompt
|
|
84
|
-
options, zones, cards, phases, and board target ids. Compose those generated
|
|
85
|
-
primitives in `ui/App.tsx`.
|
|
86
|
-
|
|
87
|
-
Prefer `Interaction`, `PromptInbox`, `Prompt`, and `Zone` components first. Use
|
|
88
|
-
hooks only when the game needs domain-specific custom rendering, such as an SVG
|
|
89
|
-
board, not to reimplement validation or turn logic.
|
|
90
|
-
|
|
91
|
-
## 6. Test the reducer
|
|
92
|
-
|
|
93
|
-
Run:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
dreamboard test generate
|
|
97
|
-
dreamboard test run
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
`test generate` writes workspace-narrowed test types from the current game definition. Scenarios submit typed interactions and assert against reducer state and projected views.
|
|
101
|
-
|
|
102
|
-
If tests fail after a manifest or reducer change, regenerate the testing contract before assuming the scenario logic is wrong.
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# Board targets
|
|
2
|
-
|
|
3
|
-
Route board clicks through generated reducer-aware target helpers.
|
|
4
|
-
|
|
5
|
-
Board renderers are the main escape hatch for custom visuals. Keep legality in
|
|
6
|
-
the reducer and route clicks through generated `useBoardInteractions()`.
|
|
7
|
-
|
|
8
|
-
```tsx
|
|
9
|
-
import { useBoardInteractions } from "@dreamboard/ui-contract";
|
|
10
|
-
import { HexGrid } from "@dreamboard/ui-sdk";
|
|
11
|
-
|
|
12
|
-
function Board({ view }) {
|
|
13
|
-
const board = useBoardInteractions();
|
|
14
|
-
return (
|
|
15
|
-
<HexGrid
|
|
16
|
-
board={view.board}
|
|
17
|
-
interactiveVertices={board.targetLayers.vertex()}
|
|
18
|
-
interactiveEdges={board.targetLayers.edge()}
|
|
19
|
-
interactiveSpaces={board.targetLayers.space()}
|
|
20
|
-
renderTile={(tile, geometry) => <polygon points={geometry.points()} />}
|
|
21
|
-
renderEdge={(edge) => <line />}
|
|
22
|
-
renderVertex={(vertex) => <circle />}
|
|
23
|
-
/>
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
The hook merges target domains from the current descriptors and submits the
|
|
29
|
-
selected target to the best matching interaction. If two equal-priority
|
|
30
|
-
interactions can handle the same target, it fails loudly so the reducer can
|
|
31
|
-
declare a clearer `dispatchPriority` or the UI can route explicitly.
|
|
32
|
-
|
|
33
|
-
Use this for SVG, canvas, or geometry-heavy boards. `HexGrid` and `SquareGrid`
|
|
34
|
-
still need normal render callbacks for spaces, edges, and vertices; the
|
|
35
|
-
interactive layers only add reducer-aware target overlays. Ordinary buttons and card
|
|
36
|
-
inputs should use `Interaction` and `Zone` primitives instead.
|
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
# Board topology
|
|
2
|
-
|
|
3
|
-
Author hex and square boards with shared reducer and UI topology primitives.
|
|
4
|
-
|
|
5
|
-
Use tiled boards when your game has stable spaces plus meaningful borders or
|
|
6
|
-
corners.
|
|
7
|
-
|
|
8
|
-
Dreamboard currently supports two fixed-topology tiled layouts:
|
|
9
|
-
|
|
10
|
-
- `layout: "hex"` for axial hex maps
|
|
11
|
-
- `layout: "square"` for row and column grids
|
|
12
|
-
|
|
13
|
-
Keep `layout: "generic"` for authored graphs that are not tile-based, such as
|
|
14
|
-
tracks, networks, or map nodes.
|
|
15
|
-
|
|
16
|
-
It is normal for one manifest to mix these layouts. For example, a game can use
|
|
17
|
-
one shared generic market board, one shared hex frontier map, and one
|
|
18
|
-
per-player square mat without splitting that coverage across multiple
|
|
19
|
-
workspaces.
|
|
20
|
-
|
|
21
|
-
The generated manifest contract now preserves that mixed topology directly with
|
|
22
|
-
template-level literals plus board-scoped edge and vertex helpers:
|
|
23
|
-
|
|
24
|
-
{/* Generated from examples/board-contract-lab/app/authoring-benchmark-typing-smoke.ts (tiled-board-helpers) */}
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
const playerMatId = boardHelpers.boardIdForPlayer("player-mat", "player-1");
|
|
28
|
-
const hexBoardIds = boardHelpers.boardIdsForLayout("hex");
|
|
29
|
-
const frontierRouteId = boardHelpers.edgeIds("frontier-map", "ferry-route")[0]!;
|
|
30
|
-
const frontierCheckpointId = boardHelpers.vertexIds(
|
|
31
|
-
"frontier-map",
|
|
32
|
-
"checkpoint",
|
|
33
|
-
)[0]!;
|
|
34
|
-
const marketTemplateBoards =
|
|
35
|
-
boardHelpers.boardBaseIdsForTemplate("market-template");
|
|
36
|
-
const playerMatTemplateLayout = boardHelpers.boardTemplateLayout(
|
|
37
|
-
"player-mat-template",
|
|
38
|
-
);
|
|
39
|
-
//
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## What lives where
|
|
43
|
-
|
|
44
|
-
Put immutable topology in the manifest:
|
|
45
|
-
|
|
46
|
-
- spaces and their coordinates
|
|
47
|
-
- optional authored edge and vertex metadata
|
|
48
|
-
- authored containers and extra relations
|
|
49
|
-
|
|
50
|
-
Put mutable gameplay state in reducer state:
|
|
51
|
-
|
|
52
|
-
- ownership keyed by `spaceId`, `edgeId`, or `vertexId`
|
|
53
|
-
- placed tiles, markers, units, or hazards
|
|
54
|
-
- highlights, legal targets, and temporary UI state
|
|
55
|
-
|
|
56
|
-
That split keeps the board reusable across different games and avoids treating
|
|
57
|
-
manifest topology as a mutable gameplay store.
|
|
58
|
-
|
|
59
|
-
## Fixed-topology constraint
|
|
60
|
-
|
|
61
|
-
Hex and square boards in this release are fixed once the session starts.
|
|
62
|
-
|
|
63
|
-
- Terraforming Mars fits this model: spaces stay fixed while ownership and tile
|
|
64
|
-
state change.
|
|
65
|
-
- Gloomhaven fits this model: spaces stay fixed while units move and range is
|
|
66
|
-
recalculated.
|
|
67
|
-
- Cascadia fits this model when the habitat map is pre-authored and scoring
|
|
68
|
-
reads adjacency from the fixed board.
|
|
69
|
-
|
|
70
|
-
Carcassonne-style board growth is not part of this release. Treat that as a
|
|
71
|
-
future topology adapter rather than part of the current `square` layout.
|
|
72
|
-
|
|
73
|
-
## Shared topology vocabulary
|
|
74
|
-
|
|
75
|
-
Reducer helpers and UI hooks use the same terms:
|
|
76
|
-
|
|
77
|
-
| Term | Meaning |
|
|
78
|
-
| ----------- | ----------------------------------------------------- |
|
|
79
|
-
| `space` | One tile or cell on the board |
|
|
80
|
-
| `edge` | One border shared by one or two spaces |
|
|
81
|
-
| `vertex` | One corner touched by one to four spaces |
|
|
82
|
-
| `adjacent` | The default movement or scoring graph for a layout |
|
|
83
|
-
| `incident` | Edges touching a vertex, or vertices touching an edge |
|
|
84
|
-
| `occupancy` | Components currently on a space, edge, or vertex |
|
|
85
|
-
|
|
86
|
-
Reducer-side board helpers are covered in [Table queries and ops](./table-queries-and-ops.md).
|
|
87
|
-
UI-side board rendering is covered in [Board targets](./board-surfaces.md)
|
|
88
|
-
and [UI components](./ui-components.md).
|
|
89
|
-
|
|
90
|
-
## Choosing a layout
|
|
91
|
-
|
|
92
|
-
| Layout | Use it for | Default adjacency |
|
|
93
|
-
| --------- | ----------------------------------------------------- | ---------------------------------- |
|
|
94
|
-
| `generic` | Tracks, networks, arbitrary relation graphs | Whatever you author in `relations` |
|
|
95
|
-
| `hex` | Terraforming Mars, Gloomhaven, Cascadia, wargame maps | Derived hex adjacency |
|
|
96
|
-
| `square` | Chess, Checkers, Onitama, fixed tile grids | Orthogonal 4-way adjacency |
|
|
97
|
-
|
|
98
|
-
Square boards can still use diagonal logic. The reducer and UI helpers expose
|
|
99
|
-
square-specific variants for diagonal neighbors and Chebyshev distance.
|
|
100
|
-
|
|
101
|
-
## Reducer state pattern
|
|
102
|
-
|
|
103
|
-
Store dynamic tiled state in plain overlay maps keyed by stable location IDs.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
import { z } from "zod";
|
|
107
|
-
import type {
|
|
108
|
-
TiledEdgeMap,
|
|
109
|
-
TiledSpaceMap,
|
|
110
|
-
TiledVertexMap,
|
|
111
|
-
} from "@dreamboard/app-sdk/reducer";
|
|
112
|
-
import { ids } from "../shared/manifest-contract";
|
|
113
|
-
|
|
114
|
-
const publicState = z.object({
|
|
115
|
-
ownershipBySpaceId: z.partialRecord(ids.spaceId, ids.playerId).default({}),
|
|
116
|
-
effectByEdgeId: z.partialRecord(ids.edgeId, z.string()).default({}),
|
|
117
|
-
markerByVertexId: z
|
|
118
|
-
.partialRecord(
|
|
119
|
-
ids.vertexId,
|
|
120
|
-
z.object({
|
|
121
|
-
ownerId: ids.playerId,
|
|
122
|
-
kind: z.string(),
|
|
123
|
-
}),
|
|
124
|
-
)
|
|
125
|
-
.default({}),
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
type OwnershipBySpaceId = TiledSpaceMap<
|
|
129
|
-
typeof manifestContract.table,
|
|
130
|
-
"mars",
|
|
131
|
-
string
|
|
132
|
-
>;
|
|
133
|
-
type EffectByEdgeId = TiledEdgeMap<
|
|
134
|
-
typeof manifestContract.table,
|
|
135
|
-
"arena",
|
|
136
|
-
string
|
|
137
|
-
>;
|
|
138
|
-
type MarkerByVertexId = TiledVertexMap<
|
|
139
|
-
typeof manifestContract.table,
|
|
140
|
-
"arena",
|
|
141
|
-
{ ownerId: string; kind: string }
|
|
142
|
-
>;
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
Use `z.partialRecord(...)` for mutable tiled overlays. Runtime gameplay state is
|
|
146
|
-
usually sparse, so empty-object initialization should compile and missing keys
|
|
147
|
-
should mean "nothing placed here yet".
|
|
148
|
-
|
|
149
|
-
Use this pattern for:
|
|
150
|
-
|
|
151
|
-
- Terraforming Mars: `ownershipBySpaceId`, placed tile type, adjacency-bonus flags
|
|
152
|
-
- Gloomhaven: movement blockers by edge, trap markers by vertex, units keyed by
|
|
153
|
-
space
|
|
154
|
-
- Cascadia: adjacency-driven scoring keyed by placed habitat or wildlife tiles
|
|
155
|
-
|
|
156
|
-
## Projecting UI-ready boards
|
|
157
|
-
|
|
158
|
-
Reducer views should join immutable topology with mutable overlay state before
|
|
159
|
-
rendering. Destructure the injected `q` inside `project({ state, playerId, q })`
|
|
160
|
-
instead of constructing a fresh query from state.
|
|
161
|
-
|
|
162
|
-
```ts
|
|
163
|
-
import { defineView } from "@dreamboard/app-sdk/reducer";
|
|
164
|
-
import type { GameContract } from "./game-contract";
|
|
165
|
-
|
|
166
|
-
export const playerView = defineView<GameContract>()({
|
|
167
|
-
project({ state, q }) {
|
|
168
|
-
const board = q.board.tiled("arena");
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
boardId: board.id,
|
|
172
|
-
layout: board.layout,
|
|
173
|
-
spaces: Object.values(board.spaces).map((space) => ({
|
|
174
|
-
...space,
|
|
175
|
-
ownerId: state.publicState.ownershipBySpaceId[space.id] ?? null,
|
|
176
|
-
edgeIds: q.board.spaceEdges(board.id, space.id),
|
|
177
|
-
vertexIds: q.board.spaceVertices(board.id, space.id),
|
|
178
|
-
})),
|
|
179
|
-
legalEdgeIds: state.publicState.legalEdgeIds,
|
|
180
|
-
legalVertexIds: state.publicState.legalVertexIds,
|
|
181
|
-
};
|
|
182
|
-
},
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Keep canonical legality and scoring in reducer code. The UI should consume a
|
|
187
|
-
projected board view plus simple highlight or legal-target sets.
|
|
188
|
-
|
|
189
|
-
## Board shape and traversal
|
|
190
|
-
|
|
191
|
-
- `board.spaces` is an ID-keyed record.
|
|
192
|
-
- `board.edges` is an array of edge records.
|
|
193
|
-
- `board.vertices` is an array of vertex records.
|
|
194
|
-
|
|
195
|
-
<Warning>
|
|
196
|
-
Do not use `Object.keys(board.edges)` or `Object.keys(board.vertices)` to
|
|
197
|
-
discover runtime IDs. Those expressions return array indexes, not edge or
|
|
198
|
-
vertex IDs.
|
|
199
|
-
</Warning>
|
|
200
|
-
|
|
201
|
-
Use reducer-native query helpers when you need topology traversal:
|
|
202
|
-
|
|
203
|
-
- `q.board.spaceEdges(boardId, spaceId)`
|
|
204
|
-
- `q.board.spaceVertices(boardId, spaceId)`
|
|
205
|
-
- `q.board.incidentEdges(boardId, vertexId)`
|
|
206
|
-
- `q.board.incidentVertices(boardId, edgeId)`
|
|
207
|
-
|
|
208
|
-
`incidentEdges(...)` and `incidentVertices(...)` are geometry helpers. They
|
|
209
|
-
return all tiled edges or vertices that share the same incident spaces as the
|
|
210
|
-
input location. They do not apply gameplay-specific filters such as road
|
|
211
|
-
ownership or blocked-path rules.
|
|
212
|
-
|
|
213
|
-
## Wiring the UI
|
|
214
|
-
|
|
215
|
-
Use one of the layout-specific renderers:
|
|
216
|
-
|
|
217
|
-
- `HexGrid` for hex spaces, edges, and vertices
|
|
218
|
-
- `SquareGrid` for square cells, edges, and vertices
|
|
219
|
-
|
|
220
|
-
Use the headless topology hooks when the UI needs geometry-aware logic:
|
|
221
|
-
|
|
222
|
-
- `useBoardTopology(...)` for shared tiled queries
|
|
223
|
-
- `useHexBoard(...)` for hex-specific coordinate helpers
|
|
224
|
-
- `useSquareBoard(...)` for square-specific neighbor and distance variants
|
|
225
|
-
|
|
226
|
-
```tsx
|
|
227
|
-
import { HexGrid, SquareGrid, useBoardTopology, useSquareBoard } from "@dreamboard/ui-sdk";
|
|
228
|
-
|
|
229
|
-
function ArenaBoard({ board, onEdgeClick, onVertexClick }) {
|
|
230
|
-
const topology = useSquareBoard(board);
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<SquareGrid
|
|
234
|
-
board={board}
|
|
235
|
-
interactiveEdges={{ selectTargetId: onEdgeClick }}
|
|
236
|
-
interactiveVertices={{ selectTargetId: onVertexClick }}
|
|
237
|
-
renderCell={(row, col) => {
|
|
238
|
-
const cell = topology.getCellAt(row, col);
|
|
239
|
-
return <Cell ownerId={cell?.owner ?? null} />;
|
|
240
|
-
}}
|
|
241
|
-
renderPiece={(piece) => <Piece piece={piece} />}
|
|
242
|
-
/>
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
This keeps interactivity consistent across layouts. Authors do not need to
|
|
248
|
-
re-derive borders, corners, or adjacency in UI code.
|
|
249
|
-
|
|
250
|
-
Project the whole tiled board record into the reducer view. Then keep
|
|
251
|
-
game-specific legality beside it:
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
project({ state, q }) {
|
|
255
|
-
return {
|
|
256
|
-
board: q.board.tiled("arena"),
|
|
257
|
-
legalEdgeIds: state.publicState.legalEdgeIds,
|
|
258
|
-
legalVertexIds: state.publicState.legalVertexIds,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
Use this rule when deciding what to expose:
|
|
264
|
-
|
|
265
|
-
- `spaces` are enough for tile or cell rendering only
|
|
266
|
-
- `edges` are required when the UI should render or interact with borders
|
|
267
|
-
- `vertices` are required when the UI should render or interact with corners
|
|
268
|
-
|
|
269
|
-
For example, a route-building board view should project all three so the UI can
|
|
270
|
-
render paths and markers and also expose placement targets.
|
|
271
|
-
|
|
272
|
-
The important reducer-native detail is that target-layer IDs keep the board's
|
|
273
|
-
exact runtime types. If the reducer view projects typed `edge.id` and
|
|
274
|
-
`vertex.id` values, `board={board}` preserves those same ids through
|
|
275
|
-
`selectTargetId` instead of forcing the UI to remap topology ids by hand.
|
|
276
|
-
|
|
277
|
-
## Runtime IDs
|
|
278
|
-
|
|
279
|
-
Hex and square edge and vertex IDs are derived runtime IDs based on authored
|
|
280
|
-
space topology.
|
|
281
|
-
|
|
282
|
-
- author spaces in the manifest
|
|
283
|
-
- optionally attach authored edge and vertex metadata by `ref.spaces`
|
|
284
|
-
- read the runtime `edgeId` and `vertexId` values from reducer helpers,
|
|
285
|
-
`boardHelpers.edgeIds(...)`, `boardHelpers.vertexIds(...)`,
|
|
286
|
-
`boardHelpers.boardIdForPlayer(...)`, or the projected board view
|
|
287
|
-
- use those IDs as keys in reducer state and as click targets in the UI
|
|
288
|
-
|
|
289
|
-
<Warning>
|
|
290
|
-
Do not hardcode the derived string format in app code. Read the runtime
|
|
291
|
-
`edgeId` and `vertexId` values from the helpers and projected board view
|
|
292
|
-
above instead.
|
|
293
|
-
</Warning>
|
|
294
|
-
|
|
295
|
-
## Valid and invalid tiled refs
|
|
296
|
-
|
|
297
|
-
Author edge and vertex refs with real shared geometry, not just “nearby”
|
|
298
|
-
spaces. These examples show the difference.
|
|
299
|
-
|
|
300
|
-
### Square edge refs
|
|
301
|
-
|
|
302
|
-
Valid square edge ref: the two cells share one border.
|
|
303
|
-
|
|
304
|
-
```ts
|
|
305
|
-
{
|
|
306
|
-
layout: "square",
|
|
307
|
-
spaces: [
|
|
308
|
-
{ id: "a1", row: 0, col: 0 },
|
|
309
|
-
{ id: "a2", row: 0, col: 1 },
|
|
310
|
-
],
|
|
311
|
-
edges: [
|
|
312
|
-
{ ref: { spaces: ["a1", "a2"] }, typeId: "road" },
|
|
313
|
-
],
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Invalid square edge ref: diagonal cells do not share an edge.
|
|
318
|
-
|
|
319
|
-
```ts
|
|
320
|
-
{
|
|
321
|
-
layout: "square",
|
|
322
|
-
spaces: [
|
|
323
|
-
{ id: "a1", row: 0, col: 0 },
|
|
324
|
-
{ id: "b2", row: 1, col: 1 },
|
|
325
|
-
],
|
|
326
|
-
edges: [
|
|
327
|
-
{ ref: { spaces: ["a1", "b2"] }, typeId: "road" },
|
|
328
|
-
],
|
|
329
|
-
}
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
Why invalid: `a1` and `b2` only meet at a corner, so this must be a vertex ref
|
|
333
|
-
instead of an edge ref.
|
|
334
|
-
|
|
335
|
-
### Square vertex refs
|
|
336
|
-
|
|
337
|
-
Valid square vertex ref: the cells resolve to one shared corner.
|
|
338
|
-
|
|
339
|
-
```ts
|
|
340
|
-
{
|
|
341
|
-
layout: "square",
|
|
342
|
-
spaces: [
|
|
343
|
-
{ id: "a1", row: 0, col: 0 },
|
|
344
|
-
{ id: "a2", row: 0, col: 1 },
|
|
345
|
-
{ id: "b1", row: 1, col: 0 },
|
|
346
|
-
{ id: "b2", row: 1, col: 1 },
|
|
347
|
-
],
|
|
348
|
-
vertices: [
|
|
349
|
-
{ ref: { spaces: ["a1", "a2", "b1", "b2"] }, typeId: "tower" },
|
|
350
|
-
],
|
|
351
|
-
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Invalid square vertex ref: the cells do not collapse to exactly one corner.
|
|
355
|
-
|
|
356
|
-
```ts
|
|
357
|
-
{
|
|
358
|
-
layout: "square",
|
|
359
|
-
spaces: [
|
|
360
|
-
{ id: "a1", row: 0, col: 0 },
|
|
361
|
-
{ id: "a2", row: 0, col: 1 },
|
|
362
|
-
{ id: "c1", row: 2, col: 0 },
|
|
363
|
-
],
|
|
364
|
-
vertices: [
|
|
365
|
-
{ ref: { spaces: ["a1", "a2", "c1"] }, typeId: "tower" },
|
|
366
|
-
],
|
|
367
|
-
}
|
|
368
|
-
```
|
|
369
|
-
|
|
370
|
-
Why invalid: those three cells do not touch the same corner, so the ref cannot
|
|
371
|
-
resolve to one runtime vertex ID.
|
|
372
|
-
|
|
373
|
-
### Hex edge refs
|
|
374
|
-
|
|
375
|
-
Valid hex edge ref: the two hexes share one border.
|
|
376
|
-
|
|
377
|
-
```ts
|
|
378
|
-
{
|
|
379
|
-
layout: "hex",
|
|
380
|
-
spaces: [
|
|
381
|
-
{ id: "hex-a", q: 0, r: 0 },
|
|
382
|
-
{ id: "hex-b", q: 1, r: 0 },
|
|
383
|
-
],
|
|
384
|
-
edges: [
|
|
385
|
-
{ ref: { spaces: ["hex-a", "hex-b"] }, typeId: "road" },
|
|
386
|
-
],
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
Invalid hex edge ref: non-adjacent hexes do not form an edge.
|
|
391
|
-
|
|
392
|
-
```ts
|
|
393
|
-
{
|
|
394
|
-
layout: "hex",
|
|
395
|
-
spaces: [
|
|
396
|
-
{ id: "hex-a", q: 0, r: 0 },
|
|
397
|
-
{ id: "hex-c", q: 2, r: 0 },
|
|
398
|
-
],
|
|
399
|
-
edges: [
|
|
400
|
-
{ ref: { spaces: ["hex-a", "hex-c"] }, typeId: "road" },
|
|
401
|
-
],
|
|
402
|
-
}
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Why invalid: `hex-a` and `hex-c` are separated by another hex, so there is no
|
|
406
|
-
shared border to attach an edge to.
|
|
407
|
-
|
|
408
|
-
### Hex vertex refs
|
|
409
|
-
|
|
410
|
-
Valid hex vertex ref: the three hexes meet at one corner.
|
|
411
|
-
|
|
412
|
-
```ts
|
|
413
|
-
{
|
|
414
|
-
layout: "hex",
|
|
415
|
-
spaces: [
|
|
416
|
-
{ id: "hex-a", q: 0, r: 0 },
|
|
417
|
-
{ id: "hex-b", q: 1, r: 0 },
|
|
418
|
-
{ id: "hex-c", q: 0, r: 1 },
|
|
419
|
-
],
|
|
420
|
-
vertices: [
|
|
421
|
-
{ ref: { spaces: ["hex-a", "hex-b", "hex-c"] }, typeId: "settlement" },
|
|
422
|
-
],
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
Invalid hex vertex ref: collinear hexes do not share a single corner.
|
|
427
|
-
|
|
428
|
-
```ts
|
|
429
|
-
{
|
|
430
|
-
layout: "hex",
|
|
431
|
-
spaces: [
|
|
432
|
-
{ id: "hex-a", q: 0, r: 0 },
|
|
433
|
-
{ id: "hex-b", q: 1, r: 0 },
|
|
434
|
-
{ id: "hex-c", q: 2, r: 0 },
|
|
435
|
-
],
|
|
436
|
-
vertices: [
|
|
437
|
-
{ ref: { spaces: ["hex-a", "hex-b", "hex-c"] }, typeId: "settlement" },
|
|
438
|
-
],
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
Why invalid: those three hexes never meet at one point, so the ref fails
|
|
443
|
-
validation instead of producing an ambiguous runtime vertex.
|