@ankhorage/zora-game 0.1.0 → 0.3.0

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.
Files changed (98) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +3 -1
  3. package/dist/ZORA_GAME_COMPONENT_META.d.ts +228 -0
  4. package/dist/ZORA_GAME_COMPONENT_META.d.ts.map +1 -1
  5. package/dist/ZORA_GAME_COMPONENT_META.js +4 -0
  6. package/dist/ZORA_GAME_COMPONENT_META.js.map +1 -1
  7. package/dist/ZORA_GAME_PLUGIN.d.ts +241 -3
  8. package/dist/ZORA_GAME_PLUGIN.d.ts.map +1 -1
  9. package/dist/ZORA_GAME_PLUGIN.js +4 -0
  10. package/dist/ZORA_GAME_PLUGIN.js.map +1 -1
  11. package/dist/ZORA_PLUGIN_METADATA.d.ts +237 -3
  12. package/dist/ZORA_PLUGIN_METADATA.d.ts.map +1 -1
  13. package/dist/ZORA_PLUGIN_METADATA.js +11 -3
  14. package/dist/ZORA_PLUGIN_METADATA.js.map +1 -1
  15. package/dist/features/game-presentation/adapters/inbound/Game.d.ts +5 -0
  16. package/dist/features/game-presentation/adapters/inbound/Game.d.ts.map +1 -0
  17. package/dist/features/game-presentation/adapters/inbound/Game.js +16 -0
  18. package/dist/features/game-presentation/adapters/inbound/Game.js.map +1 -0
  19. package/dist/features/game-presentation/adapters/inbound/GameEntity.d.ts.map +1 -1
  20. package/dist/features/game-presentation/adapters/inbound/GameEntity.js +4 -11
  21. package/dist/features/game-presentation/adapters/inbound/GameEntity.js.map +1 -1
  22. package/dist/features/game-presentation/adapters/inbound/GameInputZone.d.ts +4 -0
  23. package/dist/features/game-presentation/adapters/inbound/GameInputZone.d.ts.map +1 -0
  24. package/dist/features/game-presentation/adapters/inbound/GameInputZone.js +35 -0
  25. package/dist/features/game-presentation/adapters/inbound/GameInputZone.js.map +1 -0
  26. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.d.ts +17 -0
  27. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.d.ts.map +1 -0
  28. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.js +20 -0
  29. package/dist/features/game-presentation/adapters/inbound/createGamePointerEvent.js.map +1 -0
  30. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.d.ts +9 -0
  31. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.d.ts.map +1 -0
  32. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.js +37 -0
  33. package/dist/features/game-presentation/adapters/inbound/useGameInputZoneResponder.js.map +1 -0
  34. package/dist/features/game-presentation/composition/GameRuntimeContext.d.ts +5 -0
  35. package/dist/features/game-presentation/composition/GameRuntimeContext.d.ts.map +1 -0
  36. package/dist/features/game-presentation/composition/GameRuntimeContext.js +4 -0
  37. package/dist/features/game-presentation/composition/GameRuntimeContext.js.map +1 -0
  38. package/dist/features/game-presentation/composition/useGameRuntime.d.ts +5 -0
  39. package/dist/features/game-presentation/composition/useGameRuntime.d.ts.map +1 -0
  40. package/dist/features/game-presentation/composition/useGameRuntime.js +103 -0
  41. package/dist/features/game-presentation/composition/useGameRuntime.js.map +1 -0
  42. package/dist/features/game-presentation/composition/useGameScheduledEffects.d.ts +17 -0
  43. package/dist/features/game-presentation/composition/useGameScheduledEffects.d.ts.map +1 -0
  44. package/dist/features/game-presentation/composition/useGameScheduledEffects.js +40 -0
  45. package/dist/features/game-presentation/composition/useGameScheduledEffects.js.map +1 -0
  46. package/dist/features/game-presentation/meta/gameInputZoneMeta.d.ts +112 -0
  47. package/dist/features/game-presentation/meta/gameInputZoneMeta.d.ts.map +1 -0
  48. package/dist/features/game-presentation/meta/gameInputZoneMeta.js +92 -0
  49. package/dist/features/game-presentation/meta/gameInputZoneMeta.js.map +1 -0
  50. package/dist/features/game-presentation/meta/gameMeta.d.ts +120 -0
  51. package/dist/features/game-presentation/meta/gameMeta.d.ts.map +1 -0
  52. package/dist/features/game-presentation/meta/gameMeta.js +97 -0
  53. package/dist/features/game-presentation/meta/gameMeta.js.map +1 -0
  54. package/dist/features/game-presentation/public.d.ts +3 -1
  55. package/dist/features/game-presentation/public.d.ts.map +1 -1
  56. package/dist/features/game-presentation/public.js +2 -0
  57. package/dist/features/game-presentation/public.js.map +1 -1
  58. package/dist/features/game-presentation/utils/clampGamePercentage.d.ts +3 -0
  59. package/dist/features/game-presentation/utils/clampGamePercentage.d.ts.map +1 -0
  60. package/dist/features/game-presentation/utils/clampGamePercentage.js +5 -0
  61. package/dist/features/game-presentation/utils/clampGamePercentage.js.map +1 -0
  62. package/dist/features/game-presentation/utils/toGamePercentage.d.ts +3 -0
  63. package/dist/features/game-presentation/utils/toGamePercentage.d.ts.map +1 -0
  64. package/dist/features/game-presentation/utils/toGamePercentage.js +6 -0
  65. package/dist/features/game-presentation/utils/toGamePercentage.js.map +1 -0
  66. package/dist/index.d.ts +2 -2
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/types/gamePresentation.d.ts +22 -0
  71. package/dist/types/gamePresentation.d.ts.map +1 -1
  72. package/dist/types/gamePresentation.js.map +1 -1
  73. package/dist/types/gameRuntime.d.ts +12 -0
  74. package/dist/types/gameRuntime.d.ts.map +1 -0
  75. package/dist/types/gameRuntime.js +2 -0
  76. package/dist/types/gameRuntime.js.map +1 -0
  77. package/package.json +5 -2
  78. package/src/ZORA_GAME_COMPONENT_META.ts +4 -0
  79. package/src/ZORA_GAME_PLUGIN.ts +4 -0
  80. package/src/ZORA_PLUGIN_METADATA.test.ts +39 -5
  81. package/src/ZORA_PLUGIN_METADATA.ts +11 -3
  82. package/src/features/game-presentation/adapters/inbound/Game.tsx +27 -0
  83. package/src/features/game-presentation/adapters/inbound/GameEntity.tsx +4 -13
  84. package/src/features/game-presentation/adapters/inbound/GameInputZone.tsx +70 -0
  85. package/src/features/game-presentation/adapters/inbound/createGamePointerEvent.test.ts +56 -0
  86. package/src/features/game-presentation/adapters/inbound/createGamePointerEvent.ts +39 -0
  87. package/src/features/game-presentation/adapters/inbound/useGameInputZoneResponder.ts +49 -0
  88. package/src/features/game-presentation/composition/GameRuntimeContext.ts +6 -0
  89. package/src/features/game-presentation/composition/useGameRuntime.ts +181 -0
  90. package/src/features/game-presentation/composition/useGameScheduledEffects.ts +85 -0
  91. package/src/features/game-presentation/meta/gameInputZoneMeta.ts +94 -0
  92. package/src/features/game-presentation/meta/gameMeta.ts +99 -0
  93. package/src/features/game-presentation/public.ts +4 -0
  94. package/src/features/game-presentation/utils/clampGamePercentage.ts +4 -0
  95. package/src/features/game-presentation/utils/toGamePercentage.ts +6 -0
  96. package/src/index.ts +9 -1
  97. package/src/types/gamePresentation.ts +24 -0
  98. package/src/types/gameRuntime.ts +13 -0
