@ankhorage/zora-game 0.5.0 → 0.6.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 (87) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +1 -1
  3. package/dist/ZORA_GAME_COMPONENT_META.d.ts +125 -1
  4. package/dist/ZORA_GAME_COMPONENT_META.d.ts.map +1 -1
  5. package/dist/ZORA_GAME_COMPONENT_META.js +2 -0
  6. package/dist/ZORA_GAME_COMPONENT_META.js.map +1 -1
  7. package/dist/ZORA_GAME_PLUGIN.d.ts +130 -1
  8. package/dist/ZORA_GAME_PLUGIN.d.ts.map +1 -1
  9. package/dist/ZORA_GAME_PLUGIN.js +2 -0
  10. package/dist/ZORA_GAME_PLUGIN.js.map +1 -1
  11. package/dist/ZORA_PLUGIN_METADATA.d.ts +128 -1
  12. package/dist/ZORA_PLUGIN_METADATA.d.ts.map +1 -1
  13. package/dist/ZORA_PLUGIN_METADATA.js +4 -0
  14. package/dist/ZORA_PLUGIN_METADATA.js.map +1 -1
  15. package/dist/features/game-presentation/adapters/inbound/Game.d.ts.map +1 -1
  16. package/dist/features/game-presentation/adapters/inbound/Game.js +4 -1
  17. package/dist/features/game-presentation/adapters/inbound/Game.js.map +1 -1
  18. package/dist/features/game-presentation/adapters/inbound/GameEntity.d.ts +2 -1
  19. package/dist/features/game-presentation/adapters/inbound/GameEntity.d.ts.map +1 -1
  20. package/dist/features/game-presentation/adapters/inbound/GameEntity.js +14 -4
  21. package/dist/features/game-presentation/adapters/inbound/GameEntity.js.map +1 -1
  22. package/dist/features/game-presentation/adapters/inbound/GameMeasurementProbe.d.ts +4 -0
  23. package/dist/features/game-presentation/adapters/inbound/GameMeasurementProbe.d.ts.map +1 -0
  24. package/dist/features/game-presentation/adapters/inbound/GameMeasurementProbe.js +52 -0
  25. package/dist/features/game-presentation/adapters/inbound/GameMeasurementProbe.js.map +1 -0
  26. package/dist/features/game-presentation/adapters/inbound/createGameMeasurementEvent.d.ts +14 -0
  27. package/dist/features/game-presentation/adapters/inbound/createGameMeasurementEvent.d.ts.map +1 -0
  28. package/dist/features/game-presentation/adapters/inbound/createGameMeasurementEvent.js +25 -0
  29. package/dist/features/game-presentation/adapters/inbound/createGameMeasurementEvent.js.map +1 -0
  30. package/dist/features/game-presentation/adapters/inbound/measureGameEntity.d.ts +5 -0
  31. package/dist/features/game-presentation/adapters/inbound/measureGameEntity.d.ts.map +1 -0
  32. package/dist/features/game-presentation/adapters/inbound/measureGameEntity.js +22 -0
  33. package/dist/features/game-presentation/adapters/inbound/measureGameEntity.js.map +1 -0
  34. package/dist/features/game-presentation/composition/createGameMeasurementRegistry.d.ts +4 -0
  35. package/dist/features/game-presentation/composition/createGameMeasurementRegistry.d.ts.map +1 -0
  36. package/dist/features/game-presentation/composition/createGameMeasurementRegistry.js +15 -0
  37. package/dist/features/game-presentation/composition/createGameMeasurementRegistry.js.map +1 -0
  38. package/dist/features/game-presentation/composition/useGameRuntime.d.ts +2 -2
  39. package/dist/features/game-presentation/composition/useGameRuntime.d.ts.map +1 -1
  40. package/dist/features/game-presentation/composition/useGameRuntime.js.map +1 -1
  41. package/dist/features/game-presentation/meta/gameEntityMeta.d.ts +14 -0
  42. package/dist/features/game-presentation/meta/gameEntityMeta.d.ts.map +1 -1
  43. package/dist/features/game-presentation/meta/gameEntityMeta.js +7 -0
  44. package/dist/features/game-presentation/meta/gameEntityMeta.js.map +1 -1
  45. package/dist/features/game-presentation/meta/gameMeasurementProbeMeta.d.ts +112 -0
  46. package/dist/features/game-presentation/meta/gameMeasurementProbeMeta.d.ts.map +1 -0
  47. package/dist/features/game-presentation/meta/gameMeasurementProbeMeta.js +70 -0
  48. package/dist/features/game-presentation/meta/gameMeasurementProbeMeta.js.map +1 -0
  49. package/dist/features/game-presentation/meta/gameMeta.d.ts +1 -1
  50. package/dist/features/game-presentation/meta/gameMeta.d.ts.map +1 -1
  51. package/dist/features/game-presentation/meta/gameMeta.js +9 -1
  52. package/dist/features/game-presentation/meta/gameMeta.js.map +1 -1
  53. package/dist/features/game-presentation/public.d.ts +2 -1
  54. package/dist/features/game-presentation/public.d.ts.map +1 -1
  55. package/dist/features/game-presentation/public.js +1 -0
  56. package/dist/features/game-presentation/public.js.map +1 -1
  57. package/dist/index.d.ts +2 -2
  58. package/dist/index.d.ts.map +1 -1
  59. package/dist/index.js +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/types/gamePresentation.d.ts +9 -0
  62. package/dist/types/gamePresentation.d.ts.map +1 -1
  63. package/dist/types/gamePresentation.js.map +1 -1
  64. package/dist/types/gameRuntime.d.ts +16 -1
  65. package/dist/types/gameRuntime.d.ts.map +1 -1
  66. package/dist/types/gameRuntime.js.map +1 -1
  67. package/package.json +1 -1
  68. package/src/ZORA_GAME_COMPONENT_META.ts +2 -0
  69. package/src/ZORA_GAME_PLUGIN.ts +2 -0
  70. package/src/ZORA_PLUGIN_METADATA.test.ts +18 -0
  71. package/src/ZORA_PLUGIN_METADATA.ts +4 -0
  72. package/src/features/game-presentation/adapters/inbound/Game.tsx +7 -1
  73. package/src/features/game-presentation/adapters/inbound/GameEntity.tsx +15 -2
  74. package/src/features/game-presentation/adapters/inbound/GameMeasurementProbe.tsx +79 -0
  75. package/src/features/game-presentation/adapters/inbound/createGameMeasurementEvent.test.ts +56 -0
  76. package/src/features/game-presentation/adapters/inbound/createGameMeasurementEvent.ts +38 -0
  77. package/src/features/game-presentation/adapters/inbound/measureGameEntity.ts +28 -0
  78. package/src/features/game-presentation/composition/createGameMeasurementRegistry.test.ts +35 -0
  79. package/src/features/game-presentation/composition/createGameMeasurementRegistry.ts +16 -0
  80. package/src/features/game-presentation/composition/useGameRuntime.ts +3 -3
  81. package/src/features/game-presentation/meta/gameEntityMeta.ts +7 -0
  82. package/src/features/game-presentation/meta/gameMeasurementProbeMeta.ts +72 -0
  83. package/src/features/game-presentation/meta/gameMeta.ts +9 -1
  84. package/src/features/game-presentation/public.ts +2 -0
  85. package/src/index.ts +2 -0
  86. package/src/types/gamePresentation.ts +10 -0
  87. package/src/types/gameRuntime.ts +19 -1
