@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,253 @@
1
+ import * as THREE from "three";
2
+ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
3
+
4
+ import type {
5
+ AgentGameOfficeAgent,
6
+ AgentGameOfficeRendererController,
7
+ AgentGameOfficeResolvedOptions,
8
+ AgentGameOfficeSnapshot,
9
+ } from "../../core/types";
10
+ import { applyAgentPose, updateAgentMotion } from "./agent-animation";
11
+ import { createAgentBodyInstancedLayer } from "./agent-body-instancing";
12
+ import { resolveAgentFacingTarget, resolveAgentPosition } from "./agent-layout";
13
+ import {
14
+ type AgentLabelRecord,
15
+ createAgentLabel,
16
+ removeAgentLabel,
17
+ updateAgentLabel,
18
+ updateAgentLabelPosition,
19
+ } from "./agent-label";
20
+ import { type AgentMeshParts, createAgentMesh } from "./agent-mesh";
21
+ import { addLights, buildOfficeScene, disposeObject } from "./scene";
22
+
23
+ type AgentMeshRecord = {
24
+ id: string;
25
+ agent: AgentGameOfficeAgent;
26
+ renderIndex: number;
27
+ mesh: AgentMeshParts;
28
+ label: AgentLabelRecord;
29
+ target: THREE.Vector3;
30
+ };
31
+
32
+ export const THREE_RENDERER_PIXEL_RATIO_LIMIT = 1.5;
33
+ export const THREE_RENDERER_TARGET_FPS = 30;
34
+
35
+ export function createOfficeMouseButtons(): OrbitControls["mouseButtons"] {
36
+ return {
37
+ LEFT: THREE.MOUSE.PAN,
38
+ MIDDLE: THREE.MOUSE.DOLLY,
39
+ RIGHT: THREE.MOUSE.ROTATE,
40
+ };
41
+ }
42
+
43
+ export function mountThreeAgentGameOffice(
44
+ container: HTMLElement,
45
+ initialSnapshot: AgentGameOfficeSnapshot,
46
+ _options: AgentGameOfficeResolvedOptions,
47
+ ): AgentGameOfficeRendererController {
48
+ const width = Math.max(container.clientWidth || 960, 320);
49
+ const height = Math.max(container.clientHeight || 540, 240);
50
+ const scene = new THREE.Scene();
51
+ scene.background = new THREE.Color(0xf7f8fb);
52
+
53
+ const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000);
54
+ camera.position.set(30, 24, 28);
55
+ camera.lookAt(10, 0, 0);
56
+
57
+ const renderer = new THREE.WebGLRenderer({
58
+ antialias: true,
59
+ alpha: false,
60
+ powerPreference: "high-performance",
61
+ });
62
+ renderer.setSize(width, height);
63
+ renderer.setPixelRatio(Math.min(globalThis.devicePixelRatio || 1, THREE_RENDERER_PIXEL_RATIO_LIMIT));
64
+ renderer.shadowMap.enabled = false;
65
+ renderer.domElement.style.width = "100%";
66
+ renderer.domElement.style.height = "100%";
67
+ renderer.domElement.style.display = "block";
68
+ renderer.domElement.style.cursor = "grab";
69
+
70
+ function setGrabbingCursor() {
71
+ renderer.domElement.style.cursor = "grabbing";
72
+ }
73
+
74
+ function setGrabCursor() {
75
+ renderer.domElement.style.cursor = "grab";
76
+ }
77
+
78
+ renderer.domElement.addEventListener("pointerdown", setGrabbingCursor);
79
+ renderer.domElement.addEventListener("pointerup", setGrabCursor);
80
+ renderer.domElement.addEventListener("pointerleave", setGrabCursor);
81
+
82
+ let labelsDirty = true;
83
+ const controls = new OrbitControls(camera, renderer.domElement);
84
+ controls.target.set(10, 0, 0);
85
+ controls.enableDamping = true;
86
+ controls.dampingFactor = 0.08;
87
+ controls.enablePan = true;
88
+ controls.enableRotate = true;
89
+ controls.enableZoom = true;
90
+ controls.mouseButtons = createOfficeMouseButtons();
91
+ controls.minDistance = 8;
92
+ controls.maxDistance = 70;
93
+ controls.maxPolarAngle = Math.PI * 0.48;
94
+ controls.addEventListener("change", () => {
95
+ labelsDirty = true;
96
+ });
97
+ controls.update();
98
+
99
+ const root = document.createElement("div");
100
+ root.style.position = "relative";
101
+ root.style.width = "100%";
102
+ root.style.height = "100%";
103
+ root.style.overflow = "hidden";
104
+ root.appendChild(renderer.domElement);
105
+
106
+ const overlay = document.createElement("div");
107
+ overlay.style.position = "absolute";
108
+ overlay.style.inset = "0";
109
+ overlay.style.pointerEvents = "none";
110
+ root.appendChild(overlay);
111
+ container.appendChild(root);
112
+
113
+ addLights(scene);
114
+ buildOfficeScene(scene);
115
+ const agentBodyLayer = createAgentBodyInstancedLayer(scene);
116
+
117
+ const agents = new Map<string, AgentMeshRecord>();
118
+ let frameId = 0;
119
+ let disposed = false;
120
+ let previousFrameTime = performance.now();
121
+ let previousRenderTime = previousFrameTime;
122
+
123
+ function update(snapshot: AgentGameOfficeSnapshot) {
124
+ const liveIds = new Set(snapshot.agents.map((agent) => agent.id));
125
+ for (const [agentId, record] of agents.entries()) {
126
+ if (!liveIds.has(agentId)) {
127
+ scene.remove(record.mesh.group);
128
+ removeAgentLabel(record.label);
129
+ agents.delete(agentId);
130
+ }
131
+ }
132
+
133
+ snapshot.agents.forEach((agent, index) => {
134
+ const record = agents.get(agent.id) ?? createAgentRecord(agent, index);
135
+ agents.set(agent.id, record);
136
+ const target = resolveAgentPosition(agent, index, snapshot.agents.length);
137
+ record.agent = agent;
138
+ record.renderIndex = index;
139
+ record.target.copy(target);
140
+ record.mesh.group.visible = agent.sceneState !== "offline";
141
+ updateAgentLabel(record.label, agent);
142
+ updateAgentLabelPosition(record.label, record.mesh.group.position, camera, renderer.domElement);
143
+ });
144
+ labelsDirty = true;
145
+ }
146
+
147
+ function createAgentRecord(agent: AgentGameOfficeAgent, index: number): AgentMeshRecord {
148
+ const mesh = createAgentMesh(agent, index);
149
+ const position = resolveAgentPosition(agent, index, initialSnapshot.agents.length);
150
+ mesh.group.position.copy(position);
151
+ scene.add(mesh.group);
152
+
153
+ const label = createAgentLabel(overlay);
154
+
155
+ return { id: agent.id, agent, renderIndex: index, mesh, label, target: position.clone() };
156
+ }
157
+
158
+ function animate() {
159
+ if (disposed) {
160
+ return;
161
+ }
162
+ const now = performance.now();
163
+ const targetFrameIntervalMs = 1000 / THREE_RENDERER_TARGET_FPS;
164
+ if (now - previousRenderTime < targetFrameIntervalMs) {
165
+ frameId = requestAnimationFrame(animate);
166
+ return;
167
+ }
168
+ const deltaSeconds = Math.min((now - previousFrameTime) / 1000, 0.05);
169
+ previousFrameTime = now;
170
+ previousRenderTime = now;
171
+ const elapsedSeconds = now / 1000;
172
+
173
+ const controlsChanged = controls.update();
174
+ const shouldUpdateLabels = labelsDirty || controlsChanged;
175
+
176
+ for (const record of agents.values()) {
177
+ const moving = record.agent.sceneState !== "offline" && updateAgentMotion({
178
+ mesh: record.mesh,
179
+ agent: record.agent,
180
+ target: record.target,
181
+ facingTarget: resolveAgentFacingTarget(record.agent),
182
+ elapsedSeconds,
183
+ deltaSeconds,
184
+ });
185
+ record.mesh.group.visible = record.agent.sceneState !== "offline";
186
+ applyAgentPose(record.mesh, record.agent, moving, elapsedSeconds);
187
+ if (moving || shouldUpdateLabels) {
188
+ updateAgentLabelPosition(record.label, record.mesh.group.position, camera, renderer.domElement);
189
+ }
190
+ }
191
+ agentBodyLayer.update(Array.from(agents.values()).map((record) => ({
192
+ mesh: record.mesh,
193
+ renderIndex: record.renderIndex,
194
+ visible: record.agent.sceneState !== "offline",
195
+ })));
196
+ renderer.render(scene, camera);
197
+ labelsDirty = false;
198
+ frameId = requestAnimationFrame(animate);
199
+ }
200
+
201
+ const resizeObserver = new ResizeObserver(() => {
202
+ const nextWidth = Math.max(container.clientWidth || width, 320);
203
+ const nextHeight = Math.max(container.clientHeight || height, 240);
204
+ camera.aspect = nextWidth / nextHeight;
205
+ camera.updateProjectionMatrix();
206
+ renderer.setSize(nextWidth, nextHeight);
207
+ labelsDirty = true;
208
+ });
209
+ resizeObserver.observe(container);
210
+
211
+ update(initialSnapshot);
212
+ animate();
213
+
214
+ return {
215
+ update,
216
+ focusAgent(agentId) {
217
+ if (!agentId) {
218
+ camera.position.set(30, 24, 28);
219
+ controls.target.set(10, 0, 0);
220
+ controls.update();
221
+ labelsDirty = true;
222
+ return;
223
+ }
224
+ const record = agents.get(agentId);
225
+ if (!record) {
226
+ return;
227
+ }
228
+ const target = record.mesh.group.position;
229
+ camera.position.set(target.x + 5, 8, target.z + 7);
230
+ controls.target.set(target.x, 1, target.z);
231
+ controls.update();
232
+ labelsDirty = true;
233
+ },
234
+ destroy() {
235
+ disposed = true;
236
+ cancelAnimationFrame(frameId);
237
+ resizeObserver.disconnect();
238
+ controls.dispose();
239
+ renderer.domElement.removeEventListener("pointerdown", setGrabbingCursor);
240
+ renderer.domElement.removeEventListener("pointerup", setGrabCursor);
241
+ renderer.domElement.removeEventListener("pointerleave", setGrabCursor);
242
+ agents.forEach((record) => {
243
+ removeAgentLabel(record.label);
244
+ disposeObject(record.mesh.group);
245
+ });
246
+ agents.clear();
247
+ agentBodyLayer.dispose();
248
+ disposeObject(scene);
249
+ renderer.dispose();
250
+ root.remove();
251
+ },
252
+ };
253
+ }