@@ -0,0 +1,181 @@
1
+ import {
2
+ advanceGameTime,
3
+ applyGameEvent,
4
+ createGameSession,
5
+ type GameInput,
6
+ type GameOutput,
7
+ } from '@ankhorage/game';
8
+ import React from 'react';
9
+
10
+ import type { GameProps } from '../../../types/gamePresentation';
11
+ import type { GameRuntimeContextValue, GameRuntimeState } from '../../../types/gameRuntime';
12
+ import { useGameScheduledEffects } from './useGameScheduledEffects';
13
+
14
+ const EMPTY_GAME_INPUT: GameInput = {};
15
+
16
+ /*** Own one local transient Game session while delegating all domain transitions to @ankhorage/game. */
17
+ export function useGameRuntime(props: GameProps): GameRuntimeContextValue {
18
+ const { definition, input = EMPTY_GAME_INPUT, seed = 0, resetKey = '' } = props;
19
+ const definitionRef = useLatestValue(definition);
20
+ const inputRef = useLatestValue(input);
21
+ const outputRef = useLatestValue(props.onOutput);
22
+ const autoAdvanceTime = props.autoAdvanceTime ?? true;
23
+ const clockRef = React.useRef(0);
24
+ const runtimeKey = createRuntimeKey(definition.id, resetKey, seed);
25
+ const [runtime, setRuntime] = React.useState<GameRuntimeState>(() =>
26
+ createRuntimeState(definition, input, seed, runtimeKey, 0),
27
+ );
28
+
29
+ useGameRuntimeReset({
30
+ clockRef,
31
+ definitionRef,
32
+ inputRef,
33
+ runtimeKey,
34
+ seed,
35
+ setRuntime,
36
+ });
37
+ useGameOutputEmitter(runtime, outputRef);
38
+
39
+ const dispatch = React.useCallback<GameRuntimeContextValue['dispatch']>(
40
+ (event) => {
41
+ const now = Date.now();
42
+ setRuntime((current) =>
43
+ applyRuntimeEvent({
44
+ autoAdvanceTime,
45
+ current,
46
+ definition: definitionRef.current,
47
+ elapsedMs: Math.max(0, now - clockRef.current),
48
+ event,
49
+ input: inputRef.current,
50
+ runtimeKey,
51
+ seed,
52
+ }),
53
+ );
54
+ clockRef.current = now;
55
+ },
56
+ [autoAdvanceTime, definitionRef, inputRef, runtimeKey, seed],
57
+ );
58
+
59
+ useGameScheduledEffects({
60
+ clockRef,
61
+ definitionRef,
62
+ enabled: autoAdvanceTime,
63
+ inputRef,
64
+ runtimeKey,
65
+ scheduled: runtime.session.scheduled,
66
+ setRuntime,
67
+ });
68
+
69
+ return React.useMemo(() => ({ session: runtime.session, dispatch }), [dispatch, runtime.session]);
70
+ }
71
+
72
+ interface GameRuntimeResetArgs {
73
+ readonly clockRef: React.RefObject<number>;
74
+ readonly definitionRef: React.RefObject<GameProps['definition']>;
75
+ readonly inputRef: React.RefObject<GameInput>;
76
+ readonly runtimeKey: string;
77
+ readonly seed: number;
78
+ readonly setRuntime: React.Dispatch<React.SetStateAction<GameRuntimeState>>;
79
+ }
80
+
81
+ /*** Reset the transient session only when its explicit serializable runtime identity changes. */
82
+ function useGameRuntimeReset({
83
+ clockRef,
84
+ definitionRef,
85
+ inputRef,
86
+ runtimeKey,
87
+ seed,
88
+ setRuntime,
89
+ }: GameRuntimeResetArgs): void {
90
+ React.useEffect(() => {
91
+ clockRef.current = Date.now();
92
+ setRuntime((current) =>
93
+ current.key === runtimeKey
94
+ ? current
95
+ : createRuntimeState(
96
+ definitionRef.current,
97
+ inputRef.current,
98
+ seed,
99
+ runtimeKey,
100
+ current.revision + 1,
101
+ ),
102
+ );
103
+ }, [clockRef, definitionRef, inputRef, runtimeKey, seed, setRuntime]);
104
+ }
105
+
106
+ /*** Emit each runtime output exactly when one execution revision becomes current. */
107
+ function useGameOutputEmitter(
108
+ runtime: GameRuntimeState,
109
+ outputRef: React.RefObject<GameProps['onOutput']>,
110
+ ): void {
111
+ React.useEffect(() => {
112
+ emitGameOutputs(runtime.outputs, outputRef.current);
113
+ }, [outputRef, runtime.outputs, runtime.revision]);
114
+ }
115
+
116
+ interface ApplyRuntimeEventArgs {
117
+ readonly autoAdvanceTime: boolean;
118
+ readonly current: GameRuntimeState;
119
+ readonly definition: GameProps['definition'];
120
+ readonly elapsedMs: number;
121
+ readonly event: Parameters<typeof applyGameEvent>[2];
122
+ readonly input: GameInput;
123
+ readonly runtimeKey: string;
124
+ readonly seed: number;
125
+ }
126
+
127
+ /*** Apply elapsed time and one explicit game event against the matching local session. */
128
+ function applyRuntimeEvent(args: ApplyRuntimeEventArgs): GameRuntimeState {
129
+ const base =
130
+ args.current.key === args.runtimeKey
131
+ ? args.current
132
+ : createRuntimeState(
133
+ args.definition,
134
+ args.input,
135
+ args.seed,
136
+ args.runtimeKey,
137
+ args.current.revision,
138
+ );
139
+ const timed = args.autoAdvanceTime
140
+ ? advanceGameTime(args.definition, base.session, args.elapsedMs, args.input)
141
+ : { session: base.session, outputs: [] };
142
+ const result = applyGameEvent(args.definition, timed.session, args.event, args.input);
143
+ return {
144
+ key: args.runtimeKey,
145
+ session: result.session,
146
+ outputs: [...(base === args.current ? [] : base.outputs), ...timed.outputs, ...result.outputs],
147
+ revision: args.current.revision + 1,
148
+ };
149
+ }
150
+
151
+ /*** Create one transient runtime state through the published Game owner API. */
152
+ function createRuntimeState(
153
+ definition: GameProps['definition'],
154
+ input: GameInput,
155
+ seed: number,
156
+ key: string,
157
+ revision: number,
158
+ ): GameRuntimeState {
159
+ const result = createGameSession(definition, { input, seed });
160
+ return { key, session: result.session, outputs: result.outputs, revision };
161
+ }
162
+
163
+ /*** Create one runtime identity from serializable definition/session initialization inputs. */
164
+ function createRuntimeKey(definitionId: string, resetKey: string, seed: number): string {
165
+ return `${definitionId}:${resetKey}:${seed}`;
166
+ }
167
+
168
+ /*** Emit runtime outputs through the ZORA event callback boundary. */
169
+ function emitGameOutputs(outputs: readonly GameOutput[], onOutput: GameProps['onOutput']): void {
170
+ if (onOutput === undefined) return;
171
+ outputs.forEach((output) => onOutput(output));
172
+ }
173
+
174
+ /*** Keep a stable ref object updated with the latest adapter input. */
175
+ function useLatestValue<TValue>(value: TValue): React.RefObject<TValue> {
176
+ const ref = React.useRef(value);
177
+ React.useEffect(() => {
178
+ ref.current = value;
179
+ }, [value]);
180
+ return ref;
181
+ }
@@ -0,0 +1,85 @@
1
+ import { advanceGameTime, type GameDefinition, type GameInput } from '@ankhorage/game';
2
+ import React from 'react';
3
+
4
+ import type { GameRuntimeState } from '../../../types/gameRuntime';
5
+
6
+ type SetGameRuntime = React.Dispatch<React.SetStateAction<GameRuntimeState>>;
7
+
8
+ /*** Advance the nearest scheduled game effects through one platform timer. */
9
+ export function useGameScheduledEffects({
10
+ clockRef,
11
+ definitionRef,
12
+ enabled,
13
+ inputRef,
14
+ runtimeKey,
15
+ scheduled,
16
+ setRuntime,
17
+ }: UseGameScheduledEffectsArgs): void {
18
+ React.useEffect(() => {
19
+ if (!enabled || scheduled.length === 0) return undefined;
20
+ const delayMs = resolveNextScheduledDelay(scheduled);
21
+
22
+ const timer = setTimeout(() => {
23
+ const now = Date.now();
24
+ const elapsedMs = Math.max(0, now - clockRef.current);
25
+ clockRef.current = now;
26
+ setRuntime((current) =>
27
+ advanceScheduledRuntime({
28
+ current,
29
+ definition: definitionRef.current,
30
+ elapsedMs,
31
+ input: inputRef.current,
32
+ runtimeKey,
33
+ }),
34
+ );
35
+ }, delayMs);
36
+
37
+ return () => clearTimeout(timer);
38
+ }, [clockRef, definitionRef, enabled, inputRef, runtimeKey, scheduled, setRuntime]);
39
+ }
40
+
41
+ interface UseGameScheduledEffectsArgs {
42
+ readonly clockRef: React.RefObject<number>;
43
+ readonly definitionRef: React.RefObject<GameDefinition>;
44
+ readonly enabled: boolean;
45
+ readonly inputRef: React.RefObject<GameInput>;
46
+ readonly runtimeKey: string;
47
+ readonly scheduled: GameRuntimeState['session']['scheduled'];
48
+ readonly setRuntime: SetGameRuntime;
49
+ }
50
+
51
+ interface AdvanceScheduledRuntimeArgs {
52
+ readonly current: GameRuntimeState;
53
+ readonly definition: GameDefinition;
54
+ readonly elapsedMs: number;
55
+ readonly input: GameInput;
56
+ readonly runtimeKey: string;
57
+ }
58
+
59
+ /*** Advance one matching runtime state while preserving sessions that have already reset. */
60
+ function advanceScheduledRuntime({
61
+ current,
62
+ definition,
63
+ elapsedMs,
64
+ input,
65
+ runtimeKey,
66
+ }: AdvanceScheduledRuntimeArgs): GameRuntimeState {
67
+ if (current.key !== runtimeKey) return current;
68
+ const result = advanceGameTime(definition, current.session, elapsedMs, input);
69
+ return {
70
+ key: runtimeKey,
71
+ session: result.session,
72
+ outputs: result.outputs,
73
+ revision: current.revision + 1,
74
+ };
75
+ }
76
+
77
+ /*** Resolve the nearest scheduled game consequence without owning domain timing rules. */
78
+ function resolveNextScheduledDelay(
79
+ scheduledEffects: GameRuntimeState['session']['scheduled'],
80
+ ): number {
81
+ return scheduledEffects.reduce(
82
+ (nearest, scheduled) => Math.min(nearest, scheduled.remainingMs),
83
+ Number.POSITIVE_INFINITY,
84
+ );
85
+ }
@@ -0,0 +1,94 @@
1
+ import type { ZoraComponentMeta } from '@ankhorage/zora/metadata';
2
+
3
+ /*** Describe a field-relative pointer/touch input adapter for one embeddable Game session. */
4
+ export const gameInputZoneMeta = {
5
+ name: 'GameInputZone',
6
+ category: 'component',
7
+ description:
8
+ 'Normalizes pointer and touch positions into field-relative percentages and dispatches a generic game event.',
9
+ directManifestNode: true,
10
+ allowedChildren: [],
11
+ blueprint: {
12
+ label: 'Game input zone',
13
+ defaultProps: {
14
+ eventType: 'game.pointer',
15
+ x: 0,
16
+ y: 0,
17
+ width: 100,
18
+ height: 100,
19
+ zIndex: 1,
20
+ enabled: true,
21
+ continuous: true,
22
+ },
23
+ },
24
+ props: {
25
+ eventType: {
26
+ type: 'string',
27
+ category: 'Input',
28
+ label: 'Game event type',
29
+ default: 'game.pointer',
30
+ authoring: { authority: 'instance' },
31
+ },
32
+ entityId: {
33
+ type: 'string',
34
+ category: 'Input',
35
+ label: 'Entity id',
36
+ authoring: { authority: 'instance' },
37
+ },
38
+ x: {
39
+ type: 'number',
40
+ category: 'Position',
41
+ label: 'X (%)',
42
+ default: 0,
43
+ authoring: { authority: 'instance' },
44
+ },
45
+ y: {
46
+ type: 'number',
47
+ category: 'Position',
48
+ label: 'Y (%)',
49
+ default: 0,
50
+ authoring: { authority: 'instance' },
51
+ },
52
+ width: {
53
+ type: 'number',
54
+ category: 'Size',
55
+ label: 'Width (%)',
56
+ default: 100,
57
+ authoring: { authority: 'instance' },
58
+ },
59
+ height: {
60
+ type: 'number',
61
+ category: 'Size',
62
+ label: 'Height (%)',
63
+ default: 100,
64
+ authoring: { authority: 'instance' },
65
+ },
66
+ zIndex: {
67
+ type: 'number',
68
+ category: 'Position',
69
+ label: 'Z index',
70
+ default: 1,
71
+ authoring: { authority: 'instance' },
72
+ },
73
+ enabled: {
74
+ type: 'boolean',
75
+ category: 'State',
76
+ label: 'Enabled',
77
+ default: true,
78
+ authoring: { authority: 'instance' },
79
+ },
80
+ continuous: {
81
+ type: 'boolean',
82
+ category: 'Input',
83
+ label: 'Dispatch while moving',
84
+ default: true,
85
+ authoring: { authority: 'instance' },
86
+ },
87
+ accessibilityLabel: {
88
+ type: 'string',
89
+ category: 'Accessibility',
90
+ label: 'Accessibility label',
91
+ authoring: { authority: 'instance' },
92
+ },
93
+ },
94
+ } as const satisfies ZoraComponentMeta;
@@ -0,0 +1,99 @@
1
+ import type { ZoraComponentMeta } from '@ankhorage/zora/metadata';
2
+
3
+ /*** Describe the embeddable config-driven Game runtime/presentation boundary for ZORA authoring. */
4
+ export const gameMeta = {
5
+ name: 'Game',
6
+ category: 'pattern',
7
+ description:
8
+ 'Embeds one config-driven game session inside ordinary ZORA layout and emits domain-neutral game outputs.',
9
+ directManifestNode: true,
10
+ allowedChildren: ['GameEntity', 'GameInputZone', 'GameOverlay', 'Gradient', 'Image', 'View'],
11
+ blueprint: {
12
+ label: 'Game',
13
+ defaultProps: {
14
+ definition: {
15
+ id: 'game',
16
+ initialPhase: 'playing',
17
+ stages: [{ id: 'main' }],
18
+ rules: [],
19
+ },
20
+ seed: 0,
21
+ resetKey: '',
22
+ autoAdvanceTime: true,
23
+ minHeight: 320,
24
+ clip: true,
25
+ },
26
+ },
27
+ bindings: {
28
+ props: {
29
+ definition: {
30
+ value: { type: 'object' },
31
+ acceptsFallback: true,
32
+ },
33
+ input: {
34
+ value: { type: 'record' },
35
+ acceptsFallback: true,
36
+ },
37
+ },
38
+ },
39
+ events: {
40
+ output: {
41
+ label: 'Game output',
42
+ eventType: 'game.output',
43
+ description: 'Emitted when the platform-neutral game runtime produces an app/domain output.',
44
+ payloadFields: [
45
+ { path: 'type', type: 'string' },
46
+ { path: 'payload', type: 'record' },
47
+ ],
48
+ },
49
+ },
50
+ props: {
51
+ seed: {
52
+ type: 'number',
53
+ category: 'Runtime',
54
+ label: 'Initial seed',
55
+ default: 0,
56
+ authoring: { authority: 'instance' },
57
+ },
58
+ resetKey: {
59
+ type: 'string',
60
+ category: 'Runtime',
61
+ label: 'Reset key',
62
+ default: '',
63
+ authoring: { authority: 'instance' },
64
+ },
65
+ autoAdvanceTime: {
66
+ type: 'boolean',
67
+ category: 'Runtime',
68
+ label: 'Advance scheduled effects',
69
+ default: true,
70
+ authoring: { authority: 'instance' },
71
+ },
72
+ aspectRatio: {
73
+ type: 'number',
74
+ category: 'Layout',
75
+ label: 'Aspect ratio',
76
+ authoring: { authority: 'instance' },
77
+ },
78
+ minHeight: {
79
+ type: 'number',
80
+ category: 'Layout',
81
+ label: 'Minimum height',
82
+ default: 320,
83
+ authoring: { authority: 'instance' },
84
+ },
85
+ clip: {
86
+ type: 'boolean',
87
+ category: 'Layout',
88
+ label: 'Clip overflow',
89
+ default: true,
90
+ authoring: { authority: 'instance' },
91
+ },
92
+ accessibilityLabel: {
93
+ type: 'string',
94
+ category: 'Accessibility',
95
+ label: 'Accessibility label',
96
+ authoring: { authority: 'instance' },
97
+ },
98
+ },
99
+ } as const satisfies ZoraComponentMeta;
@@ -1,10 +1,14 @@
1
1
  export type {
2
2
  GameEntityProps,
3
3
  GameFieldProps,
4
+ GameInputZoneProps,
4
5
  GameOverlayPlacement,
5
6
  GameOverlayProps,
6
7
  GamePointerEvents,
8
+ GameProps,
7
9
  } from '../../types/gamePresentation';
