dreamboard 0.1.15 → 0.1.17

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.
@@ -5509,7 +5509,7 @@ function renderCardPropertiesSchemaByCardSetId(cardSets) {
5509
5509
  ` ${quote(cardSet.id)}: ${toPascalCase(cardSet.id)}CardPropertiesSchema,`
5510
5510
  ];
5511
5511
  });
5512
- return `const cardPropertiesSchemaByCardSetId: Record<string, z.ZodTypeAny> = {
5512
+ return `const cardPropertiesSchemaByCardSetId: Record<string, z.ZodType<unknown>> = {
5513
5513
  ${entries.join("\n")}
5514
5514
  };`;
5515
5515
  }
@@ -5715,7 +5715,7 @@ function renderCardStateSchemaById(analysis) {
5715
5715
  return `z.object(
5716
5716
  Object.fromEntries(
5717
5717
  literals.cardIds.map((cardId) => [cardId, createCardStateSchema(cardId)]),
5718
- ) as Record<CardId, z.ZodTypeAny>,
5718
+ ) as Record<CardId, z.ZodType<unknown>>,
5719
5719
  )`;
5720
5720
  }
5721
5721
  function renderCardStateSchemaFactory(analysis) {
@@ -5793,8 +5793,17 @@ function renderGenericBoardStateSchema(board, runtimeBoardId) {
5793
5793
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5794
5794
  templateId: z.string().nullable().optional(),
5795
5795
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5796
+ // T220: per-board state.spaces is loose-keyed by string. The
5797
+ // canonical narrow id (\`ids.spaceId\`) is a Zod enum of EVERY
5798
+ // space id across every board, which makes a strict
5799
+ // \`z.record(enum, \u2026)\` reject any board whose spaces are a
5800
+ // proper subset of that enum. Loose keying matches the JSON wire
5801
+ // shape (\`additionalProperties\`); the inner \`id: ids.spaceId\`
5802
+ // narrows the value's spaceId at parse time. A future per-board
5803
+ // branded-id refactor (option B) can re-tighten this without a
5804
+ // wire change.
5796
5805
  spaces: z.record(
5797
- ids.spaceId,
5806
+ z.string(),
5798
5807
  z.object({
5799
5808
  id: ids.spaceId,
5800
5809
  name: z.string().nullable().optional(),
@@ -5840,8 +5849,9 @@ function renderHexBoardStateSchema(board, runtimeBoardId) {
5840
5849
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5841
5850
  templateId: z.string().nullable().optional(),
5842
5851
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5852
+ // T220: see generic-board comment on the loose-keying choice.
5843
5853
  spaces: z.record(
5844
- ids.spaceId,
5854
+ z.string(),
5845
5855
  z.object({
5846
5856
  id: ids.spaceId,
5847
5857
  name: z.string().nullable().optional(),
@@ -5897,8 +5907,9 @@ function renderSquareBoardStateSchema(board, runtimeBoardId) {
5897
5907
  playerId: ${playerId ? `z.literal(${quote(playerId)})` : "ids.playerId.nullable().optional()"},
5898
5908
  templateId: z.string().nullable().optional(),
5899
5909
  fields: ${`${boardFieldsTypeName(board.board.id)}Schema`},
5910
+ // T220: see generic-board comment on the loose-keying choice.
5900
5911
  spaces: z.record(
5901
- ids.spaceId,
5912
+ z.string(),
5902
5913
  z.object({
5903
5914
  id: ids.spaceId,
5904
5915
  name: z.string().nullable().optional(),
@@ -7611,7 +7622,11 @@ const runtimeGenericBoardStateSchema = z.object({
7611
7622
  playerId: ids.playerId.nullable().optional(),
7612
7623
  templateId: z.string().nullable().optional(),
7613
7624
  fields: unknownRecordSchema,
7614
- spaces: z.record(ids.spaceId, boardSpaceStateSchema),
7625
+ // T220: per-board state.spaces is loose-keyed by string. See the
7626
+ // codegen-template comment in renderGenericBoardStateSchema for
7627
+ // the rationale; the wire shape is unchanged (additionalProperties
7628
+ // JSON), and the inner id field narrows at parse time.
7629
+ spaces: z.record(z.string(), boardSpaceStateSchema),
7615
7630
  relations: z.array(boardRelationStateSchema),
7616
7631
  containers: z.record(ids.boardContainerId, boardContainerStateSchema),
7617
7632
  });
@@ -7641,7 +7656,8 @@ const squareVertexStateSchema = z.object({
7641
7656
  });
7642
7657
  const runtimeHexBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7643
7658
  layout: z.literal("hex"),
7644
- spaces: z.record(ids.spaceId, hexSpaceStateSchema),
7659
+ // T220: loose-keyed by string \u2014 see comment above.
7660
+ spaces: z.record(z.string(), hexSpaceStateSchema),
7645
7661
  relations: z.array(boardRelationStateSchema),
7646
7662
  containers: z.object({}),
7647
7663
  orientation: z.enum(["pointy-top", "flat-top"]),
@@ -7650,7 +7666,8 @@ const runtimeHexBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7650
7666
  });
7651
7667
  const runtimeSquareBoardStateSchema = runtimeGenericBoardStateSchema.extend({
7652
7668
  layout: z.literal("square"),
7653
- spaces: z.record(ids.spaceId, squareSpaceStateSchema),
7669
+ // T220: loose-keyed by string \u2014 see comment above.
7670
+ spaces: z.record(z.string(), squareSpaceStateSchema),
7654
7671
  relations: z.array(boardRelationStateSchema),
7655
7672
  containers: z.record(ids.boardContainerId, boardContainerStateSchema),
7656
7673
  edges: z.array(hexEdgeStateSchema),
@@ -7862,11 +7879,11 @@ export const schemas = {
7862
7879
  } as const;
7863
7880
 
7864
7881
  export function createGameStateSchema<
7865
- PhaseNameSchema extends z.ZodTypeAny,
7866
- PublicSchema extends z.ZodTypeAny,
7867
- PrivateSchema extends z.ZodTypeAny,
7868
- HiddenSchema extends z.ZodTypeAny,
7869
- PhasesSchema extends z.ZodTypeAny,
7882
+ PhaseNameSchema extends z.ZodType<unknown>,
7883
+ PublicSchema extends z.ZodType<unknown>,
7884
+ PrivateSchema extends z.ZodType<unknown>,
7885
+ HiddenSchema extends z.ZodType<unknown>,
7886
+ PhasesSchema extends z.ZodType<unknown>,
7870
7887
  >({
7871
7888
  phaseNameSchema,
7872
7889
  publicSchema,
@@ -8050,6 +8067,27 @@ import { literals } from "./manifest-literals";
8050
8067
  import type { PlayerId as PublicPlayerId, TableState as PublicTableState } from "./manifest-types";
8051
8068
 
8052
8069
  const unknownRecordSchema`
8070
+ ).replaceAll(
8071
+ "literals.playerIds as unknown as readonly PlayerId[]",
8072
+ "literals.playerIds"
8073
+ ).replaceAll(
8074
+ "cardId: cardIdSchema as unknown as z.ZodType<CardId>,",
8075
+ "cardId: assumeManifestSchema<CardId>(cardIdSchema),"
8076
+ ).replaceAll(
8077
+ "deckId: deckIdSchema as unknown as z.ZodType<DeckId>,",
8078
+ "deckId: assumeManifestSchema<DeckId>(deckIdSchema),"
8079
+ ).replaceAll(
8080
+ "handId: handIdSchema as unknown as z.ZodType<HandId>,",
8081
+ "handId: assumeManifestSchema<HandId>(handIdSchema),"
8082
+ ).replace(
8083
+ /export const staticBoards = ([\s\S]*?) as const;\n\nconst baseInitialTable = /,
8084
+ "export const staticBoards = $1 as const satisfies StaticBoards<PublicTableState>;\n\nconst baseInitialTable = "
8085
+ ).replace(
8086
+ /const baseInitialTable = ([\s\S]*?) as const as unknown as TableState;\nconst baseDeckCardsByZoneId:/,
8087
+ "const baseInitialTable = cloneManifestDefault<PublicTableState>($1);\nconst baseDeckCardsByZoneId:"
8088
+ ).replace(
8089
+ "staticBoards: staticBoards as unknown as StaticBoards<TableState>,",
8090
+ "staticBoards,"
8053
8091
  ).replace(
8054
8092
  `export const manifestContract: ReducerManifestContract<
8055
8093
  RuntimeTableRecord,`,
@@ -8071,12 +8109,14 @@ const unknownRecordSchema`
8071
8109
  .min(1)
8072
8110
  .transform((value) => asPlayerId(value)),
8073
8111
  );`,
8074
- `const playerIdSchema = markManifestScopedSchema(
8075
- z
8076
- .string()
8077
- .min(1)
8078
- .transform((value) => asPlayerId(value)),
8079
- ) as unknown as z.ZodType<PublicPlayerId>;`
8112
+ `const playerIdSchema = assumeManifestSchema<PublicPlayerId>(
8113
+ markManifestScopedSchema(
8114
+ z
8115
+ .string()
8116
+ .min(1)
8117
+ .transform((value) => asPlayerId(value)),
8118
+ ),
8119
+ );`
8080
8120
  );
8081
8121
  }
