@agent-os-lab/agent-game-sdk 0.1.1

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 (68) hide show
  1. package/README.md +99 -0
  2. package/package.json +38 -0
  3. package/src/core/agent-game-store.ts +110 -0
  4. package/src/core/agent-service-event-adapter.ts +20 -0
  5. package/src/core/assets.ts +119 -0
  6. package/src/core/commands.ts +42 -0
  7. package/src/core/errors.ts +19 -0
  8. package/src/core/event-adapter.ts +40 -0
  9. package/src/core/index.ts +23 -0
  10. package/src/core/life-presets.ts +54 -0
  11. package/src/core/movement.ts +50 -0
  12. package/src/core/office-building-layout.ts +376 -0
  13. package/src/core/office-layout.ts +152 -0
  14. package/src/core/pixel-character-avatar.ts +87 -0
  15. package/src/core/pixel-character.ts +684 -0
  16. package/src/core/realtime-events.ts +44 -0
  17. package/src/core/realtime-transport.ts +39 -0
  18. package/src/core/reducer.ts +105 -0
  19. package/src/core/scene.ts +144 -0
  20. package/src/core/schedule.ts +20 -0
  21. package/src/core/sequence.ts +48 -0
  22. package/src/core/state.ts +26 -0
  23. package/src/core/svg-pixel-avatar.ts +372 -0
  24. package/src/core/town-office-assets.ts +109 -0
  25. package/src/core/town-office-room-presets.ts +455 -0
  26. package/src/core/town-office-seat-layout.ts +238 -0
  27. package/src/graph.ts +112 -0
  28. package/src/index.ts +2 -0
  29. package/src/office/core/projection.ts +89 -0
  30. package/src/office/core/source.ts +46 -0
  31. package/src/office/core/types.ts +110 -0
  32. package/src/office/index.ts +4 -0
  33. package/src/office/mount.ts +104 -0
  34. package/src/office/react/AgentGameOfficeView.ts +58 -0
  35. package/src/office/react/index.ts +1 -0
  36. package/src/office/renderers/three/agent-activity-effects.ts +161 -0
  37. package/src/office/renderers/three/agent-animation.ts +205 -0
  38. package/src/office/renderers/three/agent-body-instancing.ts +119 -0
  39. package/src/office/renderers/three/agent-label.ts +82 -0
  40. package/src/office/renderers/three/agent-layout.ts +72 -0
  41. package/src/office/renderers/three/agent-mesh.ts +145 -0
  42. package/src/office/renderers/three/mount.ts +253 -0
  43. package/src/office/renderers/three/scene.ts +790 -0
  44. package/src/phaser/agent-game-scene.ts +87 -0
  45. package/src/phaser/anchor-debug.ts +22 -0
  46. package/src/phaser/avatar-registry.ts +46 -0
  47. package/src/phaser/camera-controls.ts +419 -0
  48. package/src/phaser/camera-model.ts +81 -0
  49. package/src/phaser/create-agent-game.ts +242 -0
  50. package/src/phaser/debug-overlay.ts +21 -0
  51. package/src/phaser/index.ts +13 -0
  52. package/src/phaser/movement-tween.ts +59 -0
  53. package/src/phaser/office-background.ts +48 -0
  54. package/src/phaser/office-building-renderer.ts +87 -0
  55. package/src/phaser/office-layout-renderer.ts +58 -0
  56. package/src/phaser/render-layers.ts +30 -0
  57. package/src/phaser/scene-reconciler.ts +614 -0
  58. package/src/phaser/scene-renderer.ts +138 -0
  59. package/src/phaser/text-style.ts +8 -0
  60. package/src/phaser/town-office-business-props.ts +256 -0
  61. package/src/phaser/town-office-environment.ts +89 -0
  62. package/src/phaser/town-office-furniture.ts +182 -0
  63. package/src/phaser/town-office-primitives.ts +53 -0
  64. package/src/phaser/town-office-renderer.ts +429 -0
  65. package/src/phaser/types.ts +67 -0
  66. package/src/phaser/viewport.ts +88 -0
  67. package/src/runtime-client.ts +435 -0
  68. package/src/types.ts +80 -0