10
+ export { Game } from './adapters/inbound/Game';
8
11
  export { GameEntity } from './adapters/inbound/GameEntity';
9
12
  export { GameField } from './adapters/inbound/GameField';
13
+ export { GameInputZone } from './adapters/inbound/GameInputZone';
10
14
  export { GameOverlay } from './adapters/inbound/GameOverlay';
@@ -0,0 +1,4 @@
1
+ /*** Clamp one game presentation coordinate to the inclusive field-percentage range. */
2
+ export function clampGamePercentage(value: number): number {
3
+ return Math.min(100, Math.max(0, value));
4
+ }
@@ -0,0 +1,6 @@
1
+ import { clampGamePercentage } from './clampGamePercentage';
2
+
3
+ /*** Convert one game percentage coordinate to React Native percentage style syntax. */
4
+ export function toGamePercentage(value: number): `${number}%` {
5
+ return `${clampGamePercentage(value)}%`;
6
+ }
package/src/index.ts CHANGED
@@ -1,9 +1,17 @@
1
1
  export type {
2
2
  GameEntityProps,
3
3
  GameFieldProps,
4
+ GameInputZoneProps,
4
5
  GameOverlayPlacement,
5
6
  GameOverlayProps,
6
7
  GamePointerEvents,
8
+ GameProps,
9
+ } from './features/game-presentation/public';
10
+ export {
11
+ Game,
12
+ GameEntity,
13
+ GameField,
14
+ GameInputZone,
15
+ GameOverlay,
7
16
  } from './features/game-presentation/public';
