@agent-os-lab/agent-game-sdk 0.1.3 → 0.1.5

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.
@@ -3,6 +3,7 @@ import {
3
3
  resolveOfficeSource,
4
4
  } from "./core/source";
5
5
  import { mergeAgentPresence, type GameRuntimeSubscription } from "../runtime-client";
6
+ import { resolveOfficeLayout } from "./layout";
6
7
  import type {
7
8
  AgentGameOfficeMountOptions,
8
9
  AgentGameOfficeRenderer,
@@ -18,6 +19,7 @@ export async function mountAgentGameOffice(
18
19
  options: AgentGameOfficeMountOptions,
19
20
  ): Promise<AgentGameOfficeController> {
20
21
  const renderer = resolveRenderer(options.renderer ?? "three");
22
+ const officeLayout = resolveOfficeLayout(options.office);
21
23
  const mutableSource = options.source.type === "snapshot"
22
24
  ? createSnapshotOfficeSource({ agents: options.source.agents })
23
25
  : null;
@@ -38,6 +40,7 @@ export async function mountAgentGameOffice(
38
40
  controller = await renderer.mount(container, latestSnapshot ?? emptySnapshot(), {
39
41
  ...options,
40
42
  renderer: options.renderer ?? "three",
43
+ officeLayout,
41
44
  });
42
45
 
43
46
  if (options.source.type === "runtime" && runtimeSource) {
@@ -72,6 +75,9 @@ export async function mountAgentGameOffice(
72
75
  focusAgent(agentId) {
73
76
  controller?.focusAgent?.(agentId);
74
77
  },
78
+ refreshAgents() {
79
+ runtimeSubscription?.refresh();
80
+ },
75
81
  destroy() {
76
82
  unsubscribe();
77
83
  runtimeSubscription?.close();
@@ -9,15 +9,27 @@ import type {
9
9
  export type AgentGameOfficeViewProps = AgentGameOfficeMountOptions & {
10
10
  className?: string;
11
11
  style?: React.CSSProperties;
12
+ onReady?: (controller: AgentGameOfficeController) => void;
13
+ onDestroy?: () => void;
12
14
  };
13
15
 
14
16
  export function AgentGameOfficeView({
15
17
  className,
18
+ onDestroy,
19
+ onReady,
16
20
  style,
17
21
  ...options
18
22
  }: AgentGameOfficeViewProps) {
19
23
  const containerRef = useRef<HTMLDivElement | null>(null);
20
24
  const viewRef = useRef<AgentGameOfficeController | null>(null);
25
+ const onDestroyRef = useRef(onDestroy);
26
+ const onReadyRef = useRef(onReady);
27
+ const officeConfigKey = createOfficeConfigKey(options.office);
28
+
29
+ useEffect(() => {
30
+ onReadyRef.current = onReady;
31
+ onDestroyRef.current = onDestroy;
32
+ }, [onDestroy, onReady]);
21
33
 
22
34
  useEffect(() => {
23
35
  const container = containerRef.current;
@@ -32,14 +44,16 @@ export function AgentGameOfficeView({
32
44
  return;
33
45
  }
34
46
  viewRef.current = view;
47
+ onReadyRef.current?.(view);
35
48
  });
36
49
 
37
50
  return () => {
38
51
  disposed = true;
39
52
  viewRef.current?.destroy();
40
53
  viewRef.current = null;
54
+ onDestroyRef.current?.();
41
55
  };
42
- }, [options.renderer, options.source]);
56
+ }, [options.renderer, options.source, officeConfigKey]);
43
57
 
44
58
  useEffect(() => {
45
59
  viewRef.current?.focusAgent(options.focusedAgentId ?? null);
@@ -56,3 +70,13 @@ export function AgentGameOfficeView({
56
70
  },
57
71
  });
58
72
  }
73
+
74
+ function createOfficeConfigKey(office: AgentGameOfficeMountOptions["office"]): string {
75
+ if (!office) {
76
+ return "default";
77
+ }
78
+ return JSON.stringify(office.rooms.map((room) => ({
79
+ type: room.type,
80
+ capacity: room.capacity ?? null,
81
+ })));
82
+ }
@@ -84,6 +84,7 @@ export class AgentBodyInstancedLayer {
84
84
  );
85
85
  mesh.castShadow = false;
86
86
  mesh.receiveShadow = false;
87
+ mesh.frustumCulled = false;
87
88
  mesh.name = `agentBody:${key}`;
88
89
  this.scene.add(mesh);
89
90
 
@@ -85,6 +85,7 @@ export class AgentEffectInstancedLayer {
85
85
  );
86
86
  mesh.castShadow = false;
87
87
  mesh.receiveShadow = false;
88
+ mesh.frustumCulled = false;
88
89
  mesh.name = `agentEffect:${key}`;
89
90
  this.scene.add(mesh);
90
91
 
@@ -2,71 +2,27 @@ import * as THREE from "three";
2
2
 
3
3
  import type { AgentGameOfficeAgent } from "../../core/types";
4
4
  import {
5
- DESK_SEATS,
6
- LARGE_MEETING_X,
7
- LARGE_MEETING_Z,
8
- LOUNGE_CENTER,
9
- MEETING_SEATS,
10
- OFFICE_LAYOUT_SCALE,
11
- scaleOfficeX,
12
- scaleOfficeZ,
13
- } from "./scene";
5
+ resolveOfficeAgentAnchor,
6
+ type ResolvedOfficeLayout,
7
+ } from "../../layout";
14
8
 
15
9
  export function resolveAgentPosition(
16
10
  agent: AgentGameOfficeAgent,
17
11
  index: number,
18
12
  totalAgents: number,
13
+ layout: ResolvedOfficeLayout,
19
14
  ): THREE.Vector3 {
20
15
  if (agent.targetPosition) {
21
16
  return new THREE.Vector3(agent.targetPosition.x, 0, agent.targetPosition.z);
22
17
  }
23
- switch (agent.zoneId) {
24
- case "desk":
25
- return new THREE.Vector3(
26
- DESK_SEATS[index % DESK_SEATS.length]!.x,
27
- 0,
28
- DESK_SEATS[index % DESK_SEATS.length]!.z,
29
- );
30
- case "meeting-room":
31
- return new THREE.Vector3(
32
- MEETING_SEATS[index % MEETING_SEATS.length]!.x,
33
- 0,
34
- MEETING_SEATS[index % MEETING_SEATS.length]!.z,
35
- );
36
- case "lounge":
37
- return new THREE.Vector3(
38
- LOUNGE_CENTER.x - 1.7 * OFFICE_LAYOUT_SCALE + (index % 4) * 1.15 * OFFICE_LAYOUT_SCALE,
39
- 0,
40
- LOUNGE_CENTER.z - 0.8 * OFFICE_LAYOUT_SCALE + Math.floor(index / 4) * 0.95 * OFFICE_LAYOUT_SCALE,
41
- );
42
- case "pantry":
43
- return new THREE.Vector3(scaleOfficeX(-9.2), 0, scaleOfficeZ(5.2 + index * 0.4));
44
- case "review-area": {
45
- const angle = (index / Math.max(totalAgents, 1)) * Math.PI * 2;
46
- return new THREE.Vector3(
47
- scaleOfficeX(0.5) + Math.cos(angle) * 2.2 * OFFICE_LAYOUT_SCALE,
48
- 0,
49
- scaleOfficeZ(4) + Math.sin(angle) * 1.4 * OFFICE_LAYOUT_SCALE,
50
- );
51
- }
52
- case "offstage":
53
- return new THREE.Vector3(scaleOfficeX(-13), 0, scaleOfficeZ(8));
54
- }
18
+ void index;
19
+ void totalAgents;
20
+ const anchor = resolveOfficeAgentAnchor(layout, agent);
21
+ return new THREE.Vector3(anchor.position.x, 0, anchor.position.z);
55
22
  }
56
23
 
57
- export function resolveAgentFacingTarget(agent: AgentGameOfficeAgent): THREE.Vector3 {
58
- switch (agent.zoneId) {
59
- case "desk":
60
- return new THREE.Vector3(scaleOfficeX(-7), 0, scaleOfficeZ(-5));
61
- case "meeting-room":
62
- return new THREE.Vector3(LARGE_MEETING_X, 0, LARGE_MEETING_Z);
63
- case "lounge":
64
- return new THREE.Vector3(LOUNGE_CENTER.x, 0, LOUNGE_CENTER.z);
65
- case "review-area":
66
- return new THREE.Vector3(scaleOfficeX(0.5), 0, scaleOfficeZ(3.05));
67
- case "pantry":
68
- return new THREE.Vector3(scaleOfficeX(-9.2), 0, scaleOfficeZ(5.2));
69
- case "offstage":
70
- return new THREE.Vector3(scaleOfficeX(-13), 0, scaleOfficeZ(8));
71
- }
24
+ export function resolveAgentFacingTarget(agent: AgentGameOfficeAgent, layout: ResolvedOfficeLayout): THREE.Vector3 {
25
+ const anchor = resolveOfficeAgentAnchor(layout, agent);
26
+ const facing = anchor.facing ?? anchor.position;
27
+ return new THREE.Vector3(facing.x, 0, facing.z);
72
28
  }
@@ -32,6 +32,8 @@ type AgentMeshRecord = {
32
32
 
33
33
  export const THREE_RENDERER_PIXEL_RATIO_LIMIT = 1.5;
34
34
  export const THREE_RENDERER_TARGET_FPS = 30;
35
+ export const THREE_RENDERER_MIN_CAMERA_DISTANCE = 8;
36
+ export const THREE_RENDERER_MAX_CAMERA_DISTANCE = 150;
35
37
 
36
38
  export function createOfficeMouseButtons(): OrbitControls["mouseButtons"] {
37
39
  return {
@@ -89,8 +91,8 @@ export function mountThreeAgentGameOffice(
89
91
  controls.enableRotate = true;
90
92
  controls.enableZoom = true;
91
93
  controls.mouseButtons = createOfficeMouseButtons();
92
- controls.minDistance = 8;
93
- controls.maxDistance = 70;
94
+ controls.minDistance = THREE_RENDERER_MIN_CAMERA_DISTANCE;
95
+ controls.maxDistance = THREE_RENDERER_MAX_CAMERA_DISTANCE;
94
96
  controls.maxPolarAngle = Math.PI * 0.48;
95
97
  controls.addEventListener("change", () => {
96
98
  labelsDirty = true;
@@ -112,7 +114,7 @@ export function mountThreeAgentGameOffice(
112
114
  container.appendChild(root);
113
115
 
114
116
  addLights(scene);
115
- buildOfficeScene(scene);
117
+ buildOfficeScene(scene, _options.officeLayout);
116
118
  const agentBodyLayer = createAgentBodyInstancedLayer(scene);
117
119
  const agentEffectLayer = createAgentEffectInstancedLayer(scene);
118
120
 
@@ -135,7 +137,7 @@ export function mountThreeAgentGameOffice(
135
137
  snapshot.agents.forEach((agent, index) => {
136
138
  const record = agents.get(agent.id) ?? createAgentRecord(agent, index);
137
139
  agents.set(agent.id, record);
138
- const target = resolveAgentPosition(agent, index, snapshot.agents.length);
140
+ const target = resolveAgentPosition(agent, index, snapshot.agents.length, _options.officeLayout);
139
141
  record.agent = agent;
140
142
  record.renderIndex = index;
141
143
  record.target.copy(target);
@@ -148,7 +150,7 @@ export function mountThreeAgentGameOffice(
148
150
 
149
151
  function createAgentRecord(agent: AgentGameOfficeAgent, index: number): AgentMeshRecord {
150
152
  const mesh = createAgentMesh(agent, index);
151
- const position = resolveAgentPosition(agent, index, initialSnapshot.agents.length);
153
+ const position = resolveAgentPosition(agent, index, initialSnapshot.agents.length, _options.officeLayout);
152
154
  mesh.group.position.copy(position);
153
155
  scene.add(mesh.group);
154
156
 
@@ -180,7 +182,7 @@ export function mountThreeAgentGameOffice(
180
182
  mesh: record.mesh,
181
183
  agent: record.agent,
182
184
  target: record.target,
183
- facingTarget: resolveAgentFacingTarget(record.agent),
185
+ facingTarget: resolveAgentFacingTarget(record.agent, _options.officeLayout),
184
186
  elapsedSeconds,
185
187
  deltaSeconds,
186
188
  });