@@ -0,0 +1,28 @@
1
+ import type { View } from 'react-native';
2
+
3
+ import type { GameMeasurementBounds } from '../../../../types/gameRuntime';
4
+
5
+ /*** Measure one rendered React Native or React Native Web game entity in window coordinates. */
6
+ export function measureGameEntity(
7
+ element: View | null,
8
+ ): Promise<GameMeasurementBounds | undefined> {
9
+ if (element === null) return Promise.resolve(undefined);
10
+
11
+ return new Promise((resolve) => {
12
+ element.measureInWindow((left, top, width, height) => {
13
+ if (![left, top, width, height].every(Number.isFinite) || width < 0 || height < 0) {
14
+ resolve(undefined);
15
+ return;
16
+ }
17
+
18
+ resolve({
19
+ left,
20
+ right: left + width,
21
+ top,
22
+ bottom: top + height,
23
+ width,
24
+ height,
25
+ });
26
+ });
27
+ });
28
+ }
@@ -0,0 +1,35 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import type { GameMeasurementBounds } from '../../../types/gameRuntime';
4
+ import { createGameMeasurementRegistry } from './createGameMeasurementRegistry';
5
+
6
+ const bounds: GameMeasurementBounds = {
7
+ left: 10,
8
+ right: 30,
9
+ top: 20,
10
+ bottom: 50,
11
+ width: 20,
12
+ height: 30,
13
+ };
14
+
15
+ describe('createGameMeasurementRegistry', () => {
16
+ test('registers, reads, and unregisters one local measurement source', async () => {
17
+ const registry = createGameMeasurementRegistry();
18
+ const unregister = registry.registerMeasurement('player', () => Promise.resolve(bounds));
19
+
20
+ expect(await registry.measure('player')).toEqual(bounds);
21
+ unregister();
22
+ expect(await registry.measure('player')).toBeUndefined();
23
+ });
24
+
25
+ test('does not let an older cleanup delete a newer registration with the same id', async () => {
26
+ const registry = createGameMeasurementRegistry();
27
+ const firstCleanup = registry.registerMeasurement('actor', () => Promise.resolve(bounds));
28
+ const newerBounds = { ...bounds, left: 40, right: 60 };
29
+ registry.registerMeasurement('actor', () => Promise.resolve(newerBounds));
30
+
31
+ firstCleanup();
32
+
33
+ expect(await registry.measure('actor')).toEqual(newerBounds);
34
+ });
35
+ });
@@ -0,0 +1,16 @@
1
+ import type { GameMeasurementReader, GameMeasurementRegistry } from '../../../types/gameRuntime';
2
+
3
+ /*** Create one isolated measurement registry for an embeddable Game instance. */
4
+ export function createGameMeasurementRegistry(): GameMeasurementRegistry {
5
+ const readers = new Map<string, GameMeasurementReader>();
6
+
7
+ return {
8
+ registerMeasurement: (id, reader) => {
9
+ readers.set(id, reader);
10
+ return () => {
11
+ if (readers.get(id) === reader) readers.delete(id);
12
+ };
13
+ },
14
+ measure: (id) => readers.get(id)?.() ?? Promise.resolve(undefined),
15
+ };
16
+ }
@@ -8,13 +8,13 @@ import {
8
8
  import React from 'react';
9
9
 
10
10
  import type { GameProps } from '../../../types/gamePresentation';
11
- import type { GameRuntimeContextValue, GameRuntimeState } from '../../../types/gameRuntime';
11
+ import type { GameRuntimeState, GameRuntimeValue } from '../../../types/gameRuntime';
12
12
  import { useGameScheduledEffects } from './useGameScheduledEffects';
13
13
 
14
14
  const EMPTY_GAME_INPUT: GameInput = {};
15
15
 
16
16
  /*** Own one local transient Game session while delegating all domain transitions to @ankhorage/game. */
17
- export function useGameRuntime(props: GameProps): GameRuntimeContextValue {
17
+ export function useGameRuntime(props: GameProps): GameRuntimeValue {
18
18
  const { definition, input = EMPTY_GAME_INPUT, seed = 0, resetKey = '' } = props;
19
19
  const definitionRef = useLatestValue(definition);
20
20
  const inputRef = useLatestValue(input);
@@ -36,7 +36,7 @@ export function useGameRuntime(props: GameProps): GameRuntimeContextValue {
36
36
  });
37
37
  useGameOutputEmitter(runtime, outputRef);
38
38
 
39
- const dispatch = React.useCallback<GameRuntimeContextValue['dispatch']>(
39
+ const dispatch = React.useCallback<GameRuntimeValue['dispatch']>(
40
40
  (event) => {
41
41
  const now = Date.now();
42
42
  setRuntime((current) =>
@@ -22,6 +22,7 @@ export const gameEntityMeta = {
22
22
  rotation: { value: { type: 'number' }, acceptsFallback: true },
23
23
  zIndex: { value: { type: 'number' }, acceptsFallback: true },
24
24
  hidden: { value: { type: 'boolean' }, acceptsFallback: true },
25
+ measurementId: { value: { type: 'string' }, acceptsFallback: true },
25
26
  },
26
27
  },
27
28
  props: {
@@ -94,6 +95,12 @@ export const gameEntityMeta = {
94
95
  default: 'auto',
95
96
  authoring: { authority: 'instance' },
96
97
  },
98
+ measurementId: {
99
+ type: 'string',
100
+ category: 'Measurement',
101
+ label: 'Measurement id',
102
+ authoring: { authority: 'instance' },
103
+ },
97
104
  accessibilityLabel: {
98
105
  type: 'string',
99
106
  category: 'Accessibility',
@@ -0,0 +1,72 @@
1
+ import type { ZoraComponentMeta } from '@ankhorage/zora/metadata';
2
+
3
+ /*** Describe a non-visual source/target geometry sampling adapter for one Game instance. */
4
+ export const gameMeasurementProbeMeta = {
5
+ name: 'GameMeasurementProbe',
6
+ category: 'component',
7
+ description:
8
+ 'Measures two registered game entities and reports their raw rendered bounds through a generic Game event.',
9
+ directManifestNode: true,
10
+ allowedChildren: [],
11
+ blueprint: {
12
+ label: 'Game measurement probe',
13
+ defaultProps: {
14
+ sourceId: 'source',
15
+ targetId: 'target',
16
+ eventType: 'game.measurement',
17
+ delayMs: 0,
18
+ enabled: true,
19
+ },
20
+ },
21
+ bindings: {
22
+ props: {
23
+ sourceId: { value: { type: 'string' }, acceptsFallback: true },
24
+ targetId: { value: { type: 'string' }, acceptsFallback: true },
25
+ eventType: { value: { type: 'string' }, acceptsFallback: true },
26
+ entityId: { value: { type: 'string' }, acceptsFallback: true },
27
+ delayMs: { value: { type: 'number' }, acceptsFallback: true },
28
+ enabled: { value: { type: 'boolean' }, acceptsFallback: true },
29
+ },
30
+ },
31
+ props: {
32
+ sourceId: {
33
+ type: 'string',
34
+ category: 'Measurement',
35
+ label: 'Source id',
36
+ authoring: { authority: 'instance' },
37
+ },
38
+ targetId: {
39
+ type: 'string',
40
+ category: 'Measurement',
41
+ label: 'Target id',
42
+ authoring: { authority: 'instance' },
43
+ },
44
+ eventType: {
45
+ type: 'string',
46
+ category: 'Measurement',
47
+ label: 'Event type',
48
+ default: 'game.measurement',
49
+ authoring: { authority: 'instance' },
50
+ },
51
+ entityId: {
52
+ type: 'string',
53
+ category: 'Measurement',
54
+ label: 'Game entity id',
55
+ authoring: { authority: 'instance' },
56
+ },
57
+ delayMs: {
58
+ type: 'number',
59
+ category: 'Measurement',
60
+ label: 'Delay (ms)',
61
+ default: 0,
62
+ authoring: { authority: 'instance' },
63
+ },
64
+ enabled: {
65
+ type: 'boolean',
66
+ category: 'State',
67
+ label: 'Enabled',
68
+ default: true,
69
+ authoring: { authority: 'instance' },
70
+ },
71
+ },
72
+ } as const satisfies ZoraComponentMeta;
@@ -7,7 +7,15 @@ export const gameMeta = {
7
7
  description:
8
8
  'Embeds one config-driven game session inside ordinary ZORA layout and emits domain-neutral game outputs.',
9
9
  directManifestNode: true,
10
- allowedChildren: ['GameEntity', 'GameInputZone', 'GameOverlay', 'Gradient', 'Image', 'View'],
10
+ allowedChildren: [
11
+ 'GameEntity',
12
+ 'GameInputZone',
13
+ 'GameMeasurementProbe',
14
+ 'GameOverlay',
15
+ 'Gradient',
16
+ 'Image',
17
+ 'View',
18
+ ],
11
19
  blueprint: {
12
20
  label: 'Game',
13
21
  defaultProps: {
@@ -3,6 +3,7 @@ export type {
3
3
  GameFieldProps,
4
4
  GameInputZoneProps,
5
5
  GameKeyboardBinding,
6
+ GameMeasurementProbeProps,
6
7
  GameOverlayPlacement,
7
8
  GameOverlayProps,
8
9
  GamePointerEvents,
@@ -12,4 +13,5 @@ export { Game } from './adapters/inbound/Game';
12
13
  export { GameEntity } from './adapters/inbound/GameEntity';
13
14
  export { GameField } from './adapters/inbound/GameField';
14
15
  export { GameInputZone } from './adapters/inbound/GameInputZone';
16
+ export { GameMeasurementProbe } from './adapters/inbound/GameMeasurementProbe';
15
17
  export { GameOverlay } from './adapters/inbound/GameOverlay';
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export type {
3
3
  GameFieldProps,
4
4
  GameInputZoneProps,
5
5
  GameKeyboardBinding,
6
+ GameMeasurementProbeProps,
6
7
  GameOverlayPlacement,
7
8
  GameOverlayProps,
8
9
  GamePointerEvents,
@@ -13,6 +14,7 @@ export {
13
14
  GameEntity,
14
15
  GameField,
15
16
  GameInputZone,
17
+ GameMeasurementProbe,
16
18
  GameOverlay,
17
19
  } from './features/game-presentation/public';
18
20
  export { ZORA_GAME_PLUGIN } from './ZORA_GAME_PLUGIN';
@@ -28,6 +28,7 @@ export interface GameEntityProps {
28
28
  readonly zIndex?: number;
29
29
  readonly hidden?: boolean;
30
30
  readonly pointerEvents?: GamePointerEvents;
31
+ readonly measurementId?: string;
31
32
  readonly accessibilityLabel?: string;
32
33
  readonly testID?: string;
33
34
  }
@@ -54,6 +55,15 @@ export interface GameInputZoneProps {
54
55
  readonly testID?: string;
55
56
  }
56
57
 
58
+ export interface GameMeasurementProbeProps {
59
+ readonly sourceId: string;
60
+ readonly targetId: string;
61
+ readonly eventType?: string;
62
+ readonly entityId?: string;
63
+ readonly delayMs?: number;
64
+ readonly enabled?: boolean;
65
+ }
66
+
57
67
  export interface GameOverlayProps {
58
68
  readonly children?: React.ReactNode;
59
69
  readonly placement?: GameOverlayPlacement;
@@ -1,10 +1,28 @@
1
1
  import type { GameEvent, GameOutput, GameSession } from '@ankhorage/game';
2
2
 
3
- export interface GameRuntimeContextValue {
3
+ export interface GameMeasurementBounds {
4
+ readonly left: number;
5
+ readonly right: number;
6
+ readonly top: number;
7
+ readonly bottom: number;
8
+ readonly width: number;
9
+ readonly height: number;
10
+ }
11
+
12
+ export type GameMeasurementReader = () => Promise<GameMeasurementBounds | undefined>;
13
+
14
+ export interface GameMeasurementRegistry {
15
+ readonly registerMeasurement: (id: string, reader: GameMeasurementReader) => () => void;
16
+ readonly measure: (id: string) => Promise<GameMeasurementBounds | undefined>;
17
+ }
18
+
19
+ export interface GameRuntimeValue {
4
20
  readonly session: GameSession;
5
21
  readonly dispatch: (event: GameEvent) => void;
6
22
  }
7
23
 
24
+ export interface GameRuntimeContextValue extends GameRuntimeValue, GameMeasurementRegistry {}
25
+
8
26
  export interface GameRuntimeState {
9
27
  readonly key: string;
10
28
  readonly session: GameSession;