package/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Agent Game SDK
2
+
3
+ Embeddable agent game views and runtime clients.
4
+
5
+ ## Office View
6
+
7
+ Framework-neutral mount API:
8
+
9
+ ```ts
10
+ import { AgentGameRuntimeBrowserClient } from "agent-game-sdk";
11
+ import { mountAgentGameOffice } from "agent-game-sdk/office";
12
+
13
+ const client = new AgentGameRuntimeBrowserClient({
14
+ baseUrl: "",
15
+ tokenPath: "/api/agent-game-runtime-token",
16
+ accessToken: async () => getScopedToken(),
17
+ });
18
+
19
+ const view = await mountAgentGameOffice(container, {
20
+ renderer: "three",
21
+ source: {
22
+ type: "runtime",
23
+ client,
24
+ },
25
+ });
26
+
27
+ view.focusAgent("agent-1");
28
+ view.destroy();
29
+ ```
30
+
31
+ React wrapper:
32
+
33
+ ```tsx
34
+ import { AgentGameRuntimeBrowserClient } from "agent-game-sdk";
35
+ import { AgentGameOfficeView } from "agent-game-sdk/office/react";
36
+
37
+ const client = new AgentGameRuntimeBrowserClient({
38
+ baseUrl: "",
39
+ });
40
+
41
+ export function Office() {
42
+ return (
43
+ <AgentGameOfficeView
44
+ renderer="three"
45
+ source={{ type: "runtime", client }}
46
+ style={{ height: 640 }}
47
+ />
48
+ );
49
+ }
50
+ ```
51
+
52
+ `agent-game-sdk/office` is renderer and framework neutral. `agent-game-sdk/office/react` is the React-only entrypoint.
53
+
54
+ Browser clients should call an application BFF endpoint. Keep the AgentOS service API key on the server:
55
+
56
+ ```ts
57
+ import { AgentGameRuntimeServerClient } from "agent-game-sdk";
58
+
59
+ const client = new AgentGameRuntimeServerClient({
60
+ baseUrl: process.env.AGENTOS_BASE_URL!,
61
+ apiKey: process.env.AGENTOS_API_KEY!,
62
+ });
63
+
64
+ export async function GET() {
65
+ return Response.json(await client.createRuntimeToken());
66
+ }
67
+ ```
68
+
69
+ ## Local Demo
70
+
71
+ Run the SDK office view demo:
72
+
73
+ ```bash
74
+ AGENTOS_BASE_URL=http://localhost:3000 AGENTOS_API_KEY=... bun run demo
75
+ ```
76
+
77
+ Open `http://localhost:7357`. Set `PORT=7358` or another value to change the port.
78
+
79
+ ## Publish
80
+
81
+ Publishing is controlled from this SDK package directory. The script bumps the package version, builds the package, runs `npm pack --dry-run`, and then publishes to npm:
82
+
83
+ ```bash
84
+ bun run sdk:publish
85
+ ```
86
+
87
+ The default release is patch. Use `--minor` or `--major` when needed:
88
+
89
+ ```bash
90
+ bun run sdk:publish -- --minor
91
+ ```
92
+
93
+ Use `--dry-run` to validate the next version and package contents without publishing. The script restores the previous version after a dry run.
94
+
95
+ If npm requires two-factor authentication, pass the one-time password with `--otp`:
96
+
97
+ ```bash
98
+ bun run sdk:publish -- --otp 123456
99
+ ```
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@agent-os-lab/agent-game-sdk",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "files": [
6
+ "src",
7
+ "README.md"
8
+ ],
9
+ "exports": {
10
+ ".": "./src/index.ts",
11
+ "./office": "./src/office/index.ts",
12
+ "./office/react": "./src/office/react/index.ts"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc --noEmit -p tsconfig.json",
16
+ "demo": "bun --env-file=.env.development --hot demo/server.ts",
17
+ "sdk:publish": "bun scripts/publish-agent-game-sdk.ts",
18
+ "test": "bun test",
19
+ "typecheck": "tsc --noEmit -p tsconfig.json"
20
+ },
21
+ "peerDependencies": {
22
+ "react": "^19.0.0",
23
+ "react-dom": "^19.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/bun": "latest",
27
+ "@types/react": "^19.2.14",
28
+ "@types/react-dom": "^19.2.3",
29
+ "@types/three": "^0.184.1",
30
+ "react": "^19.2.6",
31
+ "react-dom": "^19.2.6",
32
+ "typescript": "latest"
33
+ },
34
+ "dependencies": {
35
+ "phaser": "^3.90.0",
36
+ "three": "^0.184.0"
37
+ }
38
+ }
@@ -0,0 +1,110 @@
1
+ import { applyAgentGameCommand, createAgentGameSnapshot } from "./reducer.js";
2
+ import { validateAgentGameScene, type AgentGameSceneDefinition } from "./scene.js";
3
+ import type { AgentGameCommand } from "./commands.js";
4
+ import type { AgentGameAgent, AgentGameSnapshot } from "./state.js";
5
+ import type {
6
+ AgentGameConnectionStatus,
7
+ AgentGameRealtimeCursor,
8
+ AgentGameRealtimeEvent,
9
+ AgentGameSnapshotPatch,
10
+ } from "./realtime-events.js";
11
+
12
+ export type AgentGameStoreListener = (snapshot: AgentGameSnapshot) => void;
13
+
14
+ export type AgentGameStore = {
15
+ getSnapshot(): AgentGameSnapshot;
16
+ getCursor(): AgentGameRealtimeCursor | null;
17
+ getConnectionStatus(): AgentGameConnectionStatus;
18
+ dispatch(command: AgentGameCommand): AgentGameSnapshot;
19
+ applyRemoteEvent(event: AgentGameRealtimeEvent): AgentGameSnapshot;
20
+ replaceSnapshot(snapshot: AgentGameSnapshot, cursor?: AgentGameRealtimeCursor): AgentGameSnapshot;
21
+ subscribe(listener: AgentGameStoreListener): () => void;
22
+ };
23
+
24
+ export function createAgentGameStore(scene: AgentGameSceneDefinition): AgentGameStore {
25
+ let snapshot = createAgentGameSnapshot(scene);
26
+ let cursor: AgentGameRealtimeCursor | null = null;
27
+ let connectionStatus: AgentGameConnectionStatus = { kind: "closed" };
28
+ const appliedEventIds = new Set<string>();
29
+ const listeners = new Set<AgentGameStoreListener>();
30
+
31
+ const notify = () => {
32
+ for (const listener of listeners) {
33
+ listener(snapshot);
34
+ }
35
+ };
36
+
37
+ return {
38
+ getSnapshot() {
39
+ return snapshot;
40
+ },
41
+ getCursor() {
42
+ return cursor;
43
+ },
44
+ getConnectionStatus() {
45
+ return connectionStatus;
46
+ },
47
+ dispatch(command) {
48
+ snapshot = applyAgentGameCommand(snapshot, command);
49
+ notify();
50
+ return snapshot;
51
+ },
52
+ applyRemoteEvent(event) {
53
+ if (appliedEventIds.has(event.id)) {
54
+ return snapshot;
55
+ }
56
+ appliedEventIds.add(event.id);
57
+ cursor = event.cursor;
58
+
59
+ switch (event.type) {
60
+ case "snapshot":
61
+ snapshot = event.snapshot;
62
+ break;
63
+ case "command":
64
+ snapshot = applyAgentGameCommand(snapshot, event.command);
65
+ break;
66
+ case "patch":
67
+ snapshot = applySnapshotPatch(snapshot, event.patch);
68
+ break;
69
+ case "connection":
70
+ connectionStatus = event.status;
71
+ break;
72
+ }
73
+
74
+ notify();
75
+ return snapshot;
76
+ },
77
+ replaceSnapshot(nextSnapshot, nextCursor) {
78
+ snapshot = nextSnapshot;
79
+ cursor = nextCursor ?? cursor;
80
+ notify();
81
+ return snapshot;
82
+ },
83
+ subscribe(listener) {
84
+ listeners.add(listener);
85
+ return () => {
86
+ listeners.delete(listener);
87
+ };
88
+ },
89
+ };
90
+ }
91
+
92
+ function applySnapshotPatch(snapshot: AgentGameSnapshot, patch: AgentGameSnapshotPatch): AgentGameSnapshot {
93
+ const agents = { ...snapshot.agents };
94
+ for (const [agentId, agentPatch] of Object.entries(patch.agents ?? {})) {
95
+ if (agentPatch === null) {
96
+ delete agents[agentId];
97
+ continue;
98
+ }
99
+ const current = agents[agentId];
100
+ if (!current) {
101
+ continue;
102
+ }
103
+ agents[agentId] = { ...current, ...agentPatch } as AgentGameAgent;
104
+ }
105
+
106
+ return {
107
+ scene: patch.scene ? validateAgentGameScene(patch.scene) : snapshot.scene,
108
+ agents,
109
+ };
110
+ }
@@ -0,0 +1,20 @@
1
+ import type { AgentGameRealtimeEvent } from "./realtime-events.js";
2
+ import { mapAgentServiceEventToGameCommands, type AgentServiceEventLike } from "./event-adapter.js";
3
+
4
+ export type MapAgentServiceEventToRealtimeEventsOptions = {
5
+ nextId: () => string;
6
+ nextCursor: () => string;
7
+ };
8
+
9
+ export function mapAgentServiceEventToRealtimeEvents(
10
+ agentId: string,
11
+ event: AgentServiceEventLike,
12
+ options: MapAgentServiceEventToRealtimeEventsOptions,
13
+ ): AgentGameRealtimeEvent[] {
14
+ return mapAgentServiceEventToGameCommands(agentId, event).map((command) => ({
15
+ id: options.nextId(),
16
+ cursor: options.nextCursor(),
17
+ type: "command",
18
+ command,
19
+ }));
20
+ }
@@ -0,0 +1,119 @@
1
+ import { AgentGameError } from "./errors.js";
2
+ import type { TiledMapLike } from "./scene.js";
3
+
4
+ export const REQUIRED_AGENT_AVATAR_ANIMATIONS = [
5
+ "idle.down",
6
+ "idle.up",
7
+ "idle.left",
8
+ "idle.right",
9
+ "walk.down",
10
+ "walk.up",
11
+ "walk.left",
12
+ "walk.right",
13
+ "work.typing",
14
+ "emote.think",
15
+ "emote.talk",
16
+ ] as const;
17
+
18
+ export type AgentAvatarAnimationName = (typeof REQUIRED_AGENT_AVATAR_ANIMATIONS)[number];
19
+ export type AgentAvatarAnimationFrames = string | string[];
20
+
21
+ export type AgentAvatarDefinition = {
22
+ id: string;
23
+ imageUrl: string;
24
+ atlasUrl: string;
25
+ animations: Partial<Record<AgentAvatarAnimationName, AgentAvatarAnimationFrames>> & Record<string, AgentAvatarAnimationFrames>;
26
+ };
27
+
28
+ export type AgentGameMapAsset = {
29
+ key: string;
30
+ url: string;
31
+ tilesets: Array<{ key: string; imageUrl: string }>;
32
+ };
33
+
34
+ export type AgentGameAssetManifest = {
35
+ avatars: AgentAvatarDefinition[];
36
+ maps: AgentGameMapAsset[];
37
+ };
38
+
39
+ export type CreateAgentGameMapAssetFromTiledMapOptions = {
40
+ key: string;
41
+ url: string;
42
+ tiledMap: TiledMapLike;
43
+ };
44
+
45
+ export function validateAgentAvatarDefinition(avatar: AgentAvatarDefinition): AgentAvatarDefinition {
46
+ assertNonEmpty(avatar.id, "avatar id");
47
+ assertNonEmpty(avatar.imageUrl, "avatar image url");
48
+ assertNonEmpty(avatar.atlasUrl, "avatar atlas url");
49
+
50
+ for (const animation of REQUIRED_AGENT_AVATAR_ANIMATIONS) {
51
+ if (!avatar.animations[animation]) {
52
+ throw new AgentGameError("missing_animation", `Agent game avatar ${avatar.id} is missing animation: ${animation}`);
53
+ }
54
+ const frames = normalizeAgentAvatarAnimationFrames(avatar.animations[animation]);
55
+ if (frames.length === 0) {
56
+ throw new AgentGameError("missing_animation", `Agent game avatar ${avatar.id} is missing animation: ${animation}`);
57
+ }
58
+ }
59
+
60
+ return avatar;
61
+ }
62
+
63
+ export function normalizeAgentAvatarAnimationFrames(frames: AgentAvatarAnimationFrames | undefined): string[] {
64
+ if (frames === undefined) {
65
+ return [];
66
+ }
67
+ return Array.isArray(frames) ? frames : [frames];
68
+ }
69
+
70
+ export function validateAgentGameAssetManifest(manifest: AgentGameAssetManifest): AgentGameAssetManifest {
71
+ const avatarIds = new Set<string>();
72
+ for (const avatar of manifest.avatars) {
73
+ validateAgentAvatarDefinition(avatar);
74
+ if (avatarIds.has(avatar.id)) {
75
+ throw new AgentGameError("invalid_asset_manifest", `Duplicate agent game avatar id: ${avatar.id}`);
76
+ }
77
+ avatarIds.add(avatar.id);
78
+ }
79
+
80
+ const mapKeys = new Set<string>();
81
+ for (const map of manifest.maps) {
82
+ assertNonEmpty(map.key, "map key");
83
+ assertNonEmpty(map.url, "map url");
84
+ if (mapKeys.has(map.key)) {
85
+ throw new AgentGameError("invalid_asset_manifest", `Duplicate agent game map key: ${map.key}`);
86
+ }
87
+ mapKeys.add(map.key);
88
+ }
89
+
90
+ return manifest;
91
+ }
92
+
93
+ export function createAgentGameMapAssetFromTiledMap(options: CreateAgentGameMapAssetFromTiledMapOptions): AgentGameMapAsset {
94
+ const map: AgentGameMapAsset = {
95
+ key: options.key,
96
+ url: options.url,
97
+ tilesets: (options.tiledMap.tilesets ?? []).map((tileset) => ({
98
+ key: requireNonEmpty(tileset.name, "tileset name"),
99
+ imageUrl: requireNonEmpty(tileset.image, "tileset image"),
100
+ })),
101
+ };
102
+
103
+ assertNonEmpty(map.key, "map key");
104
+ assertNonEmpty(map.url, "map url");
105
+ return map;
106
+ }
107
+
108
+ function requireNonEmpty(value: string | undefined, field: string): string {
109
+ if (!value || value.trim().length === 0) {
110
+ throw new AgentGameError("invalid_asset_manifest", `Agent game ${field} must not be empty`);
111
+ }
112
+ return value;
113
+ }
114
+
115
+ function assertNonEmpty(value: string, field: string): void {
116
+ if (value.trim().length === 0) {
117
+ throw new AgentGameError("invalid_asset_manifest", `Agent game ${field} must not be empty`);
118
+ }
119
+ }
@@ -0,0 +1,42 @@
1
+ export type AgentToolCallStatus = "started" | "succeeded" | "failed";
2
+ export type AgentGameEmotion = "focused" | "happy" | "blocked" | "tired";
3
+
4
+ export type AgentGameCommand =
5
+ | {
6
+ type: "agent.spawn";
7
+ agentId: string;
8
+ name: string;
9
+ avatarId: string;
10
+ locationId: string;
11
+ }
12
+ | {
13
+ type: "agent.move";
14
+ agentId: string;
15
+ locationId: string;
16
+ }
17
+ | {
18
+ type: "agent.work";
19
+ agentId: string;
20
+ workstationId: string;
21
+ }
22
+ | {
23
+ type: "agent.think";
24
+ agentId: string;
25
+ text?: string;
26
+ }
27
+ | {
28
+ type: "agent.talk";
29
+ agentId: string;
30
+ text: string;
31
+ }
32
+ | {
33
+ type: "agent.tool_call";
34
+ agentId: string;
35
+ toolName: string;
36
+ status: AgentToolCallStatus;
37
+ }
38
+ | {
39
+ type: "agent.emote";
40
+ agentId: string;
41
+ emotion: AgentGameEmotion;
42
+ };
@@ -0,0 +1,19 @@
1
+ export type AgentGameErrorCode =
2
+ | "invalid_scene"
3
+ | "invalid_asset_manifest"
4
+ | "missing_agent"
5
+ | "missing_anchor"
6
+ | "missing_avatar"
7
+ | "missing_animation"
8
+ | "invalid_renderer"
9
+ | "runtime_destroyed";
10
+
11
+ export class AgentGameError extends Error {
12
+ readonly code: AgentGameErrorCode;
13
+
14
+ constructor(code: AgentGameErrorCode, message: string) {
15
+ super(message);
16
+ this.name = "AgentGameError";
17
+ this.code = code;
18
+ }
19
+ }
@@ -0,0 +1,40 @@
1
+ import type { AgentGameCommand } from "./commands.js";
2
+ import { AgentGameError } from "./errors.js";
3
+
4
+ export type AgentServiceEventLike = {
5
+ type: string;
6
+ name?: string;
7
+ toolName?: string;
8
+ message?: string;
9
+ delta?: string;
10
+ };
11
+
12
+ export function mapAgentServiceEventToGameCommands(
13
+ agentId: string,
14
+ event: AgentServiceEventLike,
15
+ ): AgentGameCommand[] {
16
+ switch (event.type) {
17
+ case "message.started":
18
+ return [{ type: "agent.think", agentId, text: "Thinking" }];
19
+ case "message.delta":
20
+ return [];
21
+ case "message.completed":
22
+ return event.message ? [{ type: "agent.talk", agentId, text: event.message }] : [];
23
+ case "tool.started":
24
+ return [{ type: "agent.tool_call", agentId, toolName: requireToolName(event), status: "started" }];
25
+ case "tool.completed":
26
+ return [{ type: "agent.tool_call", agentId, toolName: requireToolName(event), status: "succeeded" }];
27
+ case "tool.failed":
28
+ return [{ type: "agent.tool_call", agentId, toolName: requireToolName(event), status: "failed" }];
29
+ default:
30
+ return [];
31
+ }
32
+ }
33
+
34
+ function requireToolName(event: AgentServiceEventLike): string {
35
+ const toolName = event.name ?? event.toolName;
36
+ if (!toolName) {
37
+ throw new AgentGameError("invalid_asset_manifest", `Agent service event is missing tool name for ${event.type}`);
38
+ }
39
+ return toolName;
40
+ }
@@ -0,0 +1,23 @@
1
+ export * from "./assets.js";
2
+ export * from "./agent-game-store.js";
3
+ export * from "./agent-service-event-adapter.js";
4
+ export * from "./commands.js";
5
+ export * from "./event-adapter.js";
6
+ export * from "./errors.js";
7
+ export * from "./life-presets.js";
8
+ export * from "./movement.js";
9
+ export * from "./office-building-layout.js";
10
+ export * from "./office-layout.js";
11
+ export * from "./pixel-character-avatar.js";
12
+ export * from "./pixel-character.js";
13
+ export * from "./reducer.js";
14
+ export * from "./realtime-events.js";
15
+ export * from "./realtime-transport.js";
16
+ export * from "./schedule.js";
17
+ export * from "./scene.js";
18
+ export * from "./sequence.js";
19
+ export * from "./state.js";
20
+ export * from "./svg-pixel-avatar.js";
21
+ export * from "./town-office-assets.js";
22
+ export * from "./town-office-room-presets.js";
23
+ export * from "./town-office-seat-layout.js";
@@ -0,0 +1,54 @@
1
+ import type { AgentGameAnchor, AgentGameSceneDefinition } from "./scene.js";
2
+ import { validateAgentGameScene } from "./scene.js";
3
+
4
+ export type AgentGameLifeSceneKind = "home" | "cafe" | "commute" | "sleep";
5
+
6
+ export type CreateLifeScenePresetOptions = {
7
+ id: string;
8
+ kind: AgentGameLifeSceneKind;
9
+ mapUrl: string;
10
+ tilesetImageUrl: string;
11
+ };
12
+
13
+ export function createLifeScenePreset(options: CreateLifeScenePresetOptions): AgentGameSceneDefinition {
14
+ return validateAgentGameScene({
15
+ id: options.id,
16
+ map: {
17
+ key: options.id,
18
+ url: options.mapUrl,
19
+ tilesets: [{ key: `${options.id}-tiles`, imageUrl: options.tilesetImageUrl }],
20
+ },
21
+ anchors: createLifeSceneAnchors(options.kind),
22
+ });
23
+ }
24
+
25
+ function createLifeSceneAnchors(kind: AgentGameLifeSceneKind): AgentGameAnchor[] {
26
+ switch (kind) {
27
+ case "home":
28
+ return [
29
+ { id: "home-spawn", kind: "spawn", x: 80, y: 128, direction: "down" },
30
+ { id: "home-desk", kind: "desk", x: 208, y: 128, direction: "up" },
31
+ { id: "home-living", kind: "living", x: 144, y: 224, direction: "down" },
32
+ { id: "home-sleep", kind: "break", x: 304, y: 96, direction: "left" },
33
+ ];
34
+ case "cafe":
35
+ return [
36
+ { id: "cafe-spawn", kind: "spawn", x: 64, y: 192, direction: "right" },
37
+ { id: "cafe-table", kind: "desk", x: 192, y: 160, direction: "down" },
38
+ { id: "cafe-counter", kind: "meeting", x: 320, y: 112, direction: "up" },
39
+ { id: "cafe-break", kind: "break", x: 384, y: 224, direction: "left" },
40
+ ];
41
+ case "commute":
42
+ return [
43
+ { id: "commute-spawn", kind: "spawn", x: 64, y: 160, direction: "right" },
44
+ { id: "commute-stop", kind: "meeting", x: 224, y: 160, direction: "right" },
45
+ { id: "commute-seat", kind: "break", x: 352, y: 160, direction: "left" },
46
+ ];
47
+ case "sleep":
48
+ return [
49
+ { id: "sleep-spawn", kind: "spawn", x: 96, y: 160, direction: "right" },
50
+ { id: "sleep-room", kind: "living", x: 208, y: 160, direction: "down" },
51
+ { id: "sleep-bed", kind: "break", x: 320, y: 128, direction: "left" },
52
+ ];
53
+ }
54
+ }
@@ -0,0 +1,50 @@
1
+ import { AgentGameError } from "./errors.js";
2
+ import { findAgentGameAnchor } from "./scene.js";
3
+ import type { AgentGameCommand } from "./commands.js";
4
+ import type { AgentGameSnapshot } from "./state.js";
5
+
6
+ export type CreateAgentGameMovePathCommandsOptions = {
7
+ agentId: string;
8
+ locationIds: string[];
9
+ };
10
+
11
+ export function createAgentGameMovePathCommands(options: CreateAgentGameMovePathCommandsOptions): AgentGameCommand[] {
12
+ if (options.locationIds.length === 0) {
13
+ throw new AgentGameError("missing_anchor", "Agent game move path must include at least one waypoint");
14
+ }
15
+
16
+ return options.locationIds.map((locationId) => ({
17
+ type: "agent.move",
18
+ agentId: options.agentId,
19
+ locationId,
20
+ }));
21
+ }
22
+
23
+ export function completeAgentGameMove(snapshot: AgentGameSnapshot, agentId: string): AgentGameSnapshot {
24
+ const agent = snapshot.agents[agentId];
25
+ if (!agent) {
26
+ throw new AgentGameError("missing_agent", `Agent game command references missing agent: ${agentId}`);
27
+ }
28
+ if (agent.activity.kind !== "moving") {
29
+ throw new AgentGameError("missing_anchor", `Agent game agent ${agentId} is not moving`);
30
+ }
31
+
32
+ const anchor = findAgentGameAnchor(snapshot.scene, agent.activity.toLocationId);
33
+ if (!anchor) {
34
+ throw new AgentGameError("missing_anchor", `Agent game move references missing anchor: ${agent.activity.toLocationId}`);
35
+ }
36
+
37
+ return {
38
+ scene: snapshot.scene,
39
+ agents: {
40
+ ...snapshot.agents,
41
+ [agentId]: {
42
+ ...agent,
43
+ locationId: anchor.id,
44
+ direction: anchor.direction ?? agent.direction,
45
+ activity: { kind: "idle" },
46
+ visibleText: undefined,
47
+ },
48
+ },
49
+ };
50
+ }