@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
@@ -0,0 +1,53 @@
1
+ import { parseHexColor } from "../core/index.js";
2
+ import { officeDepth } from "./render-layers.js";
3
+ import type { PhaserDisplayObjectLike, PhaserTextLike } from "./scene-reconciler.js";
4
+ import { crispTextStyle } from "./text-style.js";
5
+
6
+ export type TownOfficeSceneLike = {
7
+ add: {
8
+ rectangle: (x: number, y: number, width: number, height: number, fillColor: number) => PhaserDisplayObjectLike;
9
+ text: (x: number, y: number, text: string, style?: Record<string, unknown>) => PhaserTextLike;
10
+ };
11
+ cameras?: {
12
+ main?: {
13
+ zoom: number;
14
+ scrollX: number;
15
+ scrollY: number;
16
+ width?: number;
17
+ height?: number;
18
+ };
19
+ };
20
+ events?: {
21
+ on: (eventName: string, handler: (...args: any[]) => void) => unknown;
22
+ };
23
+ };
24
+
25
+ export function rect(
26
+ scene: TownOfficeSceneLike,
27
+ x: number,
28
+ y: number,
29
+ width: number,
30
+ height: number,
31
+ color: string,
32
+ depth: number,
33
+ ): PhaserDisplayObjectLike {
34
+ return scene.add.rectangle(x, y, width, height, parseHexColor(color)).setDepth?.(officeDepth(depth))
35
+ ?? scene.add.rectangle(x, y, width, height, parseHexColor(color));
36
+ }
37
+
38
+ export function text(
39
+ scene: TownOfficeSceneLike,
40
+ x: number,
41
+ y: number,
42
+ value: string,
43
+ fontSize: number,
44
+ color: string,
45
+ depth: number,
46
+ ): void {
47
+ scene.add.text(x, y, value, crispTextStyle({
48
+ color,
49
+ fontSize: `${Math.max(1, fontSize)}px`,
50
+ fontFamily: "sans-serif",
51
+ fontStyle: "700",
52
+ })).setOrigin?.(0.5, 0.5).setDepth?.(officeDepth(depth));
53
+ }
@@ -0,0 +1,429 @@
1
+ import {
2
+ createTownOfficeRoomObjects,
3
+ getOfficeRoomBounds,
4
+ measureOfficeBuildingLayoutSize,
5
+ normalizeOfficeBuildingLayoutForRatio,
6
+ type OfficeBuildingLayout,
7
+ type OfficeBuildingObjectDefinition,
8
+ type OfficeRoomBounds,
9
+ } from "../core/index.js";
10
+ import {
11
+ drawBattery,
12
+ drawBeaker,
13
+ drawBicycleRack,
14
+ drawBigScreen,
15
+ drawCertBadge,
16
+ drawChartBoard,
17
+ drawEVCharger,
18
+ drawGazebo,
19
+ drawHenanMap,
20
+ drawKpiCard,
21
+ drawMegaphone,
22
+ drawMonitorWall,
23
+ drawPartyFlag,
24
+ drawPingPongTable,
25
+ drawPodium,
26
+ drawRedBanner,
27
+ drawSeal,
28
+ drawServerRack,
29
+ drawSolarPanel,
30
+ drawTestRack,
31
+ drawTickerBoard,
32
+ drawTransmissionTower,
33
+ drawTree,
34
+ drawTV,
35
+ drawWindTurbine,
36
+ } from "./town-office-business-props.js";
37
+ import { drawWorldBoundsDebugOverlay, type AgentGameDebugOverlays } from "./debug-overlay.js";
38
+ import {
39
+ drawAirConditioner,
40
+ drawDoor,
41
+ drawExitSign,
42
+ drawLightSwitch,
43
+ drawPicture,
44
+ drawPrinter,
45
+ drawRoomEnvironment,
46
+ drawSocket,
47
+ drawWindow,
48
+ type TownOfficeRoomStyle,
49
+ } from "./town-office-environment.js";
50
+ import {
51
+ drawBench,
52
+ drawBookshelf,
53
+ drawBulletinBoard,
54
+ drawCabinet,
55
+ drawChair,
56
+ drawClock,
57
+ drawCoffeeMachine,
58
+ drawDesk,
59
+ drawFireExtinguisher,
60
+ drawFlowerPot,
61
+ drawLaptop,
62
+ drawMachine,
63
+ drawMonitor,
64
+ drawPhone,
65
+ drawPlant,
66
+ drawRugMat,
67
+ drawScheduleBoard,
68
+ drawSofa,
69
+ drawTable,
70
+ drawTrashCan,
71
+ drawVendingMachine,
72
+ drawWhiteboard,
73
+ } from "./town-office-furniture.js";
74
+ import { rect, type TownOfficeSceneLike } from "./town-office-primitives.js";
75
+ import { crispTextStyle } from "./text-style.js";
76
+ import { officeDepth } from "./render-layers.js";
77
+
78
+ export type { TownOfficeSceneLike } from "./town-office-primitives.js";
79
+
80
+ const HEADER_HEIGHT = 34;
81
+ const HEADER_TITLE_PADDING_X = 12;
82
+ const HEADER_TITLE_OFFSET_Y = 2;
83
+ const FLOOR_GRID_LINE_WIDTH = 2;
84
+
85
+ const ROOM_STYLES: Record<string, TownOfficeRoomStyle> = {
86
+ dispatch: { floor: "#e8edf4", wallTop: "#1a365d", accent: "#0d2340" },
87
+ trading: { floor: "#e8edf4", wallTop: "#1a365d", accent: "#0d2340" },
88
+ testing: { floor: "#e4ebf0", wallTop: "#1e3a5f", accent: "#0f2847" },
89
+ lab: { floor: "#e4ebf0", wallTop: "#1e3a5f", accent: "#0f2847" },
90
+ resource: { floor: "#e4f0ea", wallTop: "#1a6b4a", accent: "#0e5538" },
91
+ archive: { floor: "#eae6de", wallTop: "#3a3028", accent: "#2a2018" },
92
+ library: { floor: "#eae6de", wallTop: "#3a3028", accent: "#2a2018" },
93
+ cafeteria: { floor: "#f0ece4", wallTop: "#8b4513", accent: "#6b3008" },
94
+ lounge: { floor: "#eeebe4", wallTop: "#4a3828", accent: "#3a2818" },
95
+ fitness: { floor: "#e4ede8", wallTop: "#1a5a3a", accent: "#0e4a2a" },
96
+ meeting: { floor: "#ebe8f0", wallTop: "#2d3a5c", accent: "#1a2845" },
97
+ };
98
+
99
+ export type TownOfficeBuildingRenderOptions = {
100
+ debugOverlays?: AgentGameDebugOverlays;
101
+ domOverlayRoot?: TownOfficeDomOverlayRoot;
102
+ };
103
+
104
+ export type TownOfficeDomOverlayElement = {
105
+ className: string;
106
+ textContent: string | null;
107
+ clientWidth?: number;
108
+ clientHeight?: number;
109
+ style: Record<string, string>;
110
+ appendChild?: (child: TownOfficeDomOverlayElement) => unknown;
111
+ };
112
+
113
+ export type TownOfficeDomOverlayRoot = TownOfficeDomOverlayElement & {
114
+ ownerDocument?: {
115
+ createElement: (tagName: string) => TownOfficeDomOverlayElement;
116
+ };
117
+ };
118
+
119
+ export function drawTownOfficeBuildingLayout(
120
+ scene: TownOfficeSceneLike,
121
+ layout: OfficeBuildingLayout,
122
+ options: TownOfficeBuildingRenderOptions = {},
123
+ ): void {
124
+ const normalizedLayout = normalizeOfficeBuildingLayoutForRatio(layout);
125
+ const size = getBuildingCanvasSize(normalizedLayout);
126
+ rect(scene, size.width / 2, size.height / 2, size.width, size.height, "#e8ecf0", 0);
127
+ if (options.debugOverlays?.worldBounds) {
128
+ drawWorldBoundsDebugOverlay(scene, size.width, size.height);
129
+ }
130
+ drawBackgroundPattern(scene, size.width, size.height);
131
+
132
+ for (const room of normalizedLayout.rooms) {
133
+ const bounds = getOfficeRoomBounds(normalizedLayout, room);
134
+ const roomStyleKey = room.type ?? room.id;
135
+ const style = ROOM_STYLES[roomStyleKey] ?? ROOM_STYLES[room.id] ?? {
136
+ floor: room.theme.floorColor,
137
+ wallTop: room.theme.titleColor,
138
+ accent: room.theme.borderColor,
139
+ };
140
+
141
+ rect(scene, bounds.x + bounds.width / 2, bounds.y + bounds.height / 2, bounds.width, bounds.height, style.floor, 1)
142
+ .setStrokeStyle?.(1, 0x000000);
143
+ drawFloorPattern(scene, bounds, roomStyleKey);
144
+ rect(scene, bounds.x + bounds.width / 2, bounds.y + HEADER_HEIGHT / 2, bounds.width, HEADER_HEIGHT, style.wallTop, 2);
145
+ rect(scene, bounds.x + bounds.width / 2, bounds.y + HEADER_HEIGHT + 1, bounds.width, 2, style.accent, 2);
146
+ const titleX = bounds.x + HEADER_TITLE_PADDING_X;
147
+ const titleY = bounds.y + HEADER_HEIGHT / 2 + HEADER_TITLE_OFFSET_Y;
148
+ if (options.domOverlayRoot) {
149
+ createRoomTitleOverlay(scene, options.domOverlayRoot, titleX, titleY, room.name);
150
+ } else {
151
+ scene.add
152
+ .text(titleX, titleY, room.name, crispTextStyle({
153
+ color: room.theme.titleTextColor ?? "#ffffff",
154
+ fontSize: "14px",
155
+ fontFamily: "sans-serif",
156
+ fontStyle: "700",
157
+ }))
158
+ .setOrigin?.(0, 0.5)
159
+ .setDepth?.(officeDepth(3));
160
+ }
161
+
162
+ drawRoomEnvironment(scene, bounds, roomStyleKey, style);
163
+ const objects = room.objects.length > 0 || !room.type
164
+ ? room.objects
165
+ : createTownOfficeRoomObjects({
166
+ roomId: room.id,
167
+ roomType: room.type,
168
+ width: bounds.width,
169
+ height: bounds.height,
170
+ });
171
+ for (const object of objects) {
172
+ drawTownOfficeObject(scene, bounds, object);
173
+ }
174
+ }
175
+ }
176
+
177
+ function createRoomTitleOverlay(
178
+ scene: TownOfficeSceneLike,
179
+ root: TownOfficeDomOverlayRoot,
180
+ worldX: number,
181
+ worldY: number,
182
+ title: string,
183
+ ): void {
184
+ const element = root.ownerDocument?.createElement("div") ?? globalThis.document?.createElement?.("div");
185
+ if (!element) {
186
+ throw new Error("Town office DOM overlay requires document.createElement");
187
+ }
188
+
189
+ element.className = "room-dom-title";
190
+ element.textContent = title;
191
+ element.style.position = "absolute";
192
+ element.style.left = "0";
193
+ element.style.top = "0";
194
+ element.style.pointerEvents = "none";
195
+ element.style.transformOrigin = "left center";
196
+ root.appendChild?.(element as TownOfficeDomOverlayElement);
197
+
198
+ const update = () => {
199
+ positionDomElement(scene, root, element as TownOfficeDomOverlayElement, worldX, worldY);
200
+ };
201
+ scene.events?.on?.("postupdate", update);
202
+ update();
203
+ }
204
+
205
+ function positionDomElement(
206
+ scene: TownOfficeSceneLike,
207
+ root: TownOfficeDomOverlayRoot,
208
+ element: TownOfficeDomOverlayElement,
209
+ worldX: number,
210
+ worldY: number,
211
+ ): void {
212
+ const camera = scene.cameras?.main;
213
+ if (!camera) {
214
+ return;
215
+ }
216
+
217
+ const scaleX = camera.width && root.clientWidth ? root.clientWidth / camera.width : 1;
218
+ const scaleY = camera.height && root.clientHeight ? root.clientHeight / camera.height : 1;
219
+ const x = Math.round((worldX - camera.scrollX) * camera.zoom * scaleX);
220
+ const y = Math.round((worldY - camera.scrollY) * camera.zoom * scaleY);
221
+ const scale = camera.zoom * Math.min(scaleX, scaleY);
222
+ element.style.transform = `translate3d(${x}px, ${y}px, 0) translateY(-50%) scale(${formatDomScale(scale)})`;
223
+ }
224
+
225
+ function formatDomScale(scale: number): string {
226
+ return Number.isInteger(scale) ? String(scale) : scale.toFixed(3).replace(/0+$/, "").replace(/\.$/, "");
227
+ }
228
+
229
+ function drawBackgroundPattern(scene: TownOfficeSceneLike, width: number, height: number): void {
230
+ for (let y = 0; y < height; y += 40) {
231
+ for (let x = 0; x < width; x += 40) {
232
+ const color = (x / 40 + y / 40) % 2 === 0 ? "#e4e8ec" : "#e8ecf0";
233
+ rect(scene, x + 20, y + 20, 40, 40, color, 0);
234
+ }
235
+ }
236
+ }
237
+
238
+ function drawFloorPattern(scene: TownOfficeSceneLike, bounds: OfficeRoomBounds, roomType: string): void {
239
+ const step = roomType === "cafeteria" || roomType === "fitness" ? 16 : 24;
240
+ const lineColor = roomType === "cafeteria" || roomType === "fitness" ? "#d0ccc4" : "#c8c0b4";
241
+ for (let x = bounds.x + step; x < bounds.x + bounds.width; x += step) {
242
+ rect(scene, x, bounds.y + bounds.height / 2, FLOOR_GRID_LINE_WIDTH, bounds.height - HEADER_HEIGHT, lineColor, 1);
243
+ }
244
+ for (let y = bounds.y + HEADER_HEIGHT + step; y < bounds.y + bounds.height; y += step) {
245
+ rect(scene, bounds.x + bounds.width / 2, y, bounds.width, FLOOR_GRID_LINE_WIDTH, lineColor, 1);
246
+ }
247
+ }
248
+
249
+ function drawTownOfficeObject(scene: TownOfficeSceneLike, bounds: OfficeRoomBounds, object: OfficeBuildingObjectDefinition): void {
250
+ const x = bounds.x + object.x;
251
+ const y = bounds.y + object.y;
252
+ const scale = object.scale ?? 1;
253
+
254
+ switch (object.frame) {
255
+ case "desk-single":
256
+ case "desk-long":
257
+ drawDesk(scene, x, y, scale, !object.id.includes("training-desk-single"));
258
+ break;
259
+ case "chair-office":
260
+ drawChair(scene, x, y, scale);
261
+ break;
262
+ case "whiteboard":
263
+ case "wall-art":
264
+ drawWhiteboard(scene, x - 42 * scale, y - 14 * scale, 84 * scale, 28 * scale);
265
+ break;
266
+ case "plant-small":
267
+ case "plant-large":
268
+ drawPlant(scene, x, y, scale * (object.frame === "plant-large" ? 1.4 : 1));
269
+ break;
270
+ case "monitor":
271
+ drawMonitor(scene, x, y, scale);
272
+ break;
273
+ case "sofa-long":
274
+ drawSofa(scene, x, y, scale);
275
+ break;
276
+ case "laptop":
277
+ drawLaptop(scene, x, y, scale);
278
+ break;
279
+ case "bookshelf":
280
+ drawBookshelf(scene, x, y, scale);
281
+ break;
282
+ case "filing-cabinet":
283
+ drawCabinet(scene, x, y, scale);
284
+ break;
285
+ case "meeting-table":
286
+ drawTable(scene, x, y, 72 * scale, 34 * scale);
287
+ break;
288
+ case "coffee-machine":
289
+ drawCoffeeMachine(scene, x, y, scale);
290
+ break;
291
+ case "water-dispenser":
292
+ case "locker":
293
+ drawMachine(scene, x, y, scale);
294
+ break;
295
+ case "vending-machine":
296
+ drawVendingMachine(scene, x, y, scale);
297
+ break;
298
+ case "phone":
299
+ drawPhone(scene, x, y, scale);
300
+ break;
301
+ case "schedule-board":
302
+ drawScheduleBoard(scene, x, y, scale);
303
+ break;
304
+ case "trash-can":
305
+ drawTrashCan(scene, x, y, scale);
306
+ break;
307
+ case "bench":
308
+ drawBench(scene, x, y, scale);
309
+ break;
310
+ case "flower-pot":
311
+ drawFlowerPot(scene, x, y, scale, "#e85080");
312
+ break;
313
+ case "clock":
314
+ drawClock(scene, x, y, scale);
315
+ break;
316
+ case "bulletin-board":
317
+ drawBulletinBoard(scene, x, y, scale);
318
+ break;
319
+ case "fire-extinguisher":
320
+ drawFireExtinguisher(scene, x, y, scale);
321
+ break;
322
+ case "rug-mat":
323
+ drawRugMat(scene, x, y, scale);
324
+ break;
325
+ case "window":
326
+ drawWindow(scene, x, y, scale);
327
+ break;
328
+ case "air-conditioner":
329
+ drawAirConditioner(scene, x, y, scale);
330
+ break;
331
+ case "exit-sign":
332
+ drawExitSign(scene, x, y, scale);
333
+ break;
334
+ case "picture":
335
+ drawPicture(scene, x, y, scale, "#5588bb");
336
+ break;
337
+ case "printer":
338
+ drawPrinter(scene, x, y, scale);
339
+ break;
340
+ case "door":
341
+ drawDoor(scene, x, y, scale);
342
+ break;
343
+ case "light-switch":
344
+ drawLightSwitch(scene, x, y, scale);
345
+ break;
346
+ case "socket":
347
+ drawSocket(scene, x, y, scale);
348
+ break;
349
+ case "server-rack":
350
+ drawServerRack(scene, x, y, scale);
351
+ break;
352
+ case "tv":
353
+ drawTV(scene, x, y, scale);
354
+ break;
355
+ case "monitor-wall":
356
+ drawMonitorWall(scene, x, y, scale);
357
+ break;
358
+ case "ticker-board":
359
+ drawTickerBoard(scene, x, y, scale);
360
+ break;
361
+ case "test-rack":
362
+ drawTestRack(scene, x, y, scale);
363
+ break;
364
+ case "cert-badge":
365
+ drawCertBadge(scene, x, y, scale);
366
+ break;
367
+ case "henan-map":
368
+ drawHenanMap(scene, x, y, scale);
369
+ break;
370
+ case "battery":
371
+ drawBattery(scene, x, y, scale);
372
+ break;
373
+ case "solar-panel":
374
+ drawSolarPanel(scene, x, y, scale);
375
+ break;
376
+ case "ev-charger":
377
+ drawEVCharger(scene, x, y, scale);
378
+ break;
379
+ case "megaphone":
380
+ drawMegaphone(scene, x, y, scale);
381
+ break;
382
+ case "chart-board":
383
+ drawChartBoard(scene, x, y, scale);
384
+ break;
385
+ case "red-banner":
386
+ drawRedBanner(scene, x, y, scale);
387
+ break;
388
+ case "beaker":
389
+ drawBeaker(scene, x, y, scale);
390
+ break;
391
+ case "ping-pong-table":
392
+ drawPingPongTable(scene, x, y, scale);
393
+ break;
394
+ case "podium":
395
+ drawPodium(scene, x, y, scale);
396
+ break;
397
+ case "party-flag":
398
+ drawPartyFlag(scene, x, y, scale);
399
+ break;
400
+ case "wind-turbine":
401
+ drawWindTurbine(scene, x, y, scale);
402
+ break;
403
+ case "transmission-tower":
404
+ drawTransmissionTower(scene, x, y, scale);
405
+ break;
406
+ case "tree":
407
+ drawTree(scene, x, y, scale);
408
+ break;
409
+ case "gazebo":
410
+ drawGazebo(scene, x, y, scale);
411
+ break;
412
+ case "bicycle-rack":
413
+ drawBicycleRack(scene, x, y, scale);
414
+ break;
415
+ case "big-screen":
416
+ drawBigScreen(scene, x, y, scale);
417
+ break;
418
+ case "kpi-card":
419
+ drawKpiCard(scene, x, y, scale);
420
+ break;
421
+ case "seal":
422
+ drawSeal(scene, x, y, scale);
423
+ break;
424
+ }
425
+ }
426
+
427
+ function getBuildingCanvasSize(layout: OfficeBuildingLayout): { width: number; height: number } {
428
+ return measureOfficeBuildingLayoutSize(layout);
429
+ }
@@ -0,0 +1,67 @@
1
+ import type { AgentGameAssetManifest } from "../core/assets.js";
2
+ import type { AgentGameCommand } from "../core/commands.js";
3
+ import type { OfficeBuildingLayout } from "../core/office-building-layout.js";
4
+ import type { OfficeLayoutDefinition, OfficeObjectAtlasDefinition } from "../core/office-layout.js";
5
+ import type { AgentGameSceneDefinition } from "../core/scene.js";
6
+ import type { AgentGameSnapshot } from "../core/state.js";
7
+ import type { AgentGameStore } from "../core/agent-game-store.js";
8
+ import type { AgentGameCameraControlsController, AgentGameCameraControlsOptions } from "./camera-controls.js";
9
+ import type { AgentGameDebugOverlays } from "./debug-overlay.js";
10
+ import type { AgentGameRendererConfig, AgentGameSceneRendererRegistry } from "./scene-renderer.js";
11
+ import type { AgentGameViewportOptions } from "./viewport.js";
12
+
13
+ export type PhaserGameLike = {
14
+ destroy(removeCanvas?: boolean): void;
15
+ };
16
+
17
+ export type AgentGamePhaserOptions = {
18
+ scene: AgentGameSceneDefinition;
19
+ assets: AgentGameAssetManifest;
20
+ width?: number;
21
+ height?: number;
22
+ viewport?: AgentGameViewportOptions;
23
+ resolution?: number;
24
+ backgroundColor?: string;
25
+ backgroundImage?: {
26
+ key: string;
27
+ url: string;
28
+ width: number;
29
+ height: number;
30
+ };
31
+ officeLayout?: {
32
+ atlasKey: string;
33
+ imageUrl: string;
34
+ atlasUrl: string;
35
+ definition: OfficeLayoutDefinition;
36
+ atlas: OfficeObjectAtlasDefinition;
37
+ };
38
+ officeBuilding?: {
39
+ atlasKey: string;
40
+ imageUrl: string;
41
+ atlasUrl: string;
42
+ layout: OfficeBuildingLayout;
43
+ atlas: OfficeObjectAtlasDefinition;
44
+ };
45
+ townOfficeBuilding?: {
46
+ layout: OfficeBuildingLayout;
47
+ };
48
+ debugOverlays?: AgentGameDebugOverlays;
49
+ showAnchorDebug?: boolean;
50
+ cameraControls?: AgentGameCameraControlsOptions;
51
+ domOverlay?: boolean;
52
+ movementDurationMs?: number;
53
+ movementSpeedPxPerSecond?: number;
54
+ movementMinDurationMs?: number;
55
+ movementMaxDurationMs?: number;
56
+ store?: AgentGameStore;
57
+ renderer?: AgentGameRendererConfig;
58
+ rendererRegistry?: AgentGameSceneRendererRegistry;
59
+ createPhaserGame?: (config: unknown) => PhaserGameLike;
60
+ };
61
+
62
+ export type AgentGameController = {
63
+ dispatch(command: AgentGameCommand): AgentGameSnapshot;
64
+ getSnapshot(): AgentGameSnapshot;
65
+ getCameraControls(): AgentGameCameraControlsController | null;
66
+ destroy(): void;
67
+ };
@@ -0,0 +1,88 @@
1
+ export type AgentGameViewportRatio = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+
6
+ export type AgentGameViewportOptions = {
7
+ ratio: AgentGameViewportRatio;
8
+ baseWidth?: number;
9
+ };
10
+
11
+ export type AgentGameViewportSizeInput = {
12
+ width?: number;
13
+ height?: number;
14
+ viewport?: AgentGameViewportOptions;
15
+ };
16
+
17
+ export type AgentGameViewportSize = {
18
+ width: number;
19
+ height: number;
20
+ };
21
+
22
+ export function resolveAgentGameViewportSize(input: AgentGameViewportSizeInput): AgentGameViewportSize {
23
+ if (!isPlainObject(input)) {
24
+ throw new Error("Agent game viewport input must be an object");
25
+ }
26
+
27
+ validateExplicitSize(input.width, "width");
28
+ validateExplicitSize(input.height, "height");
29
+
30
+ if ((input.width === undefined) !== (input.height === undefined)) {
31
+ throw new Error("Agent game viewport requires both width and height when using explicit sizing");
32
+ }
33
+
34
+ if (input.viewport !== undefined) {
35
+ validateViewport(input.viewport);
36
+ }
37
+
38
+ if (input.width !== undefined && input.height !== undefined) {
39
+ return { width: input.width, height: input.height };
40
+ }
41
+
42
+ if (input.viewport === undefined) {
43
+ return { width: 800, height: 480 };
44
+ }
45
+
46
+ const width = input.viewport.baseWidth ?? 800;
47
+ const height = Math.round((width * input.viewport.ratio.height) / input.viewport.ratio.width);
48
+ if (height <= 0) {
49
+ throw new Error("Agent game viewport resolved height must be a positive integer");
50
+ }
51
+
52
+ return { width, height };
53
+ }
54
+
55
+ function validateViewport(viewport: AgentGameViewportOptions): void {
56
+ if (!isPlainObject(viewport)) {
57
+ throw new Error("Agent game viewport must be an object");
58
+ }
59
+
60
+ if (!viewport.ratio) {
61
+ throw new Error("Agent game viewport.ratio is required");
62
+ }
63
+
64
+ validatePositiveInteger(viewport.ratio.width, "viewport.ratio.width");
65
+ validatePositiveInteger(viewport.ratio.height, "viewport.ratio.height");
66
+
67
+ if (viewport.baseWidth !== undefined) {
68
+ validatePositiveInteger(viewport.baseWidth, "viewport.baseWidth");
69
+ }
70
+ }
71
+
72
+ function validateExplicitSize(value: number | undefined, field: string): void {
73
+ if (value === undefined) {
74
+ return;
75
+ }
76
+
77
+ validatePositiveInteger(value, field);
78
+ }
79
+
80
+ function validatePositiveInteger(value: number, field: string): void {
81
+ if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value) || value <= 0) {
82
+ throw new Error(`Agent game ${field} must be a positive integer`);
83
+ }
84
+ }
85
+
86
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
87
+ return typeof value === "object" && value !== null;
88
+ }