8082
8122
  function renderCardSetTypeSections(analysis) {
@@ -9157,9 +9197,9 @@ import {
9157
9197
  createClientParamSchemasByPhase,
9158
9198
  } from "@dreamboard/app-sdk/reducer";
9159
9199
  import type {
9160
- ClientParamsOfInteractionOfDefinition,
9161
- DefaultedClientParamKeysOfInteractionOfDefinition,
9162
- InteractionIdOfDefinition,
9200
+ ClientParamsOfInteractionOfDefinition,
9201
+ DefaultedClientParamKeysOfInteractionOfDefinition,
9202
+ InteractionIdOfDefinition,
9163
9203
  InteractionIdOfDefinitionPhase,
9164
9204
  PhaseNamesOfDefinition,
9165
9205
  StageNamesOfDefinitionPhase,
@@ -9167,35 +9207,38 @@ import type {
9167
9207
  ViewOfDefinition,
9168
9208
  } from "@dreamboard/app-sdk/reducer";
9169
9209
  import {
9170
- createDreamboardUI,
9171
- createHexBoardView,
9172
- InteractionField as InteractionFieldGeneric,
9173
- InteractionForm as InteractionFormGeneric,
9174
- useActivePlayers as useActivePlayersGeneric,
9175
- useBoardInteractions as useBoardInteractionsGeneric,
9176
- useGameView as useGameViewGeneric,
9177
- useInteractionByKey as useInteractionByKeyGeneric,
9178
- usePlayerTurnOrder as usePlayerTurnOrderGeneric,
9179
- type BoardInteractionsContext,
9180
- type BoardInteractionsOptions,
9181
- type BoardSpaceIdOf,
9182
- type ClientParamSchemaMap,
9183
- type HexBoardView,
9184
- type InteractionDescriptor,
9185
- type InteractionFieldProps as InteractionFieldPropsGeneric,
9186
- type InteractionFieldRenderMap,
9187
- type InteractionFormProps as InteractionFormPropsGeneric,
9188
- type InteractionHandle,
9189
- type DreamboardUI,
9190
- type UIContract,
9191
- } from "@dreamboard/ui-sdk";
9192
- import { createElement, useMemo, type ReactElement } from "react";
9193
- import {
9194
- literals,
9195
- staticBoards,
9196
- type CardId,
9197
- type EdgeId,
9210
+ createDreamboardUI,
9211
+ createResourceCounter,
9212
+ type BoardHexGridProps as BoardHexGridPropsGeneric,
9213
+ type BoardHexViewProps as BoardHexViewPropsGeneric,
9214
+ type BoardSpaceIdOf,
9215
+ type ClientParamSchemaMap,
9216
+ type InteractionDescriptor,
9217
+ type InteractionHandle,
9218
+ type InteractionRouteMap as InteractionRouteMapGeneric,
9219
+ type InteractionSwitchProps as InteractionSwitchPropsGeneric,
9220
+ type InteractionSwitchRenderState,
9221
+ type DreamboardUI,
9222
+ type ResourceCounterComponents,
9223
+ type ResourceDisplayConfig,
9224
+ type TypedGame,
9225
+ type UIContract,
9226
+ type UIRootProps,
9227
+ type ZoneCardAtProps,
9228
+ type ZoneCardRenderItem,
9229
+ type ZoneListProps,
9230
+ type ZonePileCardsProps,
9231
+ } from "@dreamboard/ui-sdk";
9232
+ import { createElement, type ReactElement, type ReactNode } from "react";
9233
+ import {
9234
+ literals,
9235
+ staticBoards,
9236
+ type CardId,
9237
+ type CardProperties,
9238
+ type CardType,
9239
+ type EdgeId,
9198
9240
  type PlayerId,
9241
+ type ResourceId,
9199
9242
  type SpaceId,
9200
9243
  type VertexId,
9201
9244
  type ZoneId as ManifestZoneId,
@@ -9294,8 +9337,7 @@ export type InteractionDescriptorFor<Key extends InteractionKey> =
9294
9337
 
9295
9338
  /**
9296
9339
  * Params shape for a phase-qualified interaction key. Drives strong typing
9297
- * for \`useInteractionByKey\`'s draft,
9298
- * \`submit\`, and \`setInput\`.
9340
+ * for component-first interaction state, forms, and submits.
9299
9341
  */
9300
9342
  export type InteractionParamsOf<Key extends InteractionKey> =
9301
9343
  InteractionParams<PhaseOfInteractionKey<Key>, IdOfInteractionKey<Key>>;
@@ -9318,6 +9360,50 @@ type InteractionHandleDefaultedKeys<Key extends InteractionKey> = Extract<
9318
9360
  keyof InteractionParamsShape<Key> & string
9319
9361
  >;
9320
9362
 
9363
+ type RequiredInteractionInputKeysOf<Key extends InteractionKey> = Exclude<
9364
+ InteractionInputKeysOf<Key>,
9365
+ InteractionHandleDefaultedKeys<Key>
9366
+ >;
9367
+
9368
+ export type RequiredInteractionInputKey<Key extends InteractionKey> =
9369
+ RequiredInteractionInputKeysOf<Key>;
9370
+
9371
+ export type ZeroInputInteractionKey = {
9372
+ [K in InteractionKey]: InteractionInputKeysOf<K> extends never
9373
+ ? K
9374
+ : never;
9375
+ }[InteractionKey];
9376
+
9377
+ export type InputInteractionKey = Exclude<
9378
+ InteractionKey,
9379
+ ZeroInputInteractionKey
9380
+ >;
9381
+
9382
+ export type InteractionRouteRenderState<Key extends InteractionKey> =
9383
+ Omit<InteractionSwitchRenderState<Key>, "descriptor"> & {
9384
+ descriptor: InteractionDescriptorFor<Key>;
9385
+ };
9386
+
9387
+ export type InteractionRouteMap = {
9388
+ [K in InteractionKey]: (state: InteractionRouteRenderState<K>) => ReactNode;
9389
+ };
9390
+
9391
+ type ExactInteractionRoutes<Routes extends InteractionRouteMap> = Routes &
9392
+ Record<Exclude<keyof Routes, keyof InteractionRouteMap>, never>;
9393
+
9394
+ export function defineInteractionRoutes<const Routes extends InteractionRouteMap>(
9395
+ routes: ExactInteractionRoutes<Routes>,
9396
+ ): InteractionRouteMap {
9397
+ return routes;
9398
+ }
9399
+
9400
+ type InteractionSwitchProps = Omit<
9401
+ InteractionSwitchPropsGeneric<InteractionKey>,
9402
+ "routes"
9403
+ > & {
9404
+ routes: InteractionRouteMap;
9405
+ };
9406
+
9321
9407
  type InteractionInputKeyOf<Key extends InteractionKey> =
9322
9408
  Key extends InteractionKey ? InteractionInputKeysOf<Key> : never;
9323
9409
 
@@ -9349,6 +9435,10 @@ type UIPromptOptionRegistry = {
9349
9435
  [K in PromptOptionValue]: { value: K };
9350
9436
  };
9351
9437
 
9438
+ type UIPlayerRegistry = {
9439
+ [K in PlayerId & string]: { player: K };
9440
+ };
9441
+
9352
9442
  type UIZoneRegistry = {
9353
9443
  [K in ZoneId & string]: { zone: K };
9354
9444
  };
@@ -9370,6 +9460,7 @@ export const uiContract = {
9370
9460
  inputs: {} as UIInputRegistry,
9371
9461
  prompts: {} as UIPromptRegistry,
9372
9462
  promptOptions: {} as UIPromptOptionRegistry,
9463
+ players: {} as UIPlayerRegistry,
9373
9464
  zones: {} as UIZoneRegistry,
9374
9465
  cards: {} as UICardRegistry,
9375
9466
  phases: {} as UIPhaseRegistry,
@@ -9383,142 +9474,6 @@ declare module "@dreamboard/ui-sdk" {
9383
9474
  }
9384
9475
  }
9385
9476
 
9386
- type WorkspaceUI = DreamboardUI<typeof uiContract>;
9387
-
9388
- export const UI: WorkspaceUI = createDreamboardUI(uiContract);
9389
- export const Interaction = UI.Interaction;
9390
- export const Prompt = UI.Prompt;
9391
- export const PromptInbox = UI.PromptInbox;
9392
- export const PromptDialogHost = UI.PromptDialogHost;
9393
- export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
9394
- export const Phase = UI.Phase;
9395
- export const Zone = UI.Zone;
9396
- export const Board = UI.Board;
9397
-
9398
- export type InteractionFieldRenderers<Key extends InteractionKey> =
9399
- InteractionFieldRenderMap<InteractionParamsShape<Key>>;
9400
-
9401
- export type InteractionFormProps<Key extends InteractionKey> = Omit<
9402
- InteractionFormPropsGeneric<
9403
- InteractionParamsShape<Key>,
9404
- InteractionHandleDefaultedKeys<Key>
9405
- >,
9406
- "descriptor" | "handle"
9407
- > & {
9408
- descriptor: InteractionDescriptorFor<Key>;
9409
- handle: InteractionHandle<
9410
- InteractionParamsShape<Key>,
9411
- InteractionHandleDefaultedKeys<Key>
9412
- >;
9413
- renderFields?: InteractionFieldRenderers<Key>;
9414
- };
9415
-
9416
- export type InteractionFieldProps<
9417
- Key extends InteractionKey,
9418
- InputKey extends keyof InteractionParamsShape<Key> & string,
9419
- > = Omit<
9420
- InteractionFieldPropsGeneric<InteractionParamsShape<Key>, InputKey>,
9421
- "descriptor" | "handle"
9422
- > & {
9423
- descriptor: InteractionDescriptorFor<Key>;
9424
- handle: InteractionHandle<
9425
- InteractionParamsShape<Key>,
9426
- InteractionHandleDefaultedKeys<Key>
9427
- >;
9428
- };
9429
-
9430
- export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
9431
- game,
9432
- ) as ClientParamSchemaMap;
9433
-
9434
- /**
9435
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s generic
9436
- * \`useInteractionByKey\`. The \`key\` argument is constrained to the
9437
- * generated {@link InteractionKey} union (typos are compile errors), and
9438
- * the returned {@link InteractionHandle} is parameterised on
9439
- * {@link InteractionParamsOf} so \`handle.draft\`, \`handle.submit\`, and
9440
- * \`handle.setInput\` are statically typed against the interaction's
9441
- * declared inputs.
9442
- *
9443
- * \`\`\`ts
9444
- * const handle = useInteractionByKey("play.placeThingCard");
9445
- * if (!handle) return null;
9446
- * handle.setInput("cardId", card.id); // inferred as ThingsDeckCardId
9447
- * await handle.submit(); // params typed from the reducer contract
9448
- * \`\`\`
9449
- */
9450
- export function useInteractionByKey<Key extends InteractionKey>(
9451
- key: Key | null | undefined,
9452
- ): InteractionHandle<
9453
- InteractionParamsShape<Key>,
9454
- InteractionHandleDefaultedKeys<Key>
9455
- > | null {
9456
- return useInteractionByKeyGeneric<
9457
- Key,
9458
- InteractionParamsShape<Key>,
9459
- InteractionHandleDefaultedKeys<Key>
9460
- >(key);
9461
- }
9462
-
9463
- export function InteractionForm<Key extends InteractionKey>(
9464
- props: InteractionFormProps<Key>,
9465
- ): ReactElement {
9466
- return createElement(
9467
- InteractionFormGeneric<
9468
- InteractionParamsShape<Key>,
9469
- InteractionHandleDefaultedKeys<Key>
9470
- >,
9471
- props,
9472
- );
9473
- }
9474
-
9475
- export function InteractionField<
9476
- Key extends InteractionKey,
9477
- InputKey extends keyof InteractionParamsShape<Key> & string,
9478
- >(props: InteractionFieldProps<Key, InputKey>): ReactElement | null {
9479
- return createElement(
9480
- InteractionFieldGeneric<InteractionParamsShape<Key>, InputKey>,
9481
- props,
9482
- );
9483
- }
9484
-
9485
- /**
9486
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
9487
- * \`useBoardInteractions\`. Returns a {@link BoardInteractionsContext}
9488
- * narrowed to this workspace's board-interaction union for exhaustive
9489
- * downstream logic.
9490
- *
9491
- * \`\`\`tsx
9492
- * const board = useBoardInteractions();
9493
- * <HexGrid interactiveVertices={board.targetLayers.vertex()} />
9494
- * \`\`\`
9495
- *
9496
- * Reach for this whenever a screen keeps more than one board target
9497
- * interaction live simultaneously and dispatch is target-driven instead of
9498
- * armed-then-clicked. Ambiguous unarmed overlaps should be resolved in the UI
9499
- * by arming an explicit interaction/input route.
9500
- */
9501
- export function useBoardInteractions(
9502
- options?: BoardInteractionsOptions,
9503
- ): BoardInteractionsContext<BoardInteractions> {
9504
- return useBoardInteractionsGeneric<BoardInteractions>(options);
9505
- }
9506
-
9507
- /** Workspace-typed active-player hook. */
9508
- export function useActivePlayers(): readonly PlayerId[] {
9509
- return useActivePlayersGeneric() as readonly PlayerId[];
9510
- }
9511
-
9512
- /** Workspace-typed reducer-native player view hook. */
9513
- export function useGameView(): GameView {
9514
- return useGameViewGeneric() as GameView;
9515
- }
9516
-
9517
- /** Workspace-typed player turn order hook. */
9518
- export function usePlayerTurnOrder(): readonly PlayerId[] {
9519
- return usePlayerTurnOrderGeneric() as readonly PlayerId[];
9520
- }
9521
-
9522
9477
  // -------------------------------------------------------------------------
9523
9478
  // Typed hex-board view adapter
9524
9479
  // -------------------------------------------------------------------------
@@ -9537,49 +9492,176 @@ export type HexBoardSpaceId<Id extends HexBoardId> = BoardSpaceIdOf<
9537
9492
  HexBoardTopology<Id>
9538
9493
  >;
9539
9494
 
9540
- /**
9541
- * Workspace-typed wrapper over \`@dreamboard/ui-sdk\`'s
9542
- * \`createHexBoardView\`. Joins the static topology of the named hex
9543
- * board with a per-space view overlay and returns a value ready for
9544
- * \`<HexGrid board={...} />\`. Each rendered tile carries a \`view\`
9545
- * field typed as the matched overlay row.
9546
- *
9547
- * Strict by construction: every static space must have exactly one
9548
- * overlay; missing, duplicate, or unknown overlay ids throw.
9549
- *
9550
- * \`\`\`tsx
9551
- * const island = useHexBoardView("island", { spaces: view.spaces });
9552
- *
9553
- * <HexGrid
9554
- * board={island}
9555
- * renderTile={(tile) => {
9556
- * const terrain = tile.view.terrain;
9557
- * // ...
9558
- * }}
9559
- * />
9560
- * \`\`\`
9561
- */
9562
- export function useHexBoardView<
9563
- const Id extends HexBoardId,
9564
- const TSpaceView extends { id: HexBoardSpaceId<Id> },
9565
- >(
9566
- boardId: Id,
9567
- options: { spaces: ReadonlyArray<TSpaceView> },
9568
- ): HexBoardView<HexBoardTopology<Id>, TSpaceView> {
9569
- const board = hexStaticBoards[boardId];
9570
- const { spaces } = options;
9571
- return useMemo(
9572
- () => createHexBoardView<HexBoardTopology<Id>, TSpaceView>(board, { spaces }),
9573
- [board, spaces],
9574
- );
9575
- }
9495
+ export type HexBoardViewProps<
9496
+ Id extends HexBoardId,
9497
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
9498
+ > = Omit<BoardHexViewPropsGeneric<HexBoardTopology<Id>, TSpaceView>, "board"> & {
9499
+ board: Id;
9500
+ };
9501
+
9502
+ export type HexBoardGridProps<
9503
+ Id extends HexBoardId,
9504
+ TSpaceView extends { id: HexBoardSpaceId<Id> },
9505
+ > = Omit<
9506
+ BoardHexGridPropsGeneric<HexBoardTopology<Id>, TSpaceView>,
9507
+ "board" | "interactions"
9508
+ > & {
9509
+ board: Id;
9510
+ interactions?:
9511
+ | "auto"
9512
+ | false
9513
+ | {
9514
+ edge?: readonly InteractionKey[];
9515
+ vertex?: readonly InteractionKey[];
9516
+ space?: readonly InteractionKey[];
9517
+ };
9518
+ };
9519
+
9520
+ type WorkspaceBoard = Omit<DreamboardUI<typeof uiContract>["Board"], "HexView" | "HexGrid"> & {
9521
+ HexView<
9522
+ const Id extends HexBoardId,
9523
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9524
+ >(props: HexBoardViewProps<Id, TSpaceView>): ReactElement;
9525
+ HexGrid<
9526
+ const Id extends HexBoardId,
9527
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9528
+ >(props: HexBoardGridProps<Id, TSpaceView>): ReactElement;
9529
+ };
9530
+
9531
+ type WorkspaceCardProperties = CardProperties extends Record<string, unknown>
9532
+ ? CardProperties
9533
+ : Record<string, unknown>;
9534
+ type WorkspaceZoneId = [ZoneId] extends [never] ? string : ZoneId;
9535
+ type WorkspaceCardId = [CardId] extends [never] ? string : CardId;
9536
+ type WorkspaceCardType = [CardType] extends [never] ? string : CardType;
9537
+
9538
+ // ZoneCardRenderItem is a discriminated union (hidden: true | false).
9539
+ // A bare Omit<..., "zone"> would collapse the union to its common keys and
9540
+ // strip cardType / properties from the hydrated branch, defeating the
9541
+ // discriminated-union narrowing the SDK contract promises. Distribute Omit
9542
+ // across the union so each branch keeps its own shape.
9543
+ type WorkspaceZoneCard = ZoneCardRenderItem<
9544
+ WorkspaceCardId & string,
9545
+ WorkspaceCardType & string,
9546
+ WorkspaceCardProperties
9547
+ > extends infer Item
9548
+ ? Item extends { zone: string }
9549
+ ? Omit<Item, "zone"> & { zone: WorkspaceZoneId }
9550
+ : never
9551
+ : never;
9552
+
9553
+ type WorkspaceZone = Omit<
9554
+ DreamboardUI<typeof uiContract>["Zone"],
9555
+ "CardAt" | "TopCard" | "List" | "PileCards"
9556
+ > & {
9557
+ CardAt(
9558
+ props: Omit<ZoneCardAtProps<WorkspaceZoneId>, "zone" | "children"> & {
9559
+ zone?: WorkspaceZoneId;
9560
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9561
+ },
9562
+ ): ReactElement | null;
9563
+ TopCard(
9564
+ props: Omit<
9565
+ ZoneCardAtProps<WorkspaceZoneId>,
9566
+ "zone" | "index" | "children"
9567
+ > & {
9568
+ zone?: WorkspaceZoneId;
9569
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9570
+ },
9571
+ ): ReactElement | null;
9572
+ List(
9573
+ props: Omit<ZoneListProps, "children"> & {
9574
+ children?: ReactNode | ((card: WorkspaceZoneCard) => ReactNode);
9575
+ },
9576
+ ): ReactElement;
9577
+ PileCards(
9578
+ props: Omit<ZonePileCardsProps, "renderCard"> & {
9579
+ renderCard: (card: WorkspaceZoneCard) => ReactNode;
9580
+ },
9581
+ ): ReactElement | null;
9582
+ };
9583
+
9584
+ type WorkspaceUI = Omit<
9585
+ DreamboardUI<typeof uiContract>,
9586
+ "Root" | "Game" | "Interaction" | "Board" | "Zone"
9587
+ > & {
9588
+ Root(props: UIRootProps): ReactElement;
9589
+ readonly Game: TypedGame<typeof uiContract, GameView, PlayerId, PhaseName>;
9590
+ readonly Interaction: Omit<
9591
+ DreamboardUI<typeof uiContract>["Interaction"],
9592
+ "Switch"
9593
+ > & {
9594
+ Switch(props: InteractionSwitchProps): ReactElement;
9595
+ };
9596
+ readonly Board: WorkspaceBoard;
9597
+ readonly Zone: WorkspaceZone;
9598
+ readonly ResourceCounter: ResourceCounterComponents<ResourceId>;
9599
+ };
9600
+
9601
+ const baseUI = createDreamboardUI(uiContract);
9602
+
9603
+ const resourcePresentationById = literals.resourcePresentationById as Partial<
9604
+ Record<string, { label?: string; icon?: string }>
9605
+ >;
9606
+
9607
+ const resourceDisplayConfig = literals.resourceIds.map((resource) => {
9608
+ const resourceId = resource as ResourceId;
9609
+ const presentation = resourcePresentationById[resource as string];
9610
+ return {
9611
+ type: resourceId,
9612
+ label: presentation?.label ?? resource,
9613
+ icon: presentation?.icon ?? resource,
9614
+ };
9615
+ }) satisfies readonly ResourceDisplayConfig<ResourceId>[];
9616
+
9617
+ const resourceCounter = createResourceCounter<ResourceId>(resourceDisplayConfig);
9618
+
9619
+ export const Board: WorkspaceUI["Board"] = {
9620
+ ...baseUI.Board,
9621
+ HexView<
9622
+ const Id extends HexBoardId,
9623
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9624
+ >({ board: boardId, ...props }: HexBoardViewProps<Id, TSpaceView>) {
9625
+ return createElement(baseUI.Board.HexView<HexBoardTopology<Id>, TSpaceView>, {
9626
+ ...props,
9627
+ board: hexStaticBoards[boardId],
9628
+ });
9629
+ },
9630
+ HexGrid<
9631
+ const Id extends HexBoardId,
9632
+ const TSpaceView extends { id: HexBoardSpaceId<Id> },
9633
+ >({ board: boardId, ...props }: HexBoardGridProps<Id, TSpaceView>) {
9634
+ return createElement(baseUI.Board.HexGrid<HexBoardTopology<Id>, TSpaceView>, {
9635
+ ...props,
9636
+ board: hexStaticBoards[boardId],
9637
+ });
9638
+ },
9639
+ } as WorkspaceUI["Board"];
9640
+
9641
+ export const UI: WorkspaceUI = {
9642
+ ...baseUI,
9643
+ Board,
9644
+ ResourceCounter: resourceCounter,
9645
+ };
9646
+ export const Game: WorkspaceUI["Game"] = UI.Game;
9647
+ export const Interaction = UI.Interaction;
9648
+ export const Prompt = UI.Prompt;
9649
+ export const PromptInbox = UI.PromptInbox;
9650
+ export const PlayerRoster: WorkspaceUI["PlayerRoster"] = UI.PlayerRoster;
9651
+ export const Dice: WorkspaceUI["Dice"] = UI.Dice;
9652
+ export const Phase = UI.Phase;
9653
+ export const Zone = UI.Zone;
9654
+ export const ResourceCounter: WorkspaceUI["ResourceCounter"] = UI.ResourceCounter;
9655
+
9656
+ export const clientParamSchemasByPhase = createClientParamSchemasByPhase(
9657
+ game,
9658
+ ) as ClientParamSchemaMap;
9576
9659
  `;
9577
9660
  }
9578
9661
  function generateReducerSupportSeed() {
9579
9662
  return `import {
9580
- createReducerOps,
9663
+ createReducerEdit,
9581
9664
  createStateQueries,
9582
- pipe,
9583
9665
  } from "@dreamboard/app-sdk/reducer";
9584
9666
  import type { GameState } from "./game-contract";
9585
9667
 
@@ -9598,16 +9680,12 @@ import type { GameState } from "./game-contract";
9598
9680
  *
9599
9681
  * Recommended authoring pattern inside a phase reducer:
9600
9682
  *
9601
- * const next = pipe(
9602
- * state,
9603
- * ops.setActivePlayers([q.players.order()[0]]),
9604
- * );
9605
- * return accept(next);
9683
+ * const tx = edit(state);
9684
+ * tx.setActivePlayers([q.players.order()[0]]);
9685
+ * return accept(tx.state);
9606
9686
  */
9607
9687
 
9608
- export const ops = createReducerOps<GameState>();
9609
-
9610
- export { pipe };
9688
+ export const edit = createReducerEdit<GameState>();
9611
9689
 
9612
9690
  export function stateQueries(state: GameState) {
9613
9691
  return createStateQueries(state);
@@ -9688,7 +9766,7 @@ function generateReducerGameContractSeed() {
9688
9766
  import { ids, manifestContract } from "../shared/manifest-contract";
9689
9767
  import { defineGameContract, type GameStateOf } from "@dreamboard/app-sdk/reducer";
9690
9768
 
9691
- // Contract files should stay focused on schemas, phase names, and exported
9769
+ // Contract files should stay focused on schemas, phases, and exported
9692
9770
  // state types. Put reducers in app/phases/* and pure computations in
9693
9771
  // app/derived.ts or app/rules/*.
9694
9772
  const publicStateSchema = z.object({
@@ -9700,15 +9778,14 @@ const hiddenStateSchema = z.object({});
9700
9778
 
9701
9779
  export const gameContract = defineGameContract({
9702
9780
  manifest: manifestContract,
9703
- // Keep this list in sync with the keys of the \`phases\` record in ./phases.
9704
- // Declaring phase names here narrows \`fx.transition(...)\`, \`phase.dispatch\`,
9705
- // and the flow state's \`currentPhase\` to a literal union.
9706
- phaseNames: ["setup"] as const,
9707
9781
  state: {
9708
9782
  public: publicStateSchema,
9709
9783
  private: privateStateSchema,
9710
9784
  hidden: hiddenStateSchema,
9711
9785
  },
9786
+ phases: {
9787
+ setup: z.object({}),
9788
+ },
9712
9789
  });
9713
9790
 
9714
9791
  export type GameContract = typeof gameContract;
@@ -9809,8 +9886,10 @@ function generateAppFrameworkTsConfig() {
9809
9886
  moduleResolution: "bundler",
9810
9887
  strict: true,
9811
9888
  esModuleInterop: true,
9812
- skipLibCheck: true,
9889
+ skipLibCheck: false,
9890
+ types: [],
9813
9891
  declaration: false,
9892
+ rootDir: "..",
9814
9893
  outDir: "./dist",
9815
9894
  paths: {
9816
9895
  "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
@@ -9832,48 +9911,57 @@ function generateAppFrameworkTsConfig() {
9832
9911
  `;
9833
9912
  }
9834
9913
  function generateUiFrameworkTsConfig() {
9835
- return `{
9836
- "compilerOptions": {
9837
- "target": "ES2020",
9838
- "module": "ESNext",
9839
- "moduleResolution": "bundler",
9840
- "jsx": "react-jsx",
9841
- "strict": true,
9842
- "esModuleInterop": true,
9843
- "skipLibCheck": true,
9844
- "paths": {
9845
- "@dreamboard/manifest-contract": [
9846
- "../shared/manifest-contract.ts"
9847
- ],
9848
- "@dreamboard/ui-contract": [
9849
- "../shared/generated/ui-contract.ts"
9850
- ],
9851
- "@shared/*": [
9852
- "../shared/*"
9853
- ]
9854
- }
9855
- },
9856
- "include": [
9857
- "./**/*.ts",
9858
- "./**/*.tsx",
9859
- "../shared/**/*.ts"
9860
- ],
9861
- "exclude": [
9862
- "node_modules",
9863
- "dist"
9864
- ]
9865
- }
9914
+ return `${JSON.stringify(
9915
+ {
9916
+ compilerOptions: {
9917
+ target: "ES2020",
9918
+ module: "ESNext",
9919
+ moduleResolution: "bundler",
9920
+ jsx: "react-jsx",
9921
+ strict: true,
9922
+ esModuleInterop: true,
9923
+ skipLibCheck: false,
9924
+ types: [],
9925
+ paths: {
9926
+ "@dreamboard/manifest-contract": ["../shared/manifest-contract.ts"],
9927
+ "@dreamboard/ui-contract": ["../shared/generated/ui-contract.ts"],
9928
+ "@shared/*": ["../shared/*"]
9929
+ }
9930
+ },
9931
+ include: ["./**/*.ts", "./**/*.tsx", "../shared/**/*.ts"],
9932
+ exclude: ["node_modules", "dist"]
9933
+ },
9934
+ null,
9935
+ 2
9936
+ )}
9866
9937
  `;
9867
9938
  }
9868
9939
  function generateReducerUiAppContent() {
9869
- return `import { Phase, PromptInbox } from "@dreamboard/ui-contract";
9940
+ return `import { Game, Phase, Prompt, PromptInbox } from "@dreamboard/ui-contract";
9870
9941
 
9871
9942
  function SetupPhase() {
9872
9943
  return (
9873
9944
  <main>
9874
9945
  <PromptInbox.Root>
9875
9946
  <PromptInbox.Empty>No available prompts.</PromptInbox.Empty>
9876
- <PromptInbox.Items />
9947
+ <PromptInbox.Items>
9948
+ {(prompt) => (
9949
+ <Prompt.Root
9950
+ key={prompt.interactionKey}
9951
+ interaction={prompt.interactionKey}
9952
+ >
9953
+ <Prompt.Title />
9954
+ <Prompt.Message />
9955
+ <Prompt.Options>
9956
+ {(option) => (
9957
+ <Prompt.Option value={option.id}>
9958
+ {option.label ?? option.id}
9959
+ </Prompt.Option>
9960
+ )}
9961
+ </Prompt.Options>
9962
+ </Prompt.Root>
9963
+ )}
9964
+ </PromptInbox.Items>
9877
9965
  </PromptInbox.Root>
9878
9966
  </main>
9879
9967
  );
@@ -9881,11 +9969,15 @@ function SetupPhase() {
9881
9969
 
9882
9970
  export default function App() {
9883
9971
  return (
9884
- <Phase.Switch
9885
- routes={{
9886
- setup: () => <SetupPhase />,
9887
- }}
9888
- />
9972
+ <Game.Root>
9973
+ {() => (
9974
+ <Phase.Switch
9975
+ routes={{
9976
+ setup: () => <SetupPhase />,
9977
+ }}
9978
+ />
9979
+ )}
9980
+ </Game.Root>
9889
9981
  );
9890
9982
  }
9891
9983
  `;
@@ -14006,6 +14098,9 @@ var ProjectSeatsDynamicRequestSchema = external_exports.object({ "state": Reduce
14006
14098
  var BoardStaticProjectionSchema = external_exports.object({ "view": JsonValueSchema, "hash": external_exports.string().min(1), "manifestVersion": external_exports.string() }).strict();
14007
14099
 
14008
14100
  // ../../packages/app-sdk/src/reducer/per-player.ts
14101
+ function asPlayerId(raw) {
14102
+ return raw;
14103
+ }
14009
14104
  function perPlayerGet(value, id) {
14010
14105
  for (const [candidate, v] of value.entries) {
14011
14106
  if (candidate === id) {
@@ -14115,6 +14210,45 @@ function resolveDomainChoices(choices, context) {
14115
14210
  disabledReason: choice.disabledReason
14116
14211
  }));
14117
14212
  }
14213
+ function resolveDependentDomainChoices(choices, context) {
14214
+ const resolved = typeof choices === "function" ? choices(context) : choices;
14215
+ return resolved.map((choice) => ({
14216
+ value: choice.value,
14217
+ label: choice.label,
14218
+ icon: choice.icon,
14219
+ badge: choice.badge,
14220
+ description: choice.description,
14221
+ disabled: choice.disabled,
14222
+ disabledReason: choice.disabledReason
14223
+ }));
14224
+ }
14225
+ function choiceValuesInclude(choices, value) {
14226
+ return choices.some((choice) => Object.is(choice.value, value));
14227
+ }
14228
+ function assertChoiceDefaultInChoices(inputName, choices, defaultValue) {
14229
+ if (choiceValuesInclude(choices, defaultValue)) return;
14230
+ const printable = defaultValue === null ? "null" : `'${defaultValue}'`;
14231
+ throw new Error(
14232
+ `${inputName} defaultValue ${printable} must be one of its choices. If null is a valid value, add an explicit { value: null, label: ... } choice.`
14233
+ );
14234
+ }
14235
+ function choiceSchema(choices) {
14236
+ const values = choices.map((choice) => choice.value);
14237
+ const stringValues = values.filter(
14238
+ (value) => value !== null
14239
+ );
14240
+ const hasNull = values.some((value) => value === null);
14241
+ if (stringValues.length === 0) {
14242
+ return external_exports.null();
14243
+ }
14244
+ const stringSchema = external_exports.enum(
14245
+ stringValues
14246
+ );
14247
+ if (hasNull) {
14248
+ return external_exports.union([stringSchema, external_exports.null()]);
14249
+ }
14250
+ return stringSchema;
14251
+ }
14118
14252
  function resolveResourceMapChoices(choices, context) {
14119
14253
  const resources = context.state.table.resources;
14120
14254
  const firstResourceMap = isPerPlayer(resources) && resources.entries.length > 0 ? resources.entries[0]?.[1] : resources;
@@ -14151,7 +14285,13 @@ function resourceMapInput(options) {
14151
14285
  schema: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()),
14152
14286
  ..."defaultValue" in options ? { defaultValue: options.defaultValue } : {},
14153
14287
  domain: (state, playerId, q, derived) => {
14154
- const context = { state, playerId, q, derived };
14288
+ const context = {
14289
+ state,
14290
+ playerId,
14291
+ q,
14292
+ derived,
14293
+ values: {}
14294
+ };
14155
14295
  return {
14156
14296
  type: "resourceMap",
14157
14297
  resources: options.resources.map((resource) => ({
@@ -14171,7 +14311,13 @@ function numberInput(options) {
14171
14311
  schema: external_exports.number(),
14172
14312
  ..."defaultValue" in options ? { defaultValue: options.defaultValue } : {},
14173
14313
  domain: (state, playerId, q, derived) => {
14174
- const context = { state, playerId, q, derived };
14314
+ const context = {
14315
+ state,
14316
+ playerId,
14317
+ q,
14318
+ derived,
14319
+ values: {}
14320
+ };
14175
14321
  return {
14176
14322
  type: "boundedNumber",
14177
14323
  min: resolveDomainNumber(options.min, context, 0),
@@ -14182,23 +14328,80 @@ function numberInput(options) {
14182
14328
  };
14183
14329
  }
14184
14330
  function choiceInput(options) {
14331
+ const dependsOn = options.dependsOn?.map((dependency) => dependency.key);
14185
14332
  if (Array.isArray(options.choices) && options.choices.length === 0) {
14186
14333
  throw new Error("formInput.choice requires at least one choice.");
14187
14334
  }
14188
- const schema = Array.isArray(options.choices) ? external_exports.enum(
14189
- options.choices.map((choice) => choice.value)
14190
- ) : external_exports.string();
14335
+ const staticChoices = Array.isArray(options.choices) ? options.choices : null;
14336
+ const hasStaticDefault = typeof options.defaultValue !== "function";
14337
+ const staticDefault = options.defaultValue;
14338
+ if (staticChoices && hasStaticDefault) {
14339
+ assertChoiceDefaultInChoices(
14340
+ "formInput.choice",
14341
+ staticChoices,
14342
+ staticDefault
14343
+ );
14344
+ }
14345
+ const schema = staticChoices ? choiceSchema(staticChoices) : external_exports.string().nullable();
14346
+ const dynamicDefault = typeof options.defaultValue === "function" ? options.defaultValue : void 0;
14191
14347
  return {
14192
14348
  kind: "form",
14193
14349
  schema,
14194
- ..."defaultValue" in options ? { defaultValue: options.defaultValue } : {},
14195
- domain: (state, playerId, q, derived) => {
14196
- const context = { state, playerId, q, derived };
14350
+ ...hasStaticDefault ? { defaultValue: staticDefault } : {},
14351
+ ...dependsOn ? { dependsOn } : {},
14352
+ domain: (state, playerId, q, derived, values) => {
14353
+ const context = {
14354
+ state,
14355
+ playerId,
14356
+ q,
14357
+ derived
14358
+ };
14359
+ const choices = dependsOn ? resolveDependentDomainChoices(
14360
+ options.choices,
14361
+ {
14362
+ ...context,
14363
+ values: values ?? {}
14364
+ }
14365
+ ) : resolveDomainChoices(options.choices, {
14366
+ ...context,
14367
+ values: {}
14368
+ });
14369
+ if (hasStaticDefault) {
14370
+ assertChoiceDefaultInChoices(
14371
+ "formInput.choice",
14372
+ choices,
14373
+ staticDefault
14374
+ );
14375
+ }
14197
14376
  return {
14198
14377
  type: "choice",
14199
- choices: resolveDomainChoices(options.choices, context)
14378
+ choices
14200
14379
  };
14201
- }
14380
+ },
14381
+ ...dynamicDefault ? {
14382
+ resolveDefaultValue: (state, playerId, q, derived, domain) => {
14383
+ const choices = domain.type === "choice" ? domain.choices.map((choice) => ({
14384
+ value: choice.value,
14385
+ label: choice.label,
14386
+ icon: choice.icon,
14387
+ badge: choice.badge,
14388
+ description: choice.description,
14389
+ disabled: choice.disabled,
14390
+ disabledReason: choice.disabledReason
14391
+ })) : [];
14392
+ const resolved = dynamicDefault({
14393
+ state,
14394
+ playerId,
14395
+ q,
14396
+ derived,
14397
+ values: {},
14398
+ choices
14399
+ });
14400
+ if (resolved === void 0) return void 0;
14401
+ assertChoiceDefaultInChoices("formInput.choice", choices, resolved);
14402
+ return resolved;
14403
+ }
14404
+ } : {}
14202
14405
  };
14203
14406
  }
14204
14407
  function choiceListInput(options) {
@@ -14210,10 +14413,21 @@ function choiceListInput(options) {
14210
14413
  return {
14211
14414
  kind: "form",
14212
14415
  schema: external_exports.array(external_exports.string()),
14213
- ...staticDefaultValue ? { defaultValue: staticDefaultValue } : {},
14416
+ ...staticDefaultValue !== void 0 ? { defaultValue: staticDefaultValue } : {},
14214
14417
  domain: (state, playerId, q, derived) => {
14215
- const context = { state, playerId, q, derived };
14216
- const choices = resolveDomainChoices(options.choices, context);
14418
+ const context = {
14419
+ state,
14420
+ playerId,
14421
+ q,
14422
+ derived,
14423
+ values: {}
14424
+ };
14425
+ const choices = resolveDomainChoices(options.choices, context).map(
14426
+ (choice) => ({
14427
+ ...choice,
14428
+ value: choice.value
14429
+ })
14430
+ );
14217
14431
  return {
14218
14432
  type: "choiceList",
14219
14433
  choices,
@@ -14240,12 +14454,25 @@ function choiceListInput(options) {
14240
14454
  playerId,
14241
14455
  q,
14242
14456
  derived,
14457
+ values: {},
14243
14458
  choices
14244
14459
  });
14245
14460
  }
14246
14461
  } : {}
14247
14462
  };
14248
14463
  }
14464
+ function formInputForState() {
14465
+ return Object.assign(
14466
+ ((schema, options) => baseFormInput(schema, options)),
14467
+ {
14468
+ resourceMap: (options) => resourceMapInput(options),
14469
+ resourceChoices: (options) => formInput.resourceChoices(options),
14470
+ number: (options) => numberInput(options),
14471
+ choice: (options) => choiceInput(options),
14472
+ choiceList: (options) => choiceListInput(options)
14473
+ }
14474
+ );
14475
+ }
14249
14476
  var formInput = Object.assign(baseFormInput, {
14250
14477
  resourceMap: resourceMapInput,
14251
14478
  resourceChoices: (options) => ({
@@ -14254,32 +14481,49 @@ var formInput = Object.assign(baseFormInput, {
14254
14481
  }),
14255
14482
  number: numberInput,
14256
14483
  choice: choiceInput,
14257
- choiceList: choiceListInput
14484
+ choiceList: choiceListInput,
14485
+ forState: formInputForState
14258
14486
  });
14259
14487
 
14260
14488
  // ../../packages/app-sdk/src/reducer/inputs/boardInput.ts
14261
14489
  function makeBoardCollector(kind) {
14262
14490
  return function collector(options) {
14263
14491
  const target = options.target;
14492
+ const dependsOn = options.dependsOn?.map((dependency) => dependency.key);
14264
14493
  return {
14265
14494
  kind,
14266
14495
  schema: external_exports.string(),
14267
- eligibleTargets: ((state, playerId, q) => target.eligible({
14496
+ eligibleTargets: ((state, playerId, q, values) => target.eligible({
14268
14497
  state,
14269
14498
  playerId,
14270
- q
14499
+ q,
14500
+ values
14271
14501
  })),
14272
- validateTarget: ((state, playerId, q, targetId) => target.validate(
14502
+ validateTarget: ((state, playerId, q, targetId, values) => target.validate(
14273
14503
  {
14274
14504
  state,
14275
14505
  playerId,
14276
- q
14506
+ q,
14507
+ values
14277
14508
  },
14278
14509
  targetId
14279
14510
  )),
14511
+ ...dependsOn ? { dependsOn } : {},
14512
+ domain: dependsOn ? (state, playerId, q, _derived, values) => ({
14513
+ type: "target",
14514
+ targetKind: target.targetKind,
14515
+ boardId: target.boardId,
14516
+ eligibleTargets: target.eligible({
14517
+ state,
14518
+ playerId,
14519
+ q,
14520
+ values
14521
+ }).map(String)
14522
+ }) : void 0,
14280
14523
  meta: {
14281
14524
  targetKind: target.targetKind,
14282
- boardId: target.boardId
14525
+ boardId: target.boardId,
14526
+ valueKind: target.valueKind
14283
14527
  }
14284
14528
  };
14285
14529
  };
@@ -14296,12 +14540,13 @@ var DEFAULT_MISSING_CANDIDATE_ISSUE = {
14296
14540
  };
14297
14541
  function createTargetRule(candidates, predicates, options = {}) {
14298
14542
  const missingCandidateIssue = options.missingCandidateIssue ?? DEFAULT_MISSING_CANDIDATE_ISSUE;
14299
- const validate2 = (ctx, id) => {
14300
- if (!candidates(ctx).includes(id)) {
14543
+ const equals = options.equals ?? Object.is;
14544
+ const validate2 = (ctx, target) => {
14545
+ if (!candidates(ctx).some((candidate) => equals(candidate, target))) {
14301
14546
  return missingCandidateIssue;
14302
14547
  }
14303
14548
  for (const predicate of predicates) {
14304
- if (!predicate.test({ ...ctx, targetId: id })) {
14549
+ if (!predicate.test({ ...ctx, targetId: target, target })) {
14305
14550
  return {
14306
14551
  errorCode: predicate.errorCode,
14307
14552
  message: predicate.message
@@ -14313,7 +14558,7 @@ function createTargetRule(candidates, predicates, options = {}) {
14313
14558
  const rule = {
14314
14559
  eligible: (ctx) => candidates(ctx).filter((targetId) => validate2(ctx, targetId) == null),
14315
14560
  validate: validate2,
14316
- isEligible: (ctx, id) => validate2(ctx, id) == null,
14561
+ isEligible: (ctx, target) => validate2(ctx, target) == null,
14317
14562
  bind: (ctx) => ({
14318
14563
  eligible: () => rule.eligible(ctx),
14319
14564
  validate: (id) => rule.validate(ctx, id),
@@ -14337,7 +14582,7 @@ function candidateIdsForKind(q, boardId, targetKind) {
14337
14582
  if (targetKind === "vertex") {
14338
14583
  return idsFromCollection(q.board.tiled(boardId).vertices);
14339
14584
  }
14340
- return idsFromCollection(q.board.get(boardId).spaces);
14585
+ return idsFromCollection(q.board.get(boardId)?.spaces);
14341
14586
  }
14342
14587
  function idsFromCollection(collection) {
14343
14588
  if (!collection) return [];
@@ -14364,10 +14609,47 @@ function createBoardTargetBuilder(targetKind, boardId) {
14364
14609
  }
14365
14610
  ),
14366
14611
  boardId,
14367
- targetKind
14612
+ targetKind,
14613
+ valueKind: "board-id"
14614
+ })
14615
+ );
14616
+ }
14617
+ function createPlayerSpaceTargetBuilder(boardId) {
14618
+ return createTargetRuleBuilder(
14619
+ (predicates) => ({
14620
+ ...createTargetRule(
14621
+ ({ state, q }) => {
14622
+ const spacesForPlayer = (playerId) => candidateIdsForKind(
14623
+ q,
14624
+ `${boardId}:${playerId}`,
14625
+ "space"
14626
+ );
14627
+ return state.table.playerOrder.flatMap(
14628
+ (playerId) => spacesForPlayer(playerId).map((spaceId) => ({
14629
+ boardId,
14630
+ playerId,
14631
+ spaceId
14632
+ }))
14633
+ );
14634
+ },
14635
+ predicates,
14636
+ {
14637
+ missingCandidateIssue: {
14638
+ errorCode: "BOARD_TARGET_NOT_ELIGIBLE",
14639
+ message: "Board target is not eligible."
14640
+ },
14641
+ equals: (left, right) => isPlayerBoardSpaceTarget(left) && isPlayerBoardSpaceTarget(right) && left.boardId === right.boardId && left.playerId === right.playerId && left.spaceId === right.spaceId
14642
+ }
14643
+ ),
14644
+ boardId,
14645
+ targetKind: "space",
14646
+ valueKind: "player-board-space"
14368
14647
  })
14369
14648
  );
14370
14649
  }
14650
+ function isPlayerBoardSpaceTarget(value) {
14651
+ return typeof value === "object" && value !== null && "boardId" in value && "playerId" in value && "spaceId" in value;
14652
+ }
14371
14653
  function makeBoardTargetFactory(targetKind) {
14372
14654
  return function target(boardId) {
14373
14655
  return createBoardTargetBuilder(targetKind, boardId);
@@ -14377,7 +14659,10 @@ var boardTarget = {
14377
14659
  edge: makeBoardTargetFactory("edge"),
14378
14660
  vertex: makeBoardTargetFactory("vertex"),
14379
14661
  space: makeBoardTargetFactory("space"),
14380
- tile: makeBoardTargetFactory("tile")
14662
+ tile: makeBoardTargetFactory("tile"),
14663
+ playerSpace(boardId) {
14664
+ return createPlayerSpaceTargetBuilder(boardId);
14665
+ }
14381
14666
  };
14382
14667
 
14383
14668
  // ../../packages/app-sdk/src/reducer/inputs/cardTarget.ts
@@ -14424,22 +14709,38 @@ var cardTarget = {
14424
14709
  // ../../packages/app-sdk/src/reducer/inputs/cardInput.ts
14425
14710
  function cardInput(options) {
14426
14711
  const target = options.target;
14712
+ const dependsOn = options.dependsOn?.map((dependency) => dependency.key);
14427
14713
  return {
14428
14714
  kind: "card",
14429
14715
  schema: external_exports.string(),
14430
- eligibleTargets: ((state, playerId, q) => target.eligible({
14716
+ eligibleTargets: ((state, playerId, q, values) => target.eligible({
14431
14717
  state,
14432
14718
  playerId,
14433
- q
14719
+ q,
14720
+ values
14434
14721
  })),
14435
- validateTarget: ((state, playerId, q, targetId) => target.validate(
14722
+ validateTarget: ((state, playerId, q, targetId, values) => target.validate(
14436
14723
  {
14437
14724
  state,
14438
14725
  playerId,
14439
- q
14726
+ q,
14727
+ values
14440
14728
  },
14441
14729
  targetId
14442
14730
  )),
14731
+ ...dependsOn ? { dependsOn } : {},
14732
+ domain: dependsOn ? (state, playerId, q, _derived, values) => ({
14733
+ type: "target",
14734
+ targetKind: target.targetKind,
14735
+ zoneId: target.zoneId,
14736
+ zoneIds: target.zoneIds,
14737
+ eligibleTargets: target.eligible({
14738
+ state,
14739
+ playerId,
14740
+ q,
14741
+ values
14742
+ }).map(String)
14743
+ }) : void 0,
14443
14744
  meta: {
14444
14745
  zoneId: target.zoneId,
14445
14746
  zoneIds: target.zoneIds,
@@ -16131,6 +16432,35 @@ function shuffleWithRng(values, rng, operation = "shuffleSharedZone") {
16131
16432
  }
16132
16433
  return { orderedValues, nextRng, consumptions };
16133
16434
  }
16435
+ function subsetWithRng(values, count, rng) {
16436
+ if (!Number.isInteger(count) || count < 0) {
16437
+ throw new Error("random.subset count must be a non-negative integer.");
16438
+ }
16439
+ if (count > values.length) {
16440
+ throw new Error(
16441
+ `random.subset count ${count} exceeds source length ${values.length}.`
16442
+ );
16443
+ }
16444
+ const shuffled = shuffleWithRng(values, rng, "randomSubset");
16445
+ return {
16446
+ values: shuffled.orderedValues.slice(0, count),
16447
+ nextRng: shuffled.nextRng,
16448
+ consumptions: shuffled.consumptions
16449
+ };
16450
+ }
16451
+ function createMutableRandomHelpers(rng) {
16452
+ let current = rng;
16453
+ return {
16454
+ random: {
16455
+ subset(options) {
16456
+ const sampled = subsetWithRng(options.from, options.count, current);
16457
+ current = sampled.nextRng;
16458
+ return sampled.values;
16459
+ }
16460
+ },
16461
+ currentRng: () => current
16462
+ };
16463
+ }
16134
16464
  function sampleRngCollectorValue(collector, rng) {
16135
16465
  const meta = collector.meta;
16136
16466
  switch (meta.rng) {
@@ -16579,6 +16909,126 @@ function createReducerFx(_state) {
16579
16909
  function updateTable(state, nextTable) {
16580
16910
  return { ...state, table: nextTable };
16581
16911
  }
16912
+ function computePlayerZoneVisibility(table, zoneId, playerId) {
16913
+ const mode = table.handVisibility[zoneId];
16914
+ if (mode === "all" || mode === "public") {
16915
+ return { faceUp: true };
16916
+ }
16917
+ return { faceUp: false, visibleTo: [playerId] };
16918
+ }
16919
+ function readPlayerZoneCards(table, zoneId, playerId) {
16920
+ const zone = table.zones.perPlayer[zoneId] ?? table.hands[zoneId];
16921
+ if (!zone) {
16922
+ throw new Error(`Player zone '${zoneId}' does not exist.`);
16923
+ }
16924
+ return perPlayerGet(zone, asPlayerId(playerId)) ?? [];
16925
+ }
16926
+ function writePlayerZoneCards(table, zoneId, playerId, cards) {
16927
+ const currentZone = table.zones.perPlayer[zoneId];
16928
+ const currentHand = table.hands[zoneId];
16929
+ const player = asPlayerId(playerId);
16930
+ if (currentZone) {
16931
+ table.zones.perPlayer[zoneId] = perPlayerSet(
16932
+ currentZone,
16933
+ player,
16934
+ [...cards]
16935
+ );
16936
+ }
16937
+ if (currentHand) {
16938
+ table.hands[zoneId] = perPlayerSet(
16939
+ currentHand,
16940
+ player,
16941
+ [...cards]
16942
+ );
16943
+ }
16944
+ }
16945
+ function assertCardAllowedInPlayerZone(table, zoneId, cardId) {
16946
+ const allowedCardSetIds = table.zones.cardSetIdsByZoneId?.[zoneId];
16947
+ if (!allowedCardSetIds || allowedCardSetIds.length === 0) {
16948
+ return;
16949
+ }
16950
+ const card = table.cards[cardId];
16951
+ if (!card) {
16952
+ throw new Error(`Card '${cardId}' does not exist.`);
16953
+ }
16954
+ if (!allowedCardSetIds.includes(card.cardSetId)) {
16955
+ throw new Error(
16956
+ `Card '${cardId}' from set '${card.cardSetId}' is not allowed in player zone '${zoneId}'.`
16957
+ );
16958
+ }
16959
+ }
16960
+ function rotatePlayerZoneTable(options) {
16961
+ const nextTable = cloneRuntimeTable(options.table);
16962
+ const zoneId = options.zoneId;
16963
+ if (!nextTable.zones.perPlayer[zoneId] && !nextTable.hands[zoneId]) {
16964
+ throw new Error(`Player zone '${zoneId}' does not exist.`);
16965
+ }
16966
+ const players = [...options.players ?? nextTable.playerOrder];
16967
+ if (players.length === 0) {
16968
+ return nextTable;
16969
+ }
16970
+ const playerSet = new Set(nextTable.playerOrder);
16971
+ for (const playerId of players) {
16972
+ if (!playerSet.has(playerId)) {
16973
+ throw new Error(
16974
+ `Cannot rotate player zone '${zoneId}': player '${playerId}' is not in player order.`
16975
+ );
16976
+ }
16977
+ }
16978
+ const selectedByPlayer = /* @__PURE__ */ new Map();
16979
+ for (const playerId of players) {
16980
+ const sourceCards = readPlayerZoneCards(nextTable, zoneId, playerId);
16981
+ const selected = options.cardIdsByPlayer?.[playerId] ?? sourceCards;
16982
+ for (const cardId of selected) {
16983
+ if (!sourceCards.includes(cardId)) {
16984
+ throw new Error(
16985
+ `Cannot rotate player zone '${zoneId}': card '${cardId}' is not in zone for player '${playerId}'.`
16986
+ );
16987
+ }
16988
+ assertCardAllowedInPlayerZone(nextTable, zoneId, cardId);
16989
+ }
16990
+ selectedByPlayer.set(playerId, [...selected]);
16991
+ }
16992
+ const removeByPlayer = /* @__PURE__ */ new Map();
16993
+ for (const playerId of players) {
16994
+ const selected = new Set(selectedByPlayer.get(playerId) ?? []);
16995
+ removeByPlayer.set(
16996
+ playerId,
16997
+ readPlayerZoneCards(nextTable, zoneId, playerId).filter(
16998
+ (cardId) => !selected.has(cardId)
16999
+ )
17000
+ );
17001
+ }
17002
+ const additionsByPlayer = new Map(
17003
+ players.map((playerId) => [playerId, []])
17004
+ );
17005
+ for (const [index, fromPlayerId] of players.entries()) {
17006
+ const offset = options.direction === "left" ? 1 : -1;
17007
+ const recipient = players[(index + offset + players.length) % players.length];
17008
+ additionsByPlayer.get(recipient).push(...selectedByPlayer.get(fromPlayerId) ?? []);
17009
+ }
17010
+ for (const playerId of players) {
17011
+ const remaining = removeByPlayer.get(playerId) ?? [];
17012
+ const additions = additionsByPlayer.get(playerId) ?? [];
17013
+ const nextCards = options.position === "top" ? [...additions, ...remaining] : [...remaining, ...additions];
17014
+ writePlayerZoneCards(nextTable, zoneId, playerId, nextCards);
17015
+ for (const [position, cardId] of nextCards.entries()) {
17016
+ nextTable.componentLocations[cardId] = {
17017
+ type: "InHand",
17018
+ handId: zoneId,
17019
+ playerId,
17020
+ position
17021
+ };
17022
+ nextTable.ownerOfCard[cardId] = playerId;
17023
+ nextTable.visibility[cardId] = computePlayerZoneVisibility(
17024
+ nextTable,
17025
+ zoneId,
17026
+ playerId
17027
+ );
17028
+ }
17029
+ }
17030
+ return nextTable;
17031
+ }
16582
17032
  var moveComponentToEdgeInternal = moveComponentToEdge;
16583
17033
  var moveComponentToVertexInternal = moveComponentToVertex;
16584
17034
  var dealCardsFromDeckToHandInternal = dealCardsFromDeckToHand;
@@ -16762,6 +17212,19 @@ function createReducerOps() {
16762
17212
  return updateTable(state, nextTable);
16763
17213
  };
16764
17214
  },
17215
+ rotatePlayerZone(args) {
17216
+ return (state) => {
17217
+ const nextTable = rotatePlayerZoneTable({
17218
+ table: state.table,
17219
+ zoneId: args.zoneId,
17220
+ direction: args.direction,
17221
+ players: args.players,
17222
+ cardIdsByPlayer: args.cardIdsByPlayer,
17223
+ position: args.position ?? "bottom"
17224
+ });
17225
+ return updateTable(state, nextTable);
17226
+ };
17227
+ },
16765
17228
  moveComponentToSpace(args) {
16766
17229
  return (state) => {
16767
17230
  const nextTable = moveComponentToSpace(
@@ -16861,6 +17324,47 @@ function createReducerOps() {
16861
17324
  return impl;
16862
17325
  }
16863
17326
 
17327
+ // ../../packages/app-sdk/src/reducer/transaction.ts
17328
+ function createReducerTransaction(initialState, ops = createReducerOps()) {
17329
+ let currentState = initialState;
17330
+ let currentQueries = createStateQueries(currentState);
17331
+ const refresh = (nextState) => {
17332
+ currentState = nextState;
17333
+ currentQueries = createStateQueries(currentState);
17334
+ return currentState;
17335
+ };
17336
+ const apply = (op) => refresh(op(currentState));
17337
+ const base = {
17338
+ get state() {
17339
+ return currentState;
17340
+ },
17341
+ get q() {
17342
+ return currentQueries;
17343
+ },
17344
+ apply
17345
+ };
17346
+ return new Proxy(base, {
17347
+ get(target, property, receiver) {
17348
+ if (property in target) {
17349
+ return Reflect.get(target, property, receiver);
17350
+ }
17351
+ const opFactory = ops[property];
17352
+ if (typeof opFactory !== "function") {
17353
+ return void 0;
17354
+ }
17355
+ return (...args) => {
17356
+ const op = opFactory(
17357
+ ...args
17358
+ );
17359
+ return apply(op);
17360
+ };
17361
+ }
17362
+ });
17363
+ }
17364
+ function createReducerEdit(ops = createReducerOps()) {
17365
+ return (state) => createReducerTransaction(state, ops);
17366
+ }
17367
+
16864
17368
  // ../../packages/app-sdk/src/reducer/definition-index.ts
16865
17369
  function phaseEntriesOf(definition) {
16866
17370
  return Object.entries(definition.phases);
@@ -16909,6 +17413,7 @@ function collectReducerDefinitionIndex(definition) {
16909
17413
  cardActionId,
16910
17414
  {
16911
17415
  ...cardAction,
17416
+ __steps: cardAction.__steps,
16912
17417
  inputs: {
16913
17418
  cardId: cardInput({
16914
17419
  target: cardTarget.zones([cardAction.playFrom]).where({
@@ -16986,7 +17491,7 @@ function buildContext(state, manifest) {
16986
17491
  manifest,
16987
17492
  playerOrder: [...state.table.playerOrder],
16988
17493
  activePlayers: [...state.flow.activePlayers],
16989
- runtime: state.runtime,
17494
+ runtime: publicRuntime(state.runtime),
16990
17495
  setup: state.runtime.setup ? {
16991
17496
  profileId: state.runtime.setup.profileId,
16992
17497
  optionValues: {
@@ -16995,6 +17500,17 @@ function buildContext(state, manifest) {
16995
17500
  } : null
16996
17501
  };
16997
17502
  }
17503
+ function publicRuntime(runtime) {
17504
+ const { rng: _rng, ...rest } = runtime;
17505
+ return rest;
17506
+ }
17507
+ var DISABLED_RANDOM_HELPERS = {
17508
+ subset() {
17509
+ throw new Error(
17510
+ "random helpers are only available in reducer mutation callbacks."
17511
+ );
17512
+ }
17513
+ };
16998
17514
  function buildRuntimeArgs(state, manifest, helpers, toDomainState2, extra, options = {}) {
16999
17515
  const domainState = toDomainState2(state);
17000
17516
  const q = options.q ?? createStateQueries(domainState);
@@ -17004,7 +17520,8 @@ function buildRuntimeArgs(state, manifest, helpers, toDomainState2, extra, optio
17004
17520
  fx: options.fx ?? fxForState(state),
17005
17521
  q,
17006
17522
  derived: options.derived ?? createDerivedResolver(domainState, { q }),
17007
- runtime: state.runtime,
17523
+ runtime: publicRuntime(state.runtime),
17524
+ random: options.random ?? DISABLED_RANDOM_HELPERS,
17008
17525
  ...extra
17009
17526
  };
17010
17527
  }
@@ -17038,13 +17555,16 @@ var runtimeResultHelpers = {
17038
17555
  // ../../packages/app-sdk/src/reducer/bundle/trusted/trusted-state-codec.ts
17039
17556
  function toDomainState(state) {
17040
17557
  const { runtime: _runtime, ...domain } = state;
17558
+ attachPhaseAccessor(domain);
17041
17559
  return domain;
17042
17560
  }
17043
17561
  function toCombinedState(session) {
17044
- return {
17562
+ const combined = {
17045
17563
  ...session.domain,
17046
17564
  runtime: session.runtime
17047
17565
  };
17566
+ attachPhaseAccessor(combined);
17567
+ return combined;
17048
17568
  }
17049
17569
  function toSessionState(state, toDomain) {
17050
17570
  return {
@@ -17052,6 +17572,19 @@ function toSessionState(state, toDomain) {
17052
17572
  runtime: state.runtime
17053
17573
  };
17054
17574
  }
17575
+ function attachPhaseAccessor(state) {
17576
+ if (!state || typeof state !== "object") return;
17577
+ const candidate = state;
17578
+ const phase = candidate.phase;
17579
+ if (!phase || typeof phase !== "object") return;
17580
+ const descriptor = Object.getOwnPropertyDescriptor(phase, "get");
17581
+ if (descriptor && typeof descriptor.value === "function") return;
17582
+ Object.defineProperty(phase, "get", {
17583
+ enumerable: false,
17584
+ configurable: true,
17585
+ value: (phaseName) => candidate.flow?.currentPhase === phaseName ? phase : null
17586
+ });
17587
+ }
17055
17588
 
17056
17589
  // ../../packages/app-sdk/src/reducer/bundle/trusted/trusted-setup-profiles.ts
17057
17590
  function resolveTrustedSetupProfiles(definition) {
@@ -17133,7 +17666,8 @@ function createTrustedRuntimeScope(definition) {
17133
17666
  }
17134
17667
  const helpers = {
17135
17668
  ...runtimeResultHelpers,
17136
- ops: createReducerOps()
17669
+ ops: createReducerOps(),
17670
+ edit: createReducerEdit()
17137
17671
  };
17138
17672
  function fxForState2(state) {
17139
17673
  return fxForState(state);
@@ -17196,24 +17730,31 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
17196
17730
  }
17197
17731
  const parsed = interactions.parseInteractionParams(
17198
17732
  interaction,
17199
- input.params
17733
+ input.params,
17734
+ { playerId: input.playerId }
17200
17735
  );
17201
17736
  if (!parsed.ok) {
17202
17737
  return rejectResult("invalid-action-params", parsed.message);
17203
17738
  }
17204
- return normalizeResult(
17739
+ const random = createMutableRandomHelpers(state.runtime.rng);
17740
+ const result = normalizeResult(
17205
17741
  interaction.reduce(
17206
- scope.buildRuntimeArgs(state, {
17207
- ...ctx,
17208
- state: scope.toDomainState(state),
17209
- input: {
17210
- playerId: input.playerId,
17211
- params: parsed.params
17212
- }
17213
- })
17742
+ scope.buildRuntimeArgs(
17743
+ state,
17744
+ {
17745
+ ...ctx,
17746
+ state: scope.toDomainState(state),
17747
+ input: {
17748
+ playerId: input.playerId,
17749
+ params: parsed.params
17750
+ }
17751
+ },
17752
+ { random: random.random }
17753
+ )
17214
17754
  ),
17215
17755
  scope.toDomainState(state)
17216
17756
  );
17757
+ return result.type === "accept" ? { ...result, runtimeRng: random.currentRng() } : result;
17217
17758
  }
17218
17759
  if (input.kind === "continuation") {
17219
17760
  const continuation = scope.continuationById(input.continuationId);
@@ -17223,21 +17764,27 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
17223
17764
  `Continuation '${input.continuationId}' was not registered.`
17224
17765
  );
17225
17766
  }
17226
- return normalizeResult(
17767
+ const random = createMutableRandomHelpers(state.runtime.rng);
17768
+ const result = normalizeResult(
17227
17769
  continuation.reduce(
17228
- scope.buildRuntimeArgs(state, {
17229
- ...ctx,
17230
- state: scope.toDomainState(state),
17231
- input: {
17232
- source: "effect",
17233
- effectKind: input.effectKind,
17234
- data: input.resumeData,
17235
- response: input.response
17236
- }
17237
- })
17770
+ scope.buildRuntimeArgs(
17771
+ state,
17772
+ {
17773
+ ...ctx,
17774
+ state: scope.toDomainState(state),
17775
+ input: {
17776
+ source: "effect",
17777
+ effectKind: input.effectKind,
17778
+ data: input.resumeData,
17779
+ response: input.response
17780
+ }
17781
+ },
17782
+ { random: random.random }
17783
+ )
17238
17784
  ),
17239
17785
  scope.toDomainState(state)
17240
17786
  );
17787
+ return result.type === "accept" ? { ...result, runtimeRng: random.currentRng() } : result;
17241
17788
  }
17242
17789
  return scope.runtimeHelpers.accept(scope.toDomainState(state));
17243
17790
  }
@@ -17369,14 +17916,19 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
17369
17916
  }
17370
17917
  ])
17371
17918
  );
17919
+ const random = createMutableRandomHelpers(stateWithSubmission.runtime.rng);
17372
17920
  const resolved = normalizeResult(
17373
17921
  resolve(
17374
- scope.buildRuntimeArgs(stateWithSubmission, {
17375
- state: scope.toDomainState(stateWithSubmission),
17376
- submissions: resolvedSubmissions,
17377
- submittedPlayerIds: [...actors],
17378
- waitingPlayerIds: []
17379
- })
17922
+ scope.buildRuntimeArgs(
17923
+ stateWithSubmission,
17924
+ {
17925
+ state: scope.toDomainState(stateWithSubmission),
17926
+ submissions: resolvedSubmissions,
17927
+ submittedPlayerIds: [...actors],
17928
+ waitingPlayerIds: []
17929
+ },
17930
+ { random: random.random }
17931
+ )
17380
17932
  ),
17381
17933
  scope.toDomainState(stateWithSubmission)
17382
17934
  );
@@ -17387,7 +17939,7 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
17387
17939
  type: "accept",
17388
17940
  state: clearSimultaneousCurrent({
17389
17941
  ...resolved.state,
17390
- runtime: stateWithSubmission.runtime
17942
+ runtime: { ...stateWithSubmission.runtime, rng: random.currentRng() }
17391
17943
  }),
17392
17944
  instructions: resolved.instructions ?? []
17393
17945
  };
@@ -17409,7 +17961,10 @@ function createTrustedInstructionRunner(scope, interactions, lifecycle) {
17409
17961
  type: "accept",
17410
17962
  state: {
17411
17963
  ...result.state,
17412
- runtime: sampled.state.runtime
17964
+ runtime: {
17965
+ ...sampled.state.runtime,
17966
+ rng: result.runtimeRng ?? sampled.state.runtime.rng
17967
+ }
17413
17968
  },
17414
17969
  instructions: result.instructions ?? []
17415
17970
  };
@@ -17504,6 +18059,48 @@ function withSelection(domain, collector) {
17504
18059
  if (!collector.selection) return domain;
17505
18060
  return { ...domain, selection: collector.selection };
17506
18061
  }
18062
+ function finiteValuesForDependency(key, domain) {
18063
+ if (!domain) {
18064
+ throw new Error(
18065
+ `Interaction input '${key}' is declared as a dependency before it has a projected domain.`
18066
+ );
18067
+ }
18068
+ if (domain.type === "target") {
18069
+ if (!domain.eligibleTargets) {
18070
+ throw new Error(
18071
+ `Interaction input '${key}' cannot be used as a dependency because its target domain is not finite.`
18072
+ );
18073
+ }
18074
+ return [...domain.eligibleTargets];
18075
+ }
18076
+ if (domain.type === "choice") {
18077
+ return domain.choices.flatMap(
18078
+ (choice) => choice.value === null ? [] : [choice.value]
18079
+ );
18080
+ }
18081
+ throw new Error(
18082
+ `Interaction input '${key}' cannot be used as a dependency. V1 supports finite target and choice dependencies only.`
18083
+ );
18084
+ }
18085
+ function cartesianDependencyTuples(dependencies, domainsByKey) {
18086
+ return dependencies.reduce(
18087
+ (tuples, key) => {
18088
+ const values = finiteValuesForDependency(key, domainsByKey[key]);
18089
+ return tuples.flatMap(
18090
+ (tuple) => values.map((value) => ({ ...tuple, [key]: value }))
18091
+ );
18092
+ },
18093
+ [{}]
18094
+ );
18095
+ }
18096
+ function withoutDependentProjection(domain) {
18097
+ const {
18098
+ dependsOn: _dependsOn,
18099
+ dependentCases: _dependentCases,
18100
+ ...rest
18101
+ } = domain;
18102
+ return rest;
18103
+ }
17507
18104
  function unsupportedDefaultInputError(key, collector) {
17508
18105
  return new Error(
17509
18106
  `Interaction input '${key}' uses a '${collector.kind}' collector without a default-renderable domain or target metadata. Use formInput.choice(...), formInput.choiceList(...), formInput.number(...), formInput.resourceMap(...), cardInput(...), or boardInput(...). For custom payloads, use a custom interaction surface with paramsSchema instead of the default InteractionForm.`
@@ -17552,19 +18149,74 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
17552
18149
  const derived = () => derivedLazy ??= createDerivedResolver(domainState, { q: queries() });
17553
18150
  const result = {};
17554
18151
  for (const [key, collector] of Object.entries(collectors)) {
17555
- if (collector.kind === "rng" || collector.kind === "prompt") {
18152
+ if (collector.kind === "rng") {
18153
+ continue;
18154
+ }
18155
+ if (collector.kind === "prompt") {
18156
+ const optionFactory = collector.meta?.options;
18157
+ if (!optionFactory) continue;
18158
+ result[key] = {
18159
+ type: "choice",
18160
+ choices: optionFactory(domainState, playerId, queries()).map(
18161
+ (option) => ({
18162
+ value: String(option.id),
18163
+ label: option.label ?? String(option.id),
18164
+ ...(() => {
18165
+ const issue = collector.validateTarget?.(
18166
+ domainState,
18167
+ playerId,
18168
+ queries(),
18169
+ String(option.id)
18170
+ );
18171
+ return issue ? {
18172
+ disabled: true,
18173
+ disabledReason: issue.message ?? issue.errorCode
18174
+ } : {};
18175
+ })()
18176
+ })
18177
+ )
18178
+ };
17556
18179
  continue;
17557
18180
  }
17558
18181
  if (collector.domain) {
17559
- result[key] = withSelection(
18182
+ const dependencies = collector.dependsOn ?? [];
18183
+ const baseDomain = withSelection(
17560
18184
  collector.domain(
17561
18185
  domainState,
17562
18186
  playerId,
17563
18187
  queries(),
17564
- derived()
18188
+ derived(),
18189
+ {}
17565
18190
  ),
17566
18191
  collector
17567
18192
  );
18193
+ if (dependencies.length === 0) {
18194
+ result[key] = baseDomain;
18195
+ continue;
18196
+ }
18197
+ const dependentCases = cartesianDependencyTuples(
18198
+ dependencies,
18199
+ result
18200
+ ).map((values) => ({
18201
+ when: values,
18202
+ domain: withoutDependentProjection(
18203
+ withSelection(
18204
+ collector.domain?.(
18205
+ domainState,
18206
+ playerId,
18207
+ queries(),
18208
+ derived(),
18209
+ values
18210
+ ),
18211
+ collector
18212
+ )
18213
+ )
18214
+ }));
18215
+ result[key] = {
18216
+ ...baseDomain,
18217
+ dependsOn: dependencies,
18218
+ dependentCases
18219
+ };
17568
18220
  continue;
17569
18221
  }
17570
18222
  const targets = eligibleTargets[key];
@@ -17586,7 +18238,17 @@ function collectInputDomains(interaction, domainState, playerId, eligibleTargets
17586
18238
  }
17587
18239
  function collectInteractionInputs(interaction, domainState, playerId, options = {}) {
17588
18240
  const collectors = interactionInputsOf(interaction);
17589
- const eligibleTargets = options.includeEligibleTargets === false ? {} : collectEligibleTargets(interaction, domainState, playerId, options);
18241
+ const dependencyKeys = new Set(
18242
+ Object.values(collectors).flatMap((collector) => [
18243
+ ...collector.dependsOn ?? []
18244
+ ])
18245
+ );
18246
+ const collectedEligibleTargets = options.includeEligibleTargets === false && dependencyKeys.size === 0 ? {} : collectEligibleTargets(interaction, domainState, playerId, options);
18247
+ const eligibleTargets = options.includeEligibleTargets === false ? Object.fromEntries(
18248
+ Object.entries(collectedEligibleTargets).filter(
18249
+ ([key]) => dependencyKeys.has(key)
18250
+ )
18251
+ ) : collectedEligibleTargets;
17590
18252
  const domainsByKey = collectInputDomains(
17591
18253
  interaction,
17592
18254
  domainState,
@@ -17601,10 +18263,13 @@ function collectInteractionInputs(interaction, domainState, playerId, options =
17601
18263
  let derivedLazy = options.derived ?? null;
17602
18264
  const derived = () => derivedLazy ??= createDerivedResolver(domainState, { q: queries() });
17603
18265
  return Object.entries(collectors).flatMap(([key, collector]) => {
17604
- if (collector.kind === "rng" || collector.kind === "prompt") {
18266
+ if (collector.kind === "rng") {
17605
18267
  return [];
17606
18268
  }
17607
18269
  const domain = domainsByKey[key];
18270
+ if (!domain && collector.kind === "prompt") {
18271
+ return [];
18272
+ }
17608
18273
  if (!domain) {
17609
18274
  throw unsupportedDefaultInputError(key, collector);
17610
18275
  }
@@ -17651,7 +18316,7 @@ function collectCardZoneIds(interaction) {
17651
18316
  function collectPromptOptions(interaction, domainState, playerId, queries) {
17652
18317
  for (const collector of Object.values(interaction.inputs)) {
17653
18318
  if (collector.kind !== "prompt") continue;
17654
- const factory = collector.meta?.eligibleOptions;
18319
+ const factory = collector.meta?.options;
17655
18320
  if (!factory) continue;
17656
18321
  return factory(domainState, playerId, queries).map((option) => ({
17657
18322
  id: String(option.id),
@@ -17677,7 +18342,12 @@ function parseInteractionParams(interaction, rawParams, options = {}) {
17677
18342
  const issues = [];
17678
18343
  for (const [key, collector] of Object.entries(collectors)) {
17679
18344
  if (collector.kind === "rng" && options.skipRng) continue;
17680
- const rawValue = record[key] === void 0 && "defaultValue" in collector ? collector.defaultValue : record[key];
18345
+ const rawValueBase = record[key] === void 0 && "defaultValue" in collector ? collector.defaultValue : record[key];
18346
+ const rawValue = collector.kind === "board-space" && collector.meta?.valueKind === "player-board-space" && typeof rawValueBase === "string" && options.playerId ? {
18347
+ boardId: collector.meta.boardId,
18348
+ playerId: options.playerId,
18349
+ spaceId: rawValueBase
18350
+ } : rawValueBase;
17681
18351
  const result = collector.schema.safeParse(rawValue);
17682
18352
  if (!result.success) {
17683
18353
  for (const issue of result.error.issues) {
@@ -17739,7 +18409,7 @@ function validateCollectorTargets(interaction, domainState, playerId, params) {
17739
18409
  domainState,
17740
18410
  playerId,
17741
18411
  queries(),
17742
- String(value)
18412
+ value
17743
18413
  );
17744
18414
  if (issue) {
17745
18415
  return makeValidationError(issue.errorCode, issue.message);
@@ -17951,11 +18621,14 @@ function deriveCommitPolicy(inputs, explicit) {
17951
18621
  if (hasManyCollector) {
17952
18622
  return { mode: "manual" };
17953
18623
  }
18624
+ if (collectors.some(isDefaultRenderableFormCollector)) {
18625
+ return { mode: "manual" };
18626
+ }
17954
18627
  if (collectors.length === 0 || !collectors.some(isTargetCollector2)) {
17955
18628
  return { mode: "manual" };
17956
18629
  }
17957
18630
  return collectors.every(
17958
- (collector) => isTargetCollector2(collector) || isDefaultRenderableFormCollector(collector) || collector.kind === "rng"
18631
+ (collector) => isTargetCollector2(collector) || collector.kind === "rng"
17959
18632
  ) ? { mode: "autoWhenReady" } : { mode: "manual" };
17960
18633
  }
17961
18634
  function projectInteractionMetadata(interaction) {
@@ -17971,7 +18644,7 @@ function enrichResourceInputPresentation(inputs, manifest) {
17971
18644
  }
17972
18645
  const resources = presentationById;
17973
18646
  const enrichChoice = (choice) => {
17974
- const presentation = resources[choice.value];
18647
+ const presentation = choice.value === null ? void 0 : resources[choice.value];
17975
18648
  return {
17976
18649
  ...choice,
17977
18650
  label: typeof presentation?.label === "string" && (!choice.label || choice.label === choice.value) ? presentation.label : choice.label || (typeof presentation?.label === "string" ? presentation.label : ""),
@@ -17979,7 +18652,16 @@ function enrichResourceInputPresentation(inputs, manifest) {
17979
18652
  };
17980
18653
  };
17981
18654
  return inputs.map((input) => {
17982
- if (input.domain.type === "choice" || input.domain.type === "choiceList") {
18655
+ if (input.domain.type === "choice") {
18656
+ return {
18657
+ ...input,
18658
+ domain: {
18659
+ ...input.domain,
18660
+ choices: input.domain.choices.map(enrichChoice)
18661
+ }
18662
+ };
18663
+ }
18664
+ if (input.domain.type === "choiceList") {
17983
18665
  return {
17984
18666
  ...input,
17985
18667
  domain: {
@@ -18099,7 +18781,10 @@ function createInteractionDecisionResolver(scope, stages, authorization) {
18099
18781
  }
18100
18782
  const trustedInteractionId = interactionId;
18101
18783
  const parseForSubmit = mode === "submit";
18102
- const parsed = parseForSubmit ? parseInteractionParams(interaction, params, { skipRng: true }) : {
18784
+ const parsed = parseForSubmit ? parseInteractionParams(interaction, params, {
18785
+ skipRng: true,
18786
+ playerId
18787
+ }) : {
18103
18788
  ok: true,
18104
18789
  params: prepareInteractionProjectionParams(interaction, params)
18105
18790
  };
@@ -18359,10 +19044,7 @@ function createStageResolver(scope) {
18359
19044
  return allowlist;
18360
19045
  }
18361
19046
  function isInteractionAllowedInStep(state, interaction, projection) {
18362
- const allowedSteps = [
18363
- ...interaction.step !== void 0 ? [interaction.step] : [],
18364
- ...interaction.steps ?? []
18365
- ];
19047
+ const allowedSteps = interaction.__steps ?? [];
18366
19048
  if (allowedSteps.length === 0) {
18367
19049
  return true;
18368
19050
  }
@@ -18491,16 +19173,15 @@ function resolveContainerRef(table, ref) {
18491
19173
  throw new Error(`Unknown board '${ref.boardId}'.`);
18492
19174
  }
18493
19175
  const space2 = board2?.spaces[ref.spaceId];
18494
- if (!space2?.zoneId) {
19176
+ if (!space2) {
18495
19177
  throw new Error(
18496
- `Board space '${ref.spaceId}' on board '${ref.boardId}' has no container zone.`
19178
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}'.`
18497
19179
  );
18498
19180
  }
18499
19181
  return {
18500
19182
  kind: "space",
18501
19183
  boardId: board2.id,
18502
- spaceId: ref.spaceId,
18503
- zoneId: space2.zoneId
19184
+ spaceId: ref.spaceId
18504
19185
  };
18505
19186
  }
18506
19187
  const board = table.boards.byId[resolveRuntimeBoardId(table, ref.boardId, ref.playerId)];
@@ -18510,16 +19191,15 @@ function resolveContainerRef(table, ref) {
18510
19191
  );
18511
19192
  }
18512
19193
  const space = board?.spaces[ref.spaceId];
18513
- if (!space?.zoneId) {
19194
+ if (!space) {
18514
19195
  throw new Error(
18515
- `Board space '${ref.spaceId}' on board '${ref.boardId}' for '${ref.playerId}' has no container zone.`
19196
+ `Unknown board space '${ref.spaceId}' on board '${ref.boardId}' for '${ref.playerId}'.`
18516
19197
  );
18517
19198
  }
18518
19199
  return {
18519
19200
  kind: "space",
18520
19201
  boardId: board.id,
18521
- spaceId: ref.spaceId,
18522
- zoneId: space.zoneId
19202
+ spaceId: ref.spaceId
18523
19203
  };
18524
19204
  }
18525
19205
  function readContainerComponents(table, ref) {
@@ -18830,7 +19510,11 @@ function createLifecycleRunner(scope, interactions) {
18830
19510
  }) : safeParseOrThrow(phase.state, {}, `phase:${phaseName}:initialState`);
18831
19511
  return {
18832
19512
  ...state,
18833
- phase: safeParseOrThrow(phase.state, phaseState, `phase:${phaseName}`),
19513
+ phase: safeParseOrThrow(
19514
+ phase.state,
19515
+ phaseState,
19516
+ `phase:${phaseName}`
19517
+ ),
18834
19518
  flow: {
18835
19519
  ...state.flow,
18836
19520
  currentPhase: phaseName
@@ -18848,12 +19532,17 @@ function createLifecycleRunner(scope, interactions) {
18848
19532
  let nextState = workingState;
18849
19533
  const instructions = [];
18850
19534
  if (phase.enter) {
19535
+ const random = createMutableRandomHelpers(workingState.runtime.rng);
18851
19536
  const entered = normalizeResult(
18852
19537
  phase.enter(
18853
- scope.buildRuntimeArgs(workingState, {
18854
- event,
18855
- state: scope.toDomainState(workingState)
18856
- })
19538
+ scope.buildRuntimeArgs(
19539
+ workingState,
19540
+ {
19541
+ event,
19542
+ state: scope.toDomainState(workingState)
19543
+ },
19544
+ { random: random.random }
19545
+ )
18857
19546
  ),
18858
19547
  scope.toDomainState(workingState)
18859
19548
  );
@@ -18864,18 +19553,23 @@ function createLifecycleRunner(scope, interactions) {
18864
19553
  }
18865
19554
  nextState = {
18866
19555
  ...entered.state,
18867
- runtime: workingState.runtime
19556
+ runtime: { ...workingState.runtime, rng: random.currentRng() }
18868
19557
  };
18869
19558
  if (entered.instructions) instructions.push(...entered.instructions);
18870
19559
  }
18871
19560
  const activeStage = interactions.resolveActiveStage(nextState, phaseName);
18872
19561
  if (activeStage?.stage.onEnter) {
19562
+ const random = createMutableRandomHelpers(nextState.runtime.rng);
18873
19563
  const stageEntered = normalizeResult(
18874
19564
  activeStage.stage.onEnter(
18875
- scope.buildRuntimeArgs(nextState, {
18876
- event,
18877
- state: scope.toDomainState(nextState)
18878
- })
19565
+ scope.buildRuntimeArgs(
19566
+ nextState,
19567
+ {
19568
+ event,
19569
+ state: scope.toDomainState(nextState)
19570
+ },
19571
+ { random: random.random }
19572
+ )
18879
19573
  ),
18880
19574
  scope.toDomainState(nextState)
18881
19575
  );
@@ -18886,7 +19580,7 @@ function createLifecycleRunner(scope, interactions) {
18886
19580
  }
18887
19581
  nextState = {
18888
19582
  ...stageEntered.state,
18889
- runtime: nextState.runtime
19583
+ runtime: { ...nextState.runtime, rng: random.currentRng() }
18890
19584
  };
18891
19585
  if (stageEntered.instructions) {
18892
19586
  instructions.push(...stageEntered.instructions);
@@ -19939,6 +20633,11 @@ function toWireDispatchResult(result, serializeState) {
19939
20633
  function createReducerBundle(definition) {
19940
20634
  const trustedBundle = createTrustedReducerBundle(definition);
19941
20635
  const codec = createIngressRuntimeCodec(definition);
20636
+ function parseTrustedState(state) {
20637
+ return codec.parseState(
20638
+ state
20639
+ );
20640
+ }
19942
20641
  function parseRuntimePlayerId(playerId) {
19943
20642
  if (typeof playerId !== "string") {
19944
20643
  throw new Error("Expected a string playerId.");
@@ -19971,7 +20670,7 @@ function createReducerBundle(definition) {
19971
20670
  );
19972
20671
  },
19973
20672
  async initializePhase({ state, to }) {
19974
- const decodedState = codec.parseState(state);
20673
+ const decodedState = parseTrustedState(state);
19975
20674
  return codec.serializeState(
19976
20675
  await trustedBundle.initializePhase({
19977
20676
  state: decodedState,
@@ -19983,7 +20682,7 @@ function createReducerBundle(definition) {
19983
20682
  const validatedWire = codec.parseInput(input);
19984
20683
  const routed = routeInteraction(validatedWire);
19985
20684
  return trustedBundle.validateInput({
19986
- state: codec.parseState(state),
20685
+ state: parseTrustedState(state),
19987
20686
  input: routed
19988
20687
  });
19989
20688
  },
@@ -19994,7 +20693,7 @@ function createReducerBundle(definition) {
19994
20693
  const validatedWire = codec.parseInput(input);
19995
20694
  const routed = routeInteraction(validatedWire);
19996
20695
  const result = await trustedBundle.reduce({
19997
- state: codec.parseState(state),
20696
+ state: parseTrustedState(state),
19998
20697
  input: routed
19999
20698
  });
20000
20699
  return toWireReduceResult(
@@ -20009,7 +20708,7 @@ function createReducerBundle(definition) {
20009
20708
  const validatedWire = codec.parseInput(input);
20010
20709
  const routed = routeInteraction(validatedWire);
20011
20710
  const result = await trustedBundle.dispatch({
20012
- state: codec.parseState(state),
20711
+ state: parseTrustedState(state),
20013
20712
  input: routed
20014
20713
  });
20015
20714
  return toWireDispatchResult(
@@ -20036,7 +20735,7 @@ function createReducerBundle(definition) {
20036
20735
  playerIds,
20037
20736
  viewId = "player"
20038
20737
  }) {
20039
- const parsedState = codec.parseState(state);
20738
+ const parsedState = parseTrustedState(state);
20040
20739
  const parsedPlayerIds = playerIds.map((pid) => codec.parsePlayerId(pid));
20041
20740
  return trustedBundle.projectSeatsDynamic({
20042
20741
  state: parsedState,
@@ -20045,9 +20744,7 @@ function createReducerBundle(definition) {
20045
20744
  });
20046
20745
  },
20047
20746
  projectSeatViewDynamic({ state, playerId, viewId = "player" }) {
20048
- const parsedState = codec.parseState(
20049
- state
20050
- );
20747
+ const parsedState = parseTrustedState(state);
20051
20748
  return trustedBundle.projectSeatViewDynamic({
20052
20749
  state: parsedState,
20053
20750
  playerId: parseRuntimePlayerId(playerId),
@@ -20076,7 +20773,7 @@ function createReducerBundle(definition) {
20076
20773
  });
20077
20774
  },
20078
20775
  hydrate({ state: snapshot }) {
20079
- state = codec.parseState(snapshot);
20776
+ state = parseTrustedState(snapshot);
20080
20777
  },
20081
20778
  async dispatch({ input }) {
20082
20779
  const validatedWire = codec.parseInput(
@@ -21316,6 +22013,11 @@ var ShadowReducerRuntime = class {
21316
22013
  this.version = version;
21317
22014
  this.started = true;
21318
22015
  }
22016
+ patchState(mutator) {
22017
+ const next = structuredClone(this.rawState());
22018
+ mutator(next);
22019
+ this.hydrate(next, this.version + 1);
22020
+ }
21319
22021
  projectAllSeats() {
21320
22022
  const projection = this.runtime.projectSeatsDynamic({
21321
22023
  playerIds: this.playerIds
@@ -21616,6 +22318,15 @@ async function createScenarioContext(options) {
21616
22318
  await assertLiveMatchesShadow("browser", options.shadow, live);
21617
22319
  }
21618
22320
  },
22321
+ patchState: async (mutator) => {
22322
+ await api.start();
22323
+ if (options.live || options.embedded || options.browser || options.actionPlan) {
22324
+ throw new Error(
22325
+ "game.patchState is only supported for reducer snapshot scenarios. Use it to materialize a state before --from-scenario or reducer tests, not inside live replay/browser runners."
22326
+ );
22327
+ }
22328
+ options.shadow.patchState(mutator);
22329
+ },
21619
22330
  submit: async (playerId, interactionId, params) => {
21620
22331
  await api.start();
21621
22332
  const interactionParams = params ?? {};
@@ -26119,4 +26830,4 @@ export {
26119
26830
  test_default,
26120
26831
  runDreamboardCli
26121
26832
  };
26122
- //# sourceMappingURL=chunk-6FPQ3S2J.js.map
26833
+ //# sourceMappingURL=chunk-ZLYS5DGK.js.map