@agent-os-lab/agent-game-sdk 0.1.10 → 0.1.11

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.
package/README.md CHANGED
@@ -33,6 +33,7 @@ const view = await mountAgentGameOffice(container, {
33
33
  });
34
34
 
35
35
  view.focusAgent("agent-1");
36
+ view.resetCamera();
36
37
  view.refreshAgents();
37
38
  view.destroy();
38
39
  ```
@@ -57,6 +58,9 @@ export function Office() {
57
58
  <button type="button" onClick={() => view?.refreshAgents()}>
58
59
  Refresh agents
59
60
  </button>
61
+ <button type="button" onClick={() => view?.resetCamera()}>
62
+ Reset view
63
+ </button>
60
64
  <AgentGameOfficeView
61
65
  renderer="three"
62
66
  office={{
package/USAGE.md CHANGED
@@ -203,6 +203,7 @@ const view = await mountAgentGameOffice(container, {
203
203
  });
204
204
 
205
205
  view.focusAgent("agent-1");
206
+ view.resetCamera();
206
207
  view.refreshAgents();
207
208
  view.destroy();
208
209
  ```
@@ -221,6 +222,8 @@ The layout packs up to three rooms per row, then starts another row. `capacity`
221
222
 
222
223
  The SDK keeps runtime statuses simple. `idle` and `resting` agents are locally distributed across ambient anchors for lounge, pantry, gym, and reading areas. `entertaining` stays biased toward the review area. If a configured room does not provide the selected ambient zone, the SDK falls back to another available non-offstage anchor.
223
224
 
225
+ `resetCamera` returns the office view to its initial camera position, rotation, target, zoom, and follow state. It is intended for UI controls such as a reset-view button after a user pans, rotates, zooms, or follows an Agent.
226
+
224
227
  `refreshAgents` asks Agent Game Runtime to refresh the tenant roster through the existing bootstrap flow. It is intended for UI controls such as a refresh button after an Agent was created or deleted in another surface.
225
228
 
226
229
  ## React Office View