8
- export { GameEntity, GameField, GameOverlay } from './features/game-presentation/public';
9
17
  export { ZORA_GAME_PLUGIN } from './ZORA_GAME_PLUGIN';
@@ -1,3 +1,4 @@
1
+ import type { GameDefinition, GameInput, GameOutput } from '@ankhorage/game';
1
2
  import type React from 'react';
2
3
 
3
4
  type GameOverlayCornerPlacement = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
@@ -31,6 +32,20 @@ export interface GameEntityProps {
31
32
  readonly testID?: string;
32
33
  }
33
34
 
35
+ export interface GameInputZoneProps {
36
+ readonly eventType: string;
37
+ readonly entityId?: string;
38
+ readonly x?: number;
39
+ readonly y?: number;
40
+ readonly width?: number;
41
+ readonly height?: number;
42
+ readonly zIndex?: number;
43
+ readonly enabled?: boolean;
44
+ readonly continuous?: boolean;
45
+ readonly accessibilityLabel?: string;
46
+ readonly testID?: string;
47
+ }
48
+
34
49
  export interface GameOverlayProps {
35
50
  readonly children?: React.ReactNode;
36
51
  readonly placement?: GameOverlayPlacement;
@@ -39,3 +54,12 @@ export interface GameOverlayProps {
39
54
  readonly accessibilityLabel?: string;
40
55
  readonly testID?: string;
41
56
  }
57
+
58
+ export interface GameProps extends GameFieldProps {
59
+ readonly definition: GameDefinition;
60
+ readonly input?: GameInput;
61
+ readonly seed?: number;
62
+ readonly resetKey?: string;
63
+ readonly autoAdvanceTime?: boolean;
64
+ readonly onOutput?: (output: GameOutput) => void;
65
+ }
@@ -0,0 +1,13 @@
1
+ import type { GameEvent, GameOutput, GameSession } from '@ankhorage/game';
2
+
3
+ export interface GameRuntimeContextValue {
4
+ readonly session: GameSession;
5
+ readonly dispatch: (event: GameEvent) => void;
6
+ }
7
+
8
+ export interface GameRuntimeState {
9
+ readonly key: string;
10
+ readonly session: GameSession;
11
+ readonly outputs: readonly GameOutput[];
12
+ readonly revision: number;
13
+ }