let-them-talk 5.2.5 → 5.4.0
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/CHANGELOG.md +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7214
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
package/office/player.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as THREE from 'three';
|
|
|
2
2
|
import { S } from './state.js';
|
|
3
3
|
import { createCharacter } from './character.js';
|
|
4
4
|
import { resolveAppearance } from './appearance.js';
|
|
5
|
+
import { galleryNavigate } from './gallery.js';
|
|
5
6
|
// ============================================================
|
|
6
7
|
// PLAYER AVATAR — Walk around the 3D world as a character
|
|
7
8
|
// Not an agent — no MCP, no messages — just visual presence
|
|
@@ -16,6 +17,8 @@ var PLAYER_RADIUS = 0.35; // collision radius
|
|
|
16
17
|
var _tmpForward = new THREE.Vector3();
|
|
17
18
|
var _tmpRight = new THREE.Vector3();
|
|
18
19
|
var _tmpDir = new THREE.Vector3();
|
|
20
|
+
var _screenRaycaster = new THREE.Raycaster();
|
|
21
|
+
_screenRaycaster.far = 8; // only detect screens within 8 units
|
|
19
22
|
var _tmpCamTarget = new THREE.Vector3();
|
|
20
23
|
var _tmpLookAt = new THREE.Vector3();
|
|
21
24
|
var _tmpTargetPos = new THREE.Vector3();
|
|
@@ -25,63 +28,49 @@ var _tmpTargetPos = new THREE.Vector3();
|
|
|
25
28
|
// Thin walls as boxes with small thickness
|
|
26
29
|
|
|
27
30
|
function getCampusColliders() {
|
|
28
|
-
var W = 50, D = 35;
|
|
29
31
|
var colliders = [
|
|
30
|
-
// Building walls (0.
|
|
31
|
-
{ minX: -
|
|
32
|
-
{ minX:
|
|
33
|
-
{ minX: -
|
|
34
|
-
// Front wall with entrance gap
|
|
35
|
-
{ minX: -
|
|
36
|
-
{ minX:
|
|
37
|
-
|
|
38
|
-
// Manager office walls:
|
|
39
|
-
// Left wall (
|
|
40
|
-
{ minX:
|
|
41
|
-
// Right wall (
|
|
42
|
-
{ minX:
|
|
43
|
-
// Back wall (
|
|
44
|
-
{ minX:
|
|
45
|
-
// Front wall left of door (
|
|
46
|
-
{ minX:
|
|
47
|
-
// Front wall right of door (
|
|
48
|
-
{ minX:
|
|
32
|
+
// Building walls (0.4 thick) — campus 90W x 60D (X: -45 to +45, Z: -30 to +30)
|
|
33
|
+
{ minX: -45.4, maxX: -45, minZ: -30, maxZ: 30 }, // left wall
|
|
34
|
+
{ minX: 45, maxX: 45.4, minZ: -30, maxZ: 30 }, // right wall (solid)
|
|
35
|
+
{ minX: -45, maxX: 45, minZ: -30.4, maxZ: -30 }, // back wall
|
|
36
|
+
// Front wall with 6-unit entrance gap at center (X: -3 to +3)
|
|
37
|
+
{ minX: -45, maxX: -3, minZ: 30, maxZ: 30.4 },
|
|
38
|
+
{ minX: 3, maxX: 45, minZ: 30, maxZ: 30.4 },
|
|
39
|
+
|
|
40
|
+
// Manager office walls: center (30, 10), size 10x10
|
|
41
|
+
// Left wall (X=25): Z from 5 to 15
|
|
42
|
+
{ minX: 24.8, maxX: 25.2, minZ: 5, maxZ: 15 },
|
|
43
|
+
// Right wall (X=35): Z from 5 to 15
|
|
44
|
+
{ minX: 34.8, maxX: 35.2, minZ: 5, maxZ: 15 },
|
|
45
|
+
// Back wall (Z=15): X from 25 to 35
|
|
46
|
+
{ minX: 25, maxX: 35, minZ: 14.8, maxZ: 15.2 },
|
|
47
|
+
// Front wall left of door (Z=5, X from 25 to 29.25)
|
|
48
|
+
{ minX: 25, maxX: 29.25, minZ: 4.85, maxZ: 5.15 },
|
|
49
|
+
// Front wall right of door (Z=5, X from 30.75 to 35)
|
|
50
|
+
{ minX: 30.75, maxX: 35, minZ: 4.85, maxZ: 5.15 },
|
|
49
51
|
// Door collider (only active when closed) — handled dynamically below
|
|
50
52
|
|
|
51
|
-
//
|
|
52
|
-
{ minX: -
|
|
53
|
-
|
|
53
|
+
// Reception desk (ground floor)
|
|
54
|
+
{ minX: -2.5, maxX: 2.5, minZ: 24, maxZ: 26, floor: 'ground' },
|
|
55
|
+
// Water feature (ground floor)
|
|
56
|
+
{ minX: -2.5, maxX: 2.5, minZ: 20, maxZ: 22, floor: 'ground' },
|
|
54
57
|
|
|
55
|
-
//
|
|
56
|
-
{ minX: -
|
|
57
|
-
{ minX: -8.15, maxX: -7.85, minZ: 4, maxZ: 7 },
|
|
58
|
+
// Bar area counter (ground floor, center -28,-18, approx)
|
|
59
|
+
{ minX: -34, maxX: -22, minZ: -22, maxZ: -20, floor: 'ground' },
|
|
58
60
|
|
|
59
|
-
//
|
|
60
|
-
{ minX: -
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
{ minX: -1.5, maxX: 1.5, minZ: 9.5, maxZ: 10.5, floor: 'ground' },
|
|
65
|
-
|
|
66
|
-
// Bar counter (ground floor)
|
|
67
|
-
{ minX: -17, maxX: -11, minZ: -12.7, maxZ: -11.3, floor: 'ground' },
|
|
68
|
-
|
|
69
|
-
// Pool table (ground floor)
|
|
70
|
-
{ minX: -3.3, maxX: -0.7, minZ: -12.7, maxZ: -11.3, floor: 'ground' },
|
|
71
|
-
// Foosball (ground floor)
|
|
72
|
-
{ minX: 1.8, maxX: 3.2, minZ: -12.4, maxZ: -11.6, floor: 'ground' },
|
|
73
|
-
|
|
74
|
-
// Mezzanine support columns (thin cylinders, approximate as small boxes)
|
|
75
|
-
// Columns at x: -18,-9,0,9,18 z: -CAMPUS_D/2 + MEZZ_DEPTH = -17.5+12 = -5.5
|
|
61
|
+
// Rec Center (ground floor, center 0,-18)
|
|
62
|
+
{ minX: -5, maxX: 5, minZ: -22, maxZ: -20, floor: 'ground' },
|
|
63
|
+
|
|
64
|
+
// Gym equipment (ground floor, center 22,-18)
|
|
65
|
+
{ minX: 16, maxX: 28, minZ: -22, maxZ: -20, floor: 'ground' },
|
|
76
66
|
];
|
|
77
67
|
|
|
78
|
-
// Desk colliders (
|
|
68
|
+
// Desk colliders (20 regular desks)
|
|
79
69
|
var CAMPUS_DESKS = [
|
|
80
|
-
{ x: -
|
|
81
|
-
{ x: -
|
|
82
|
-
{ x: -
|
|
83
|
-
{ x: -
|
|
84
|
-
{ x: -14, z: -2 }, { x: -11, z: -2 },
|
|
70
|
+
{ x: -8, z: 6 }, { x: -4, z: 6 }, { x: 0, z: 6 }, { x: 4, z: 6 }, { x: 8, z: 6 },
|
|
71
|
+
{ x: -8, z: 10 }, { x: -4, z: 10 }, { x: 0, z: 10 }, { x: 4, z: 10 }, { x: 8, z: 10 },
|
|
72
|
+
{ x: -8, z: 14 }, { x: -4, z: 14 }, { x: 0, z: 14 }, { x: 4, z: 14 }, { x: 8, z: 14 },
|
|
73
|
+
{ x: -8, z: 18 }, { x: -4, z: 18 }, { x: 0, z: 18 }, { x: 4, z: 18 }, { x: 8, z: 18 },
|
|
85
74
|
];
|
|
86
75
|
CAMPUS_DESKS.forEach(function(d) {
|
|
87
76
|
// Desk body only — chair area excluded so player can stand up without getting stuck
|
|
@@ -89,13 +78,23 @@ function getCampusColliders() {
|
|
|
89
78
|
});
|
|
90
79
|
|
|
91
80
|
// Manager's desk inside office — chair side excluded (ground floor)
|
|
92
|
-
colliders.push({ minX:
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
|
|
81
|
+
colliders.push({ minX: 27, maxX: 33, minZ: 8.5, maxZ: 10, floor: 'ground' });
|
|
82
|
+
|
|
83
|
+
// Gallery Wing colliders (inside campus, center at X:-36, Z:10, 14W x 12D)
|
|
84
|
+
// West wall (X=-43)
|
|
85
|
+
colliders.push({ minX: -43.2, maxX: -42.8, minZ: 4, maxZ: 16 });
|
|
86
|
+
// South wall (Z=4)
|
|
87
|
+
colliders.push({ minX: -43, maxX: -29, minZ: 3.8, maxZ: 4.2 });
|
|
88
|
+
// North wall (Z=16)
|
|
89
|
+
colliders.push({ minX: -43, maxX: -29, minZ: 15.8, maxZ: 16.2 });
|
|
90
|
+
// East wall (X=-29) — glass facade with door gap at center (Z: 8.75 to 11.25)
|
|
91
|
+
colliders.push({ minX: -29.2, maxX: -28.8, minZ: 4, maxZ: 8.75 });
|
|
92
|
+
colliders.push({ minX: -29.2, maxX: -28.8, minZ: 11.25, maxZ: 16 });
|
|
93
|
+
// Gallery furniture
|
|
94
|
+
colliders.push({ minX: -39.4, maxX: -36.6, minZ: 11.5, maxZ: 12.5, floor: 'ground' }); // robot desk
|
|
95
|
+
colliders.push({ minX: -37.5, maxX: -34.5, minZ: 10.2, maxZ: 10.8, floor: 'ground' }); // bench
|
|
96
|
+
colliders.push({ minX: -33.3, maxX: -32.7, minZ: 13.5, maxZ: 14.5, floor: 'ground' }); // pedestal
|
|
97
|
+
colliders.push({ minX: -39.3, maxX: -38.7, minZ: 13.5, maxZ: 14.5, floor: 'ground' }); // pedestal
|
|
99
98
|
|
|
100
99
|
return colliders;
|
|
101
100
|
}
|
|
@@ -146,15 +145,15 @@ function checkCollision(x, z, r) {
|
|
|
146
145
|
|
|
147
146
|
// Mezzanine railing — blocks walking off the edge (only when on mezzanine)
|
|
148
147
|
if (onMezzanine && !onStairs) {
|
|
149
|
-
// Front edge of mezzanine at z = -
|
|
150
|
-
if (z > -
|
|
148
|
+
// Front edge of mezzanine at z = -18 (except staircase gap at x 33.5-36.5)
|
|
149
|
+
if (z > -18.2 && z < -17.8 && !(x >= STAIR_X_MIN - 0.5 && x <= STAIR_X_MAX + 0.5)) {
|
|
151
150
|
return true;
|
|
152
151
|
}
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
// Dynamic: manager door (closed = collider, open = passable)
|
|
156
155
|
if (S.currentEnv === 'campus' && S._managerDoorLerp < 0.5 && !onMezzanine) {
|
|
157
|
-
var doorBox = { minX:
|
|
156
|
+
var doorBox = { minX: 29.25, maxX: 30.75, minZ: 4.85, maxZ: 5.15 };
|
|
158
157
|
var dcx = Math.max(doorBox.minX, Math.min(x, doorBox.maxX));
|
|
159
158
|
var dcz = Math.max(doorBox.minZ, Math.min(z, doorBox.maxZ));
|
|
160
159
|
var ddx = x - dcx, ddz = z - dcz;
|
|
@@ -193,12 +192,12 @@ function resolveMovement(oldX, oldZ, newX, newZ, r) {
|
|
|
193
192
|
}
|
|
194
193
|
|
|
195
194
|
// ==================== HEIGHT SYSTEM ====================
|
|
196
|
-
// Staircase:
|
|
197
|
-
// Mezzanine: y=3.2, z from -
|
|
198
|
-
var STAIR_X_MIN =
|
|
199
|
-
var STAIR_Z_BOTTOM = -
|
|
195
|
+
// Staircase: switchback at X=35, X range 33.5 to 36.5, Z -14 (bottom) to -22 (top, y=3.2)
|
|
196
|
+
// Mezzanine: y=3.2, z from -30 to -18, full width (X: -43 to +43)
|
|
197
|
+
var STAIR_X_MIN = 33.5, STAIR_X_MAX = 36.5;
|
|
198
|
+
var STAIR_Z_BOTTOM = -14, STAIR_Z_TOP = -22;
|
|
200
199
|
var MEZZ_HEIGHT = 3.2;
|
|
201
|
-
var MEZZ_Z_BACK = -
|
|
200
|
+
var MEZZ_Z_BACK = -30, MEZZ_Z_FRONT = -18;
|
|
202
201
|
|
|
203
202
|
function getGroundHeight(x, z, currentY) {
|
|
204
203
|
if (S.currentEnv !== 'campus') return 0;
|
|
@@ -237,7 +236,7 @@ export function spawnPlayer() {
|
|
|
237
236
|
} catch (e) {}
|
|
238
237
|
|
|
239
238
|
var parts = createCharacter('Player', appearance);
|
|
240
|
-
parts.group.position.set(0, 0,
|
|
239
|
+
parts.group.position.set(0, 0, 4); // spawn at main corridor, facing workspace
|
|
241
240
|
|
|
242
241
|
// Remove typing dots and task indicator (player doesn't need them)
|
|
243
242
|
parts.typingLabel.visible = false;
|
|
@@ -253,7 +252,7 @@ export function spawnPlayer() {
|
|
|
253
252
|
|
|
254
253
|
S._player = {
|
|
255
254
|
parts: parts,
|
|
256
|
-
pos: { x: 0, y: 0, z:
|
|
255
|
+
pos: { x: 0, y: 0, z: 4 },
|
|
257
256
|
facing: 0, // radians, 0 = +z direction
|
|
258
257
|
velocity: { x: 0, z: 0 },
|
|
259
258
|
isMoving: false,
|
|
@@ -265,6 +264,8 @@ export function spawnPlayer() {
|
|
|
265
264
|
_jumpVel: 0,
|
|
266
265
|
_jumpY: 0,
|
|
267
266
|
_landSquash: 0,
|
|
267
|
+
firstPerson: false, // V key toggle
|
|
268
|
+
_vPressed: false,
|
|
268
269
|
};
|
|
269
270
|
|
|
270
271
|
// Disable spectator camera movement but keep key/mouse tracking alive
|
|
@@ -288,6 +289,14 @@ export function despawnPlayer() {
|
|
|
288
289
|
if (S._player._sitPrompt.parentElement) S._player._sitPrompt.remove();
|
|
289
290
|
S._player._sitPrompt = null;
|
|
290
291
|
}
|
|
292
|
+
if (S._player._fpPrompt) {
|
|
293
|
+
if (S._player._fpPrompt.parentElement) S._player._fpPrompt.remove();
|
|
294
|
+
S._player._fpPrompt = null;
|
|
295
|
+
}
|
|
296
|
+
if (S._player._screenPrompt) {
|
|
297
|
+
if (S._player._screenPrompt.parentElement) S._player._screenPrompt.remove();
|
|
298
|
+
S._player._screenPrompt = null;
|
|
299
|
+
}
|
|
291
300
|
S.scene.remove(S._player.parts.group);
|
|
292
301
|
S._player.parts.group.traverse(function(child) {
|
|
293
302
|
if (child.geometry) child.geometry.dispose();
|
|
@@ -298,6 +307,9 @@ export function despawnPlayer() {
|
|
|
298
307
|
});
|
|
299
308
|
S._player = null;
|
|
300
309
|
|
|
310
|
+
// Release pointer lock if active
|
|
311
|
+
if (document.pointerLockElement) document.exitPointerLock();
|
|
312
|
+
|
|
301
313
|
// Re-enable spectator camera
|
|
302
314
|
if (S.controls) {
|
|
303
315
|
S.controls.enabled = true;
|
|
@@ -562,12 +574,79 @@ export function updatePlayer(dt, time, keys) {
|
|
|
562
574
|
player.parts.rightLowerLeg.rotation.x = 1.5 * sl;
|
|
563
575
|
player.parts.leftForearm.rotation.x = -0.4 * sl;
|
|
564
576
|
player.parts.rightForearm.rotation.x = -0.4 * sl;
|
|
565
|
-
player.parts.group.position.y = player.pos.y + sl * 0.
|
|
577
|
+
player.parts.group.position.y = player.pos.y + sl * 0.08;
|
|
566
578
|
}
|
|
567
579
|
|
|
568
|
-
// ---
|
|
580
|
+
// --- E/Q: navigate gallery screens via line trace from camera ---
|
|
581
|
+
// Runs BEFORE sit system — if looking at a screen, E navigates instead of sitting
|
|
582
|
+
var _lookingAtScreen = null;
|
|
583
|
+
if (S._galleryScreenMeshes && S._galleryScreenMeshes.length > 0 && !player.sitting) {
|
|
584
|
+
_screenRaycaster.setFromCamera({ x: 0, y: 0 }, S.camera);
|
|
585
|
+
var screenHits = _screenRaycaster.intersectObjects(S._galleryScreenMeshes, false);
|
|
586
|
+
_lookingAtScreen = screenHits.length > 0 ? screenHits[0].object.userData._galleryScreen : null;
|
|
587
|
+
|
|
588
|
+
if (_lookingAtScreen) {
|
|
589
|
+
if (!player._screenPrompt) {
|
|
590
|
+
player._screenPrompt = document.createElement('div');
|
|
591
|
+
player._screenPrompt.style.cssText = 'position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#06b6d4;padding:8px 16px;border-radius:8px;font-size:13px;z-index:1000;pointer-events:none;border:1px solid #06b6d4;';
|
|
592
|
+
document.body.appendChild(player._screenPrompt);
|
|
593
|
+
}
|
|
594
|
+
player._screenPrompt.textContent = _lookingAtScreen.toUpperCase() + ' — E: Next Q: Prev';
|
|
595
|
+
player._screenPrompt.style.display = 'block';
|
|
596
|
+
|
|
597
|
+
// E = next, Q = previous (with cooldown to prevent skipping)
|
|
598
|
+
var now = performance.now();
|
|
599
|
+
var galleryCooldown = 300; // ms between navigations
|
|
600
|
+
if (!player._galleryLastNav) player._galleryLastNav = 0;
|
|
601
|
+
|
|
602
|
+
if (keys['KeyE'] && !player._screenEPressed && now - player._galleryLastNav > galleryCooldown) {
|
|
603
|
+
player._screenEPressed = true;
|
|
604
|
+
player._galleryLastNav = now;
|
|
605
|
+
galleryNavigate(_lookingAtScreen, 1);
|
|
606
|
+
}
|
|
607
|
+
if (!keys['KeyE']) player._screenEPressed = false;
|
|
608
|
+
|
|
609
|
+
if (keys['KeyQ'] && !player._screenQPressed && now - player._galleryLastNav > galleryCooldown) {
|
|
610
|
+
player._screenQPressed = true;
|
|
611
|
+
player._galleryLastNav = now;
|
|
612
|
+
galleryNavigate(_lookingAtScreen, -1);
|
|
613
|
+
}
|
|
614
|
+
if (!keys['KeyQ']) player._screenQPressed = false;
|
|
615
|
+
} else {
|
|
616
|
+
if (player._screenPrompt) player._screenPrompt.style.display = 'none';
|
|
617
|
+
player._screenEPressed = false;
|
|
618
|
+
player._screenQPressed = false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// --- V key: toggle first-person / third-person ---
|
|
623
|
+
if (keys['KeyV'] && !player._vPressed) {
|
|
624
|
+
player._vPressed = true;
|
|
625
|
+
player.firstPerson = !player.firstPerson;
|
|
626
|
+
// Show/hide character mesh in first-person
|
|
627
|
+
player.parts.group.traverse(function(child) {
|
|
628
|
+
if (child.isMesh) child.visible = !player.firstPerson;
|
|
629
|
+
});
|
|
630
|
+
// Show FP indicator
|
|
631
|
+
if (!player._fpPrompt) {
|
|
632
|
+
player._fpPrompt = document.createElement('div');
|
|
633
|
+
player._fpPrompt.style.cssText = 'position:fixed;top:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.6);color:#58a6ff;padding:6px 14px;border-radius:6px;font-size:12px;z-index:1000;pointer-events:none;transition:opacity 0.5s;';
|
|
634
|
+
document.body.appendChild(player._fpPrompt);
|
|
635
|
+
}
|
|
636
|
+
player._fpPrompt.textContent = player.firstPerson ? 'First Person (V to switch)' : 'Third Person (V to switch)';
|
|
637
|
+
player._fpPrompt.style.opacity = '1';
|
|
638
|
+
clearTimeout(player._fpFadeTimer);
|
|
639
|
+
player._fpFadeTimer = setTimeout(function() {
|
|
640
|
+
if (player._fpPrompt) player._fpPrompt.style.opacity = '0';
|
|
641
|
+
}, 2000);
|
|
642
|
+
}
|
|
643
|
+
if (!keys['KeyV']) player._vPressed = false;
|
|
644
|
+
|
|
645
|
+
// --- Camera follow ---
|
|
569
646
|
if (player.sitting) {
|
|
570
647
|
updatePlayerCameraDesk(dt);
|
|
648
|
+
} else if (player.firstPerson) {
|
|
649
|
+
updatePlayerCameraFP(dt);
|
|
571
650
|
} else {
|
|
572
651
|
updatePlayerCamera(dt);
|
|
573
652
|
}
|
|
@@ -624,6 +703,37 @@ function updatePlayerCameraDesk(dt) {
|
|
|
624
703
|
S.camera.lookAt(_tmpLookAt);
|
|
625
704
|
}
|
|
626
705
|
|
|
706
|
+
// First-person camera — at head height, looks where mouse points
|
|
707
|
+
function updatePlayerCameraFP(dt) {
|
|
708
|
+
var player = S._player;
|
|
709
|
+
if (!player) return;
|
|
710
|
+
|
|
711
|
+
var yaw = 0, pitch = 0;
|
|
712
|
+
if (S.controls && S.controls._euler) {
|
|
713
|
+
yaw = S.controls._euler.y;
|
|
714
|
+
pitch = -S.controls._euler.x; // flip: mouse up = look up
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Clamp pitch to prevent flipping
|
|
718
|
+
pitch = Math.max(-1.2, Math.min(1.2, pitch));
|
|
719
|
+
|
|
720
|
+
// Camera at standing eye level (higher than chibi head for immersive FP view)
|
|
721
|
+
var headY = player.pos.y + 1.65;
|
|
722
|
+
S.camera.position.set(player.pos.x, headY, player.pos.z);
|
|
723
|
+
|
|
724
|
+
// Look direction from yaw/pitch
|
|
725
|
+
var lookDist = 5;
|
|
726
|
+
var lookX = player.pos.x - Math.sin(yaw) * Math.cos(pitch) * lookDist;
|
|
727
|
+
var lookZ = player.pos.z - Math.cos(yaw) * Math.cos(pitch) * lookDist;
|
|
728
|
+
var lookY = headY - Math.sin(pitch) * lookDist;
|
|
729
|
+
|
|
730
|
+
_tmpLookAt.set(lookX, lookY, lookZ);
|
|
731
|
+
S.camera.lookAt(_tmpLookAt);
|
|
732
|
+
|
|
733
|
+
// Also face the character model in the look direction (for shadow/collision)
|
|
734
|
+
player.facing = yaw + Math.PI;
|
|
735
|
+
}
|
|
736
|
+
|
|
627
737
|
// --- Public API for iframe integration ---
|
|
628
738
|
export function isPlayerSitting() {
|
|
629
739
|
return S._player && S._player.sitting;
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
|
+
|
|
4
|
+
// ============================================================
|
|
5
|
+
// ROBOT CHARACTER — Boxy/mechanical design for API agents
|
|
6
|
+
// Distinct from chibi characters: metallic, glowing eyes, antenna
|
|
7
|
+
// ============================================================
|
|
8
|
+
|
|
9
|
+
export function createRobotCharacter(name, providerColor) {
|
|
10
|
+
var group = new THREE.Group();
|
|
11
|
+
var color = new THREE.Color(providerColor || '#0ea5e9');
|
|
12
|
+
var colorHex = color.getHex();
|
|
13
|
+
|
|
14
|
+
// Materials
|
|
15
|
+
var bodyMat = new THREE.MeshStandardMaterial({ color: 0x2a2d3a, roughness: 0.3, metalness: 0.7 });
|
|
16
|
+
var chromeMat = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.1, metalness: 0.9 });
|
|
17
|
+
var darkMat = new THREE.MeshStandardMaterial({ color: 0x111118, roughness: 0.4, metalness: 0.5 });
|
|
18
|
+
var glowMat = new THREE.MeshStandardMaterial({ color: colorHex, emissive: colorHex, emissiveIntensity: 0.8, roughness: 0.2 });
|
|
19
|
+
var eyeGlowMat = new THREE.MeshStandardMaterial({ color: colorHex, emissive: colorHex, emissiveIntensity: 1.2, roughness: 0.1 });
|
|
20
|
+
var screenMat = new THREE.MeshStandardMaterial({ color: 0x333333, emissive: 0x333333, emissiveIntensity: 0.1, roughness: 0.2 });
|
|
21
|
+
|
|
22
|
+
// Shadow
|
|
23
|
+
var shadow = new THREE.Mesh(
|
|
24
|
+
new THREE.PlaneGeometry(0.6, 0.6),
|
|
25
|
+
new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0.3, depthWrite: false })
|
|
26
|
+
);
|
|
27
|
+
shadow.rotation.x = -Math.PI / 2;
|
|
28
|
+
shadow.position.y = 0.01;
|
|
29
|
+
shadow.userData.isShadow = true;
|
|
30
|
+
group.add(shadow);
|
|
31
|
+
|
|
32
|
+
// ===== BODY (boxy torso) =====
|
|
33
|
+
var body = new THREE.Mesh(new THREE.BoxGeometry(0.4, 0.35, 0.25), bodyMat);
|
|
34
|
+
body.position.y = 0.55;
|
|
35
|
+
body.castShadow = true;
|
|
36
|
+
group.add(body);
|
|
37
|
+
|
|
38
|
+
// Chest panel (darker inset)
|
|
39
|
+
var chestPanel = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.2, 0.01), darkMat);
|
|
40
|
+
chestPanel.position.set(0, 0.55, 0.131);
|
|
41
|
+
group.add(chestPanel);
|
|
42
|
+
|
|
43
|
+
// Status LED on chest
|
|
44
|
+
var statusLed = new THREE.Mesh(new THREE.SphereGeometry(0.02, 8, 8), glowMat);
|
|
45
|
+
statusLed.position.set(0, 0.6, 0.14);
|
|
46
|
+
group.add(statusLed);
|
|
47
|
+
|
|
48
|
+
// Accent stripes on body
|
|
49
|
+
var stripe1 = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.015, 0.01), glowMat);
|
|
50
|
+
stripe1.position.set(0, 0.68, 0.131);
|
|
51
|
+
group.add(stripe1);
|
|
52
|
+
var stripe2 = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.015, 0.01), glowMat);
|
|
53
|
+
stripe2.position.set(0, 0.42, 0.131);
|
|
54
|
+
group.add(stripe2);
|
|
55
|
+
|
|
56
|
+
// ===== LEGS (cylindrical, mechanical) =====
|
|
57
|
+
var legGeo = new THREE.CylinderGeometry(0.06, 0.05, 0.35, 8);
|
|
58
|
+
var leftLeg = new THREE.Mesh(legGeo, chromeMat);
|
|
59
|
+
leftLeg.position.set(-0.1, 0.2, 0);
|
|
60
|
+
leftLeg.castShadow = true;
|
|
61
|
+
group.add(leftLeg);
|
|
62
|
+
|
|
63
|
+
var rightLeg = new THREE.Mesh(legGeo, chromeMat);
|
|
64
|
+
rightLeg.position.set(0.1, 0.2, 0);
|
|
65
|
+
rightLeg.castShadow = true;
|
|
66
|
+
group.add(rightLeg);
|
|
67
|
+
|
|
68
|
+
// Joint rings
|
|
69
|
+
var jointGeo = new THREE.TorusGeometry(0.065, 0.01, 8, 12);
|
|
70
|
+
[-0.1, 0.1].forEach(function(lx) {
|
|
71
|
+
var joint = new THREE.Mesh(jointGeo, glowMat);
|
|
72
|
+
joint.position.set(lx, 0.37, 0);
|
|
73
|
+
joint.rotation.x = Math.PI / 2;
|
|
74
|
+
group.add(joint);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Feet (flat boxes)
|
|
78
|
+
[-0.1, 0.1].forEach(function(fx) {
|
|
79
|
+
var foot = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.03, 0.12), darkMat);
|
|
80
|
+
foot.position.set(fx, 0.02, 0.01);
|
|
81
|
+
group.add(foot);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ===== ARMS (jointed, mechanical) =====
|
|
85
|
+
var armGeo = new THREE.CylinderGeometry(0.04, 0.035, 0.25, 8);
|
|
86
|
+
|
|
87
|
+
var leftArm = new THREE.Mesh(armGeo, chromeMat);
|
|
88
|
+
leftArm.position.set(-0.28, 0.55, 0);
|
|
89
|
+
leftArm.rotation.z = 0.15;
|
|
90
|
+
leftArm.castShadow = true;
|
|
91
|
+
group.add(leftArm);
|
|
92
|
+
|
|
93
|
+
var rightArm = new THREE.Mesh(armGeo, chromeMat);
|
|
94
|
+
rightArm.position.set(0.28, 0.55, 0);
|
|
95
|
+
rightArm.rotation.z = -0.15;
|
|
96
|
+
rightArm.castShadow = true;
|
|
97
|
+
group.add(rightArm);
|
|
98
|
+
|
|
99
|
+
// Hands (sphere clamps)
|
|
100
|
+
var handGeo = new THREE.SphereGeometry(0.04, 8, 8);
|
|
101
|
+
var leftHand = new THREE.Mesh(handGeo, bodyMat);
|
|
102
|
+
leftHand.position.set(-0.3, 0.42, 0);
|
|
103
|
+
group.add(leftHand);
|
|
104
|
+
|
|
105
|
+
var rightHand = new THREE.Mesh(handGeo, bodyMat);
|
|
106
|
+
rightHand.position.set(0.3, 0.42, 0);
|
|
107
|
+
group.add(rightHand);
|
|
108
|
+
|
|
109
|
+
// ===== HEAD (boxy with rounded edges) =====
|
|
110
|
+
var headGeo = new THREE.BoxGeometry(0.3, 0.25, 0.22);
|
|
111
|
+
var head = new THREE.Mesh(headGeo, bodyMat);
|
|
112
|
+
head.position.y = 0.87;
|
|
113
|
+
head.castShadow = true;
|
|
114
|
+
group.add(head);
|
|
115
|
+
|
|
116
|
+
// Face plate (visor)
|
|
117
|
+
var visor = new THREE.Mesh(new THREE.BoxGeometry(0.26, 0.12, 0.01), darkMat);
|
|
118
|
+
visor.position.set(0, 0.88, 0.115);
|
|
119
|
+
group.add(visor);
|
|
120
|
+
|
|
121
|
+
// Glowing eyes (2 dots on visor)
|
|
122
|
+
var eyeGeo = new THREE.SphereGeometry(0.025, 8, 8);
|
|
123
|
+
var leftEye = new THREE.Mesh(eyeGeo, eyeGlowMat);
|
|
124
|
+
leftEye.position.set(-0.06, 0.89, 0.12);
|
|
125
|
+
group.add(leftEye);
|
|
126
|
+
|
|
127
|
+
var rightEye = new THREE.Mesh(eyeGeo, eyeGlowMat);
|
|
128
|
+
rightEye.position.set(0.06, 0.89, 0.12);
|
|
129
|
+
group.add(rightEye);
|
|
130
|
+
|
|
131
|
+
// Mouth (small LED strip)
|
|
132
|
+
var mouthStrip = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.015, 0.01), glowMat);
|
|
133
|
+
mouthStrip.position.set(0, 0.84, 0.12);
|
|
134
|
+
group.add(mouthStrip);
|
|
135
|
+
|
|
136
|
+
// ===== ANTENNA (satellite dish style) =====
|
|
137
|
+
var antennaBase = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.12, 6), chromeMat);
|
|
138
|
+
antennaBase.position.set(0.08, 1.06, 0);
|
|
139
|
+
group.add(antennaBase);
|
|
140
|
+
|
|
141
|
+
var antennaDish = new THREE.Mesh(new THREE.SphereGeometry(0.04, 8, 6, 0, Math.PI * 2, 0, Math.PI / 2), glowMat);
|
|
142
|
+
antennaDish.position.set(0.08, 1.12, 0);
|
|
143
|
+
antennaDish.rotation.x = Math.PI;
|
|
144
|
+
group.add(antennaDish);
|
|
145
|
+
|
|
146
|
+
var antennaTip = new THREE.Mesh(new THREE.SphereGeometry(0.012, 6, 6), eyeGlowMat);
|
|
147
|
+
antennaTip.position.set(0.08, 1.14, 0);
|
|
148
|
+
group.add(antennaTip);
|
|
149
|
+
|
|
150
|
+
// ===== LABELS =====
|
|
151
|
+
// Name label
|
|
152
|
+
var labelDiv = document.createElement('div');
|
|
153
|
+
labelDiv.className = 'office3d-label';
|
|
154
|
+
labelDiv.innerHTML = '<span class="office3d-label-dot" style="background:#4ade80"></span><span class="office3d-label-name">' + name + '</span><span class="office3d-robot-badge" style="background:' + providerColor + ';color:#fff;font-size:7px;padding:1px 4px;border-radius:3px;margin-left:4px">BOT</span>';
|
|
155
|
+
labelDiv.style.cssText = 'display:flex;align-items:center;gap:4px;background:rgba(0,0,0,0.65);padding:3px 8px;border-radius:6px;font-size:10px;font-family:monospace;color:#c9d1d9;white-space:nowrap;pointer-events:none;border:1px solid ' + providerColor + '44;';
|
|
156
|
+
var label = new CSS2DObject(labelDiv);
|
|
157
|
+
label.position.set(0, 1.35, 0);
|
|
158
|
+
group.add(label);
|
|
159
|
+
|
|
160
|
+
// Speech bubble
|
|
161
|
+
var bubbleDiv = document.createElement('div');
|
|
162
|
+
bubbleDiv.className = 'office3d-bubble';
|
|
163
|
+
bubbleDiv.style.cssText = 'display:none;opacity:0;background:rgba(10,10,20,0.85);color:#e0e0e0;padding:6px 12px;border-radius:10px;font-size:11px;max-width:220px;white-space:pre-wrap;word-wrap:break-word;font-family:monospace;pointer-events:none;border:1px solid ' + providerColor + '66;box-shadow:0 0 10px ' + providerColor + '33;';
|
|
164
|
+
var bubble = new CSS2DObject(bubbleDiv);
|
|
165
|
+
bubble.position.set(0, 1.55, 0);
|
|
166
|
+
group.add(bubble);
|
|
167
|
+
|
|
168
|
+
// Task indicator
|
|
169
|
+
var taskDiv = document.createElement('div');
|
|
170
|
+
taskDiv.className = 'office3d-task-indicator working';
|
|
171
|
+
var taskLabel = new CSS2DObject(taskDiv);
|
|
172
|
+
taskLabel.position.set(0, 1.7, 0);
|
|
173
|
+
taskLabel.visible = false;
|
|
174
|
+
group.add(taskLabel);
|
|
175
|
+
|
|
176
|
+
// Typing dots
|
|
177
|
+
var typingDiv = document.createElement('div');
|
|
178
|
+
typingDiv.className = 'office3d-typing';
|
|
179
|
+
typingDiv.innerHTML = '<span class="office3d-typing-dot"></span><span class="office3d-typing-dot"></span><span class="office3d-typing-dot"></span>';
|
|
180
|
+
var typingLabel = new CSS2DObject(typingDiv);
|
|
181
|
+
typingLabel.position.set(0, 1.65, 0);
|
|
182
|
+
typingLabel.visible = false;
|
|
183
|
+
group.add(typingLabel);
|
|
184
|
+
|
|
185
|
+
// ZZZ sprites
|
|
186
|
+
var zzzObjects = [];
|
|
187
|
+
for (var zi = 0; zi < 3; zi++) {
|
|
188
|
+
var zDiv = document.createElement('div');
|
|
189
|
+
zDiv.textContent = 'Z';
|
|
190
|
+
zDiv.style.cssText = 'color:#facc15;font-size:' + (10 + zi * 4) + 'px;font-weight:bold;font-family:monospace;opacity:0;pointer-events:none;';
|
|
191
|
+
var zObj = new CSS2DObject(zDiv);
|
|
192
|
+
zObj.position.set(0.2 + zi * 0.1, 1.2 + zi * 0.15, 0);
|
|
193
|
+
group.add(zObj);
|
|
194
|
+
zzzObjects.push({ obj: zObj, div: zDiv });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
group: group,
|
|
199
|
+
body: body,
|
|
200
|
+
head: head,
|
|
201
|
+
leftLeg: leftLeg,
|
|
202
|
+
rightLeg: rightLeg,
|
|
203
|
+
leftArm: leftArm,
|
|
204
|
+
rightArm: rightArm,
|
|
205
|
+
leftHand: leftHand,
|
|
206
|
+
rightHand: rightHand,
|
|
207
|
+
statusLed: statusLed,
|
|
208
|
+
antennaTip: antennaTip,
|
|
209
|
+
leftEye: leftEye,
|
|
210
|
+
rightEye: rightEye,
|
|
211
|
+
mouthStrip: mouthStrip,
|
|
212
|
+
label: label,
|
|
213
|
+
labelDiv: labelDiv,
|
|
214
|
+
bubble: bubble,
|
|
215
|
+
bubbleDiv: bubbleDiv,
|
|
216
|
+
zzzObjects: zzzObjects,
|
|
217
|
+
taskDiv: taskDiv, taskLabel: taskLabel,
|
|
218
|
+
typingDiv: typingDiv, typingLabel: typingLabel,
|
|
219
|
+
bodyMat: bodyMat,
|
|
220
|
+
glowMat: glowMat,
|
|
221
|
+
eyeGlowMat: eyeGlowMat,
|
|
222
|
+
screenMat: screenMat,
|
|
223
|
+
isRobot: true,
|
|
224
|
+
// Compatibility with chibi character parts for animation system
|
|
225
|
+
// These dummy objects prevent crashes in animation.js which expects chibi parts
|
|
226
|
+
leftLowerLeg: new THREE.Object3D(),
|
|
227
|
+
rightLowerLeg: new THREE.Object3D(),
|
|
228
|
+
leftForearm: new THREE.Object3D(),
|
|
229
|
+
rightForearm: new THREE.Object3D(),
|
|
230
|
+
faceSprite: null,
|
|
231
|
+
hairGroup: new THREE.Group(),
|
|
232
|
+
headMat: bodyMat,
|
|
233
|
+
legMat: chromeMat,
|
|
234
|
+
shoeMat: darkMat,
|
|
235
|
+
armMat: chromeMat,
|
|
236
|
+
handMat: bodyMat,
|
|
237
|
+
outfitGroup: null,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Robot-specific animations
|
|
242
|
+
export function updateRobotAnimation(agent, dt, time) {
|
|
243
|
+
if (!agent.parts || !agent.parts.isRobot) return;
|
|
244
|
+
|
|
245
|
+
// Antenna tip pulse
|
|
246
|
+
if (agent.parts.antennaTip) {
|
|
247
|
+
var pulse = (Math.sin(time * 3) + 1) / 2;
|
|
248
|
+
agent.parts.antennaTip.scale.setScalar(0.8 + pulse * 0.4);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Eye glow pulse when processing
|
|
252
|
+
if (agent._processing && agent.parts.leftEye) {
|
|
253
|
+
var eyePulse = (Math.sin(time * 8) + 1) / 2;
|
|
254
|
+
agent.parts.eyeGlowMat.emissiveIntensity = 0.6 + eyePulse * 0.8;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Status LED blink when idle
|
|
258
|
+
if (agent.parts.statusLed && !agent._processing) {
|
|
259
|
+
var ledPulse = Math.sin(time * 2) > 0.7 ? 1 : 0.3;
|
|
260
|
+
agent.parts.glowMat.emissiveIntensity = ledPulse;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Idle head bob (subtle)
|
|
264
|
+
if (agent.parts.head && agent.isSitting && !agent._processing) {
|
|
265
|
+
agent.parts.head.rotation.y = Math.sin(time * 0.5) * 0.05;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Processing spin — body slight rotation oscillation
|
|
269
|
+
if (agent._processing && agent.parts.body) {
|
|
270
|
+
agent.parts.body.rotation.y = Math.sin(time * 4) * 0.03;
|
|
271
|
+
}
|
|
272
|
+
}
|