@@ -245,6 +248,9 @@ export function Office() {
245
248
  <button type="button" onClick={() => view?.refreshAgents()}>
246
249
  Refresh agents
247
250
  </button>
251
+ <button type="button" onClick={() => view?.resetCamera()}>
252
+ Reset view
253
+ </button>
248
254
  <AgentGameOfficeView
249
255
  renderer="three"
250
256
  office={{
@@ -308,6 +314,8 @@ view.updateAgents([
308
314
 
309
315
  `updateAgents` only mutates SDK-managed snapshot sources. Runtime and custom sources own their own updates.
310
316
 
317
+ `resetCamera` is source-independent and can be called for snapshot, runtime, and custom sources.
318
+
311
319
  ## Custom Source
312
320
 
313
321
  Use `source.type: "custom"` when another state manager already projects runtime state into office snapshots.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-os-lab/agent-game-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -87,6 +87,7 @@ export type AgentGameOfficeSourceInput =
87
87
 
88
88
  export type AgentGameOfficeRendererController = {
89
89
  update(snapshot: AgentGameOfficeSnapshot): void;
90
+ resetCamera?(): void;
90
91
  focusAgent?(agentId: string | null): void;
91
92
  focusFloor?(floorId: string | null): void;
92
93
  getFocusedFloor?(): string | null;
@@ -140,6 +141,7 @@ export type AgentGameOfficeResolvedOptions = Omit<AgentGameOfficeMountOptions, "
140
141
 
141
142
  export type AgentGameOfficeController = {
142
143
  updateAgents(agents: AgentPresence[]): void;
144
+ resetCamera(): void;
143
145
  focusAgent(agentId: string | null): void;
144
146
  focusFloor(floorId: string | null): void;
145
147
  getFocusedFloor(): string | null;
@@ -705,7 +705,7 @@ function addOfficePreset(layout: MutableResolvedLayout, room: ResolvedOfficeRoom
705
705
  addComponent(layout, room.id, "desk", `desk-${index + 1}`, slot.desk.x, slot.desk.z);
706
706
  addComponent(layout, room.id, "officeChair", `desk-chair-${index + 1}`, slot.seat.x, slot.seat.z, { rotation: slot.rotation });
707
707
  addComponent(layout, room.id, "monitor", `monitor-${index + 1}`, slot.desk.x, slot.desk.z - 0.4);
708
- addSeat(layout, room.id, `desk-${index + 1}`, "desk", slot.seat.x, slot.seat.z, { x: scaleOfficeX(-7), z: scaleOfficeZ(-5) });
708
+ addSeat(layout, room.id, `desk-${index + 1}`, "desk", slot.seat.x, slot.seat.z, { x: slot.desk.x, z: slot.desk.z - 0.4 });
709
709
  });
710
710
 
711
711
  MEETING_ROOMS.forEach((meetingRoom, roomIndex) => {
@@ -72,6 +72,9 @@ export async function mountAgentGameOffice(
72
72
  mutableSource.updateAgents(agents);
73
73
  }
74
74
  },
75
+ resetCamera() {
76
+ controller?.resetCamera?.();
77
+ },
75
78
  focusAgent(agentId) {
76
79
  controller?.focusAgent?.(agentId);
77
80
  },
@@ -18,7 +18,7 @@ type AgentBodyBatch = {
18
18
  count: number;
19
19
  };
20
20
 
21
- const geometryCache = new Map<string, THREE.BoxGeometry>();
21
+ const geometryCache = new Map<string, THREE.BufferGeometry>();
22
22
  const materialCache = new Map<string, THREE.MeshLambertMaterial>();
23
23
  const matrixHelper = new THREE.Matrix4();
24
24
 
@@ -70,7 +70,8 @@ export class AgentBodyInstancedLayer {
70
70
  requiredCapacity: number,
71
71
  opacity: number,
72
72
  ): AgentBodyBatch {
73
- const key = `${part.width}:${part.height}:${part.depth}:${part.color}:${opacity}`;
73
+ const shape = part.shape ?? "box";
74
+ const key = `${shape}:${part.width}:${part.height}:${part.depth}:${part.color}:${opacity}`;
74
75
  const existing = this.batches.get(key);
75
76
  if (existing && existing.capacity >= requiredCapacity) {
76
77
  return existing;
@@ -80,7 +81,7 @@ export class AgentBodyInstancedLayer {
80
81
  }
81
82
 
82
83
  const mesh = new THREE.InstancedMesh(
83
- getGeometry(part.width, part.height, part.depth),
84
+ getGeometry(part),
84
85
  getMaterial(part.color, opacity),
85
86
  Math.max(requiredCapacity, 1),
86
87
  );
@@ -100,17 +101,33 @@ export function createAgentBodyInstancedLayer(scene: THREE.Scene): AgentBodyInst
100
101
  return new AgentBodyInstancedLayer(scene);
101
102
  }
102
103
 
103
- function getGeometry(width: number, height: number, depth: number): THREE.BoxGeometry {
104
- const key = `${width}:${height}:${depth}`;
104
+ function getGeometry(part: ReturnType<typeof resolveAgentBodyPartRenderSpecs>[number]): THREE.BufferGeometry {
105
+ const shape = part.shape ?? "box";
106
+ const key = `${shape}:${part.width}:${part.height}:${part.depth}`;
105
107
  const cached = geometryCache.get(key);
106
108
  if (cached) {
107
109
  return cached;
108
110
  }
109
- const geometry = new THREE.BoxGeometry(width, height, depth);
111
+ const geometry = createGeometry(part);
110
112
  geometryCache.set(key, geometry);
111
113
  return geometry;
112
114
  }
113
115
 
116
+ function createGeometry(part: ReturnType<typeof resolveAgentBodyPartRenderSpecs>[number]): THREE.BufferGeometry {
117
+ switch (part.shape) {
118
+ case "frontPlane":
119
+ return new THREE.PlaneGeometry(part.width, part.height);
120
+ case "backPlane": {
121
+ const geometry = new THREE.PlaneGeometry(part.width, part.height);
122
+ geometry.rotateY(Math.PI);
123
+ return geometry;
124
+ }
125
+ case "box":
126
+ case undefined:
127
+ return new THREE.BoxGeometry(part.width, part.height, part.depth);
128
+ }
129
+ }
130
+
114
131
  function getMaterial(color: number, opacity: number): THREE.MeshLambertMaterial {
115
132
  const key = `${color}:${opacity}`;
116
133
  const cached = materialCache.get(key);
@@ -2,6 +2,8 @@ import * as THREE from "three";
2
2
 
3
3
  import type { AgentGameOfficeAgent } from "../../core/types";
4
4
 
5
+ export const AGENT_LABEL_WORLD_Y_OFFSET = 3.05;
6
+
5
7
  export type AgentLabelRecord = {
6
8
  root: HTMLDivElement;
7
9
  title: HTMLDivElement;
@@ -69,7 +71,7 @@ export function updateAgentLabelPosition(
69
71
  viewport: HTMLElement,
70
72
  ): void {
71
73
  const projected = position.clone();
72
- projected.y += 2.2;
74
+ projected.y += AGENT_LABEL_WORLD_Y_OFFSET;
73
75
  projected.project(camera);
74
76
  const x = (projected.x * 0.5 + 0.5) * viewport.clientWidth;
75
77
  const y = (-projected.y * 0.5 + 0.5) * viewport.clientHeight;
@@ -13,6 +13,9 @@ export type AgentMeshParts = {
13
13
  head: THREE.Object3D;
14
14
  leftEye: THREE.Object3D;
15
15
  rightEye: THREE.Object3D;
16
+ mouth: THREE.Object3D;
17
+ chestFront: THREE.Object3D;
18
+ backPanel: THREE.Object3D;
16
19
  hairTop: THREE.Object3D;
17
20
  hairBack: THREE.Object3D;
18
21
  leftArm: THREE.Object3D;
@@ -38,6 +41,9 @@ export type AgentBodyPartKey =
38
41
  | "head"
39
42
  | "leftEye"
40
43
  | "rightEye"
44
+ | "mouth"
45
+ | "chestFront"
46
+ | "backPanel"
41
47
  | "hairTop"
42
48
  | "hairBack"
43
49
  | "leftArm"
@@ -45,6 +51,7 @@ export type AgentBodyPartKey =
45
51
 
46
52
  export type AgentBodyPartRenderSpec = {
47
53
  key: AgentBodyPartKey;
54
+ shape?: "box" | "frontPlane" | "backPlane";
48
55
  width: number;
49
56
  height: number;
50
57
  depth: number;
@@ -66,8 +73,11 @@ export function resolveAgentBodyPartRenderSpecs(index: number): AgentBodyPartRen
66
73
  { key: "leftFoot", width: 0.24, height: 0.12, depth: 0.34, color: 0x111827, x: -0.14, y: 0.06, z: 0.08 },
67
74
  { key: "rightFoot", width: 0.24, height: 0.12, depth: 0.34, color: 0x111827, x: 0.14, y: 0.06, z: 0.08 },
68
75
  { key: "head", width: 0.5, height: 0.48, depth: 0.42, color: skin, x: 0, y: 1.48, z: 0 },
69
- { key: "leftEye", width: 0.06, height: 0.07, depth: 0.035, color: 0x111827, x: -0.11, y: 1.5, z: 0.225 },
70
- { key: "rightEye", width: 0.06, height: 0.07, depth: 0.035, color: 0x111827, x: 0.11, y: 1.5, z: 0.225 },
76
+ { key: "leftEye", shape: "frontPlane", width: 0.06, height: 0.07, depth: 0, color: 0x111827, x: -0.11, y: 1.5, z: 0.235 },
77
+ { key: "rightEye", shape: "frontPlane", width: 0.06, height: 0.07, depth: 0, color: 0x111827, x: 0.11, y: 1.5, z: 0.235 },
78
+ { key: "mouth", shape: "frontPlane", width: 0.16, height: 0.035, depth: 0, color: 0x7f1d1d, x: 0, y: 1.38, z: 0.245 },
79
+ { key: "chestFront", shape: "frontPlane", width: 0.28, height: 0.12, depth: 0, color: 0xe0f2fe, x: 0, y: 1.02, z: 0.185 },
80
+ { key: "backPanel", shape: "backPlane", width: 0.36, height: 0.44, depth: 0, color: 0x0f172a, x: 0, y: 0.92, z: -0.185 },
71
81
  { key: "hairTop", width: 0.54, height: 0.14, depth: 0.46, color: hair, x: 0, y: 1.78, z: -0.01 },
72
82
  { key: "hairBack", width: 0.52, height: 0.28, depth: 0.09, color: hair, x: 0, y: 1.61, z: -0.23 },
73
83
  { key: "leftArm", width: 0.15, height: 0.56, depth: 0.17, color: shirt, x: -0.43, y: 0.88, z: 0 },
@@ -99,6 +109,9 @@ export function createAgentMesh(agent: AgentGameOfficeAgent, index: number): Age
99
109
  head: parts.head,
100
110
  leftEye: parts.leftEye,
101
111
  rightEye: parts.rightEye,
112
+ mouth: parts.mouth,
113
+ chestFront: parts.chestFront,
114
+ backPanel: parts.backPanel,
102
115
  hairTop: parts.hairTop,
103
116
  hairBack: parts.hairBack,
104
117
  leftArm: parts.leftArm,
@@ -58,7 +58,7 @@ const THREE_RENDERER_CAMERA_PADDING = 1.16;
58
58
  const OFFICE_CAMERA_MIN_BOUNDS_HEIGHT = 3.2;
59
59
  const WORKING_FOLLOW_RESET_DELAY_MS = 30_000;
60
60
  const WORK_EXIT_WAIT_MS = 30_000;
61
- const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(9, 12, 12);
61
+ const WORKING_FOLLOW_CAMERA_OFFSET = new THREE.Vector3(14, 18, 20);
62
62
  const OFFICE_CAMERA_TRANSITION_DURATION_MS = 600;
63
63
  const OFFICE_CAMERA_UP = new THREE.Vector3(0, 1, 0);
64
64
 
@@ -332,7 +332,7 @@ export function mountThreeAgentGameOffice(
332
332
  mesh: record.mesh,
333
333
  agent: record.agent,
334
334
  target: record.target,
335
- facingTarget: record.routeState.activeRoute ? record.target : resolveAgentFacingTarget(record.agent, _options.officeLayout),
335
+ facingTarget: resolveAgentFacingTarget(record.agent, _options.officeLayout),
336
336
  elapsedSeconds,
337
337
  deltaSeconds,
338
338
  });
@@ -387,6 +387,16 @@ export function mountThreeAgentGameOffice(
387
387
 
388
388
  return {
389
389
  update,
390
+ resetCamera() {
391
+ workingFollowState = {
392
+ ...workingFollowState,
393
+ selectedAgentId: null,
394
+ resetAtMs: null,
395
+ };
396
+ camera.zoom = 1;
397
+ camera.updateProjectionMatrix();
398
+ transitionOfficeCameraTo(createInitialOfficeCameraView(_options.officeLayout, camera.aspect, camera.fov));
399
+ },
390
400
  focusAgent(agentId) {
391
401
  if (!agentId) {
392
402
  transitionOfficeCameraTo(createInitialOfficeCameraView(_options.officeLayout, camera.aspect, camera.fov));