let-them-talk 4.2.0 → 5.2.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 +640 -540
- package/README.md +592 -415
- package/cli.js +1089 -589
- package/conversation-templates/autonomous-feature.json +22 -0
- package/conversation-templates/code-review.json +21 -11
- package/conversation-templates/debug-squad.json +21 -11
- package/conversation-templates/feature-build.json +21 -11
- package/conversation-templates/research-write.json +21 -11
- package/dashboard.html +9250 -7771
- package/dashboard.js +1232 -29
- package/office/agents.js +148 -4
- package/office/animation.js +68 -0
- package/office/assets.js +431 -0
- package/office/builder.js +355 -0
- package/office/building-interior.js +261 -0
- package/office/campus-env.js +119 -23
- package/office/car-hud.js +368 -0
- package/office/daynight.js +221 -0
- package/office/economy-hud.js +432 -0
- package/office/economy-ui.js +238 -0
- package/office/environment.js +818 -808
- package/office/face.js +65 -0
- package/office/fast-travel.js +215 -0
- package/office/hq-building.js +295 -0
- package/office/index.js +1095 -423
- package/office/instancing.js +160 -0
- package/office/lod-manager.js +165 -0
- package/office/multiplayer-hud.js +428 -0
- package/office/net-client.js +299 -0
- package/office/particles.js +172 -0
- package/office/player.js +658 -436
- package/office/post-processing.js +82 -0
- package/office/sky.js +319 -0
- package/office/street-furniture.js +308 -0
- package/office/vehicle.js +455 -0
- package/office/world-save.js +91 -0
- package/package.json +59 -59
- package/server.js +7190 -4685
- package/conversation-templates/managed-team.json +0 -12
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* InstancedMesh pool manager for city-scale rendering.
|
|
5
|
+
* Groups identical geometries into single draw calls.
|
|
6
|
+
* Target: 120fps with 500+ buildings via minimal draw calls.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const pools = new Map(); // key -> { mesh, count, maxCount, dummy }
|
|
10
|
+
|
|
11
|
+
const _dummy = new THREE.Object3D();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create or get an instanced mesh pool.
|
|
15
|
+
* @param {string} key - Unique pool identifier (e.g. 'building-office', 'tree-pine')
|
|
16
|
+
* @param {THREE.BufferGeometry} geometry - Shared geometry for all instances
|
|
17
|
+
* @param {THREE.Material} material - Shared material for all instances
|
|
18
|
+
* @param {number} maxCount - Maximum number of instances in pool
|
|
19
|
+
* @param {THREE.Scene} scene - Scene to add the mesh to
|
|
20
|
+
* @returns {{ mesh: THREE.InstancedMesh, key: string }}
|
|
21
|
+
*/
|
|
22
|
+
export function createPool(key, geometry, material, maxCount, scene) {
|
|
23
|
+
if (pools.has(key)) return pools.get(key);
|
|
24
|
+
|
|
25
|
+
const mesh = new THREE.InstancedMesh(geometry, material, maxCount);
|
|
26
|
+
mesh.count = 0; // Start with 0 visible instances
|
|
27
|
+
mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
|
|
28
|
+
mesh.matrixAutoUpdate = false;
|
|
29
|
+
mesh.frustumCulled = true;
|
|
30
|
+
mesh.name = 'pool-' + key;
|
|
31
|
+
|
|
32
|
+
// Enable per-instance colors
|
|
33
|
+
mesh.instanceColor = new THREE.InstancedBufferAttribute(
|
|
34
|
+
new Float32Array(maxCount * 3), 3
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
scene.add(mesh);
|
|
38
|
+
const pool = { mesh, count: 0, maxCount, key };
|
|
39
|
+
pools.set(key, pool);
|
|
40
|
+
return pool;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add an instance to a pool.
|
|
45
|
+
* @param {string} key - Pool identifier
|
|
46
|
+
* @param {THREE.Vector3} position - World position
|
|
47
|
+
* @param {THREE.Euler|null} rotation - Rotation (optional)
|
|
48
|
+
* @param {THREE.Vector3|null} scale - Scale (optional, defaults to 1,1,1)
|
|
49
|
+
* @param {THREE.Color|null} color - Per-instance color (optional)
|
|
50
|
+
* @returns {number} Instance index, or -1 if pool is full
|
|
51
|
+
*/
|
|
52
|
+
export function addInstance(key, position, rotation, scale, color) {
|
|
53
|
+
const pool = pools.get(key);
|
|
54
|
+
if (!pool) return -1;
|
|
55
|
+
if (pool.count >= pool.maxCount) return -1;
|
|
56
|
+
|
|
57
|
+
const idx = pool.count;
|
|
58
|
+
|
|
59
|
+
_dummy.position.copy(position);
|
|
60
|
+
if (rotation) _dummy.rotation.copy(rotation);
|
|
61
|
+
else _dummy.rotation.set(0, 0, 0);
|
|
62
|
+
if (scale) _dummy.scale.copy(scale);
|
|
63
|
+
else _dummy.scale.set(1, 1, 1);
|
|
64
|
+
_dummy.updateMatrix();
|
|
65
|
+
|
|
66
|
+
pool.mesh.setMatrixAt(idx, _dummy.matrix);
|
|
67
|
+
if (color) pool.mesh.setColorAt(idx, color);
|
|
68
|
+
|
|
69
|
+
pool.count++;
|
|
70
|
+
pool.mesh.count = pool.count;
|
|
71
|
+
pool.mesh.instanceMatrix.needsUpdate = true;
|
|
72
|
+
if (color) pool.mesh.instanceColor.needsUpdate = true;
|
|
73
|
+
|
|
74
|
+
return idx;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Update an existing instance's transform.
|
|
79
|
+
* @param {string} key - Pool identifier
|
|
80
|
+
* @param {number} index - Instance index
|
|
81
|
+
* @param {THREE.Vector3} position
|
|
82
|
+
* @param {THREE.Euler|null} rotation
|
|
83
|
+
* @param {THREE.Vector3|null} scale
|
|
84
|
+
*/
|
|
85
|
+
export function updateInstance(key, index, position, rotation, scale) {
|
|
86
|
+
const pool = pools.get(key);
|
|
87
|
+
if (!pool || index < 0 || index >= pool.count) return;
|
|
88
|
+
|
|
89
|
+
_dummy.position.copy(position);
|
|
90
|
+
if (rotation) _dummy.rotation.copy(rotation);
|
|
91
|
+
else _dummy.rotation.set(0, 0, 0);
|
|
92
|
+
if (scale) _dummy.scale.copy(scale);
|
|
93
|
+
else _dummy.scale.set(1, 1, 1);
|
|
94
|
+
_dummy.updateMatrix();
|
|
95
|
+
|
|
96
|
+
pool.mesh.setMatrixAt(index, _dummy.matrix);
|
|
97
|
+
pool.mesh.instanceMatrix.needsUpdate = true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Update an instance's color.
|
|
102
|
+
* @param {string} key - Pool identifier
|
|
103
|
+
* @param {number} index - Instance index
|
|
104
|
+
* @param {THREE.Color} color
|
|
105
|
+
*/
|
|
106
|
+
export function updateInstanceColor(key, index, color) {
|
|
107
|
+
const pool = pools.get(key);
|
|
108
|
+
if (!pool || index < 0 || index >= pool.count) return;
|
|
109
|
+
pool.mesh.setColorAt(index, color);
|
|
110
|
+
pool.mesh.instanceColor.needsUpdate = true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clear all instances in a pool (reset count to 0).
|
|
115
|
+
* @param {string} key - Pool identifier
|
|
116
|
+
*/
|
|
117
|
+
export function clearPool(key) {
|
|
118
|
+
const pool = pools.get(key);
|
|
119
|
+
if (!pool) return;
|
|
120
|
+
pool.count = 0;
|
|
121
|
+
pool.mesh.count = 0;
|
|
122
|
+
pool.mesh.instanceMatrix.needsUpdate = true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Remove a pool entirely, disposing geometry/material.
|
|
127
|
+
* @param {string} key - Pool identifier
|
|
128
|
+
* @param {THREE.Scene} scene - Scene to remove from
|
|
129
|
+
*/
|
|
130
|
+
export function disposePool(key, scene) {
|
|
131
|
+
const pool = pools.get(key);
|
|
132
|
+
if (!pool) return;
|
|
133
|
+
scene.remove(pool.mesh);
|
|
134
|
+
pool.mesh.geometry.dispose();
|
|
135
|
+
if (pool.mesh.material.dispose) pool.mesh.material.dispose();
|
|
136
|
+
pool.mesh.dispose();
|
|
137
|
+
pools.delete(key);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get pool stats for performance monitoring.
|
|
142
|
+
* @returns {Array<{ key: string, count: number, maxCount: number }>}
|
|
143
|
+
*/
|
|
144
|
+
export function getPoolStats() {
|
|
145
|
+
const stats = [];
|
|
146
|
+
for (const [key, pool] of pools) {
|
|
147
|
+
stats.push({ key, count: pool.count, maxCount: pool.maxCount });
|
|
148
|
+
}
|
|
149
|
+
return stats;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Dispose all pools.
|
|
154
|
+
* @param {THREE.Scene} scene
|
|
155
|
+
*/
|
|
156
|
+
export function disposeAllPools(scene) {
|
|
157
|
+
for (const key of pools.keys()) {
|
|
158
|
+
disposePool(key, scene);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* LOD (Level of Detail) manager for city-scale rendering.
|
|
5
|
+
* Wraps THREE.LOD with configurable distance thresholds and hysteresis.
|
|
6
|
+
* Target: 120fps by reducing geometry complexity at distance.
|
|
7
|
+
*
|
|
8
|
+
* Default tiers:
|
|
9
|
+
* 0: Full detail (< 20 units)
|
|
10
|
+
* 1: Medium detail (< 60 units)
|
|
11
|
+
* 2: Low-poly (< 150 units)
|
|
12
|
+
* 3: Billboard/hide (> 150 units)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const DEFAULT_THRESHOLDS = [0, 20, 60, 150];
|
|
16
|
+
const HYSTERESIS = 2; // units of overlap to prevent LOD flickering at boundaries
|
|
17
|
+
|
|
18
|
+
const managedLODs = new Map(); // id -> { lod, thresholds }
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a managed LOD object.
|
|
22
|
+
* @param {string} id - Unique identifier for this LOD group
|
|
23
|
+
* @param {Array<{ mesh: THREE.Object3D, distance: number }>} levels - LOD levels sorted by distance
|
|
24
|
+
* Each level: { mesh, distance } where distance is the threshold to switch to this level.
|
|
25
|
+
* Lower distance = higher detail. First level (distance 0) is full detail.
|
|
26
|
+
* @param {THREE.Scene} scene - Scene to add to
|
|
27
|
+
* @param {THREE.Vector3} position - World position
|
|
28
|
+
* @returns {THREE.LOD}
|
|
29
|
+
*/
|
|
30
|
+
export function createLOD(id, levels, scene, position) {
|
|
31
|
+
const lod = new THREE.LOD();
|
|
32
|
+
lod.name = 'lod-' + id;
|
|
33
|
+
|
|
34
|
+
for (const level of levels) {
|
|
35
|
+
lod.addLevel(level.mesh, level.distance);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Enable hysteresis to prevent flickering at LOD boundaries
|
|
39
|
+
if (typeof lod.autoUpdate !== 'undefined') {
|
|
40
|
+
lod.autoUpdate = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (position) lod.position.copy(position);
|
|
44
|
+
lod.matrixAutoUpdate = false;
|
|
45
|
+
lod.updateMatrix();
|
|
46
|
+
|
|
47
|
+
scene.add(lod);
|
|
48
|
+
managedLODs.set(id, { lod, levels });
|
|
49
|
+
return lod;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create LOD levels from a single geometry with auto-simplified versions.
|
|
54
|
+
* Generates 3 detail levels from a base geometry by reducing vertices.
|
|
55
|
+
* @param {THREE.BufferGeometry} baseGeometry - Full-detail geometry
|
|
56
|
+
* @param {THREE.Material} material - Material (shared across levels)
|
|
57
|
+
* @param {number[]} thresholds - Distance thresholds [full, medium, low, hide]
|
|
58
|
+
* @returns {Array<{ mesh: THREE.Object3D, distance: number }>}
|
|
59
|
+
*/
|
|
60
|
+
export function autoLODLevels(baseGeometry, material, thresholds) {
|
|
61
|
+
const t = thresholds || DEFAULT_THRESHOLDS;
|
|
62
|
+
const levels = [];
|
|
63
|
+
|
|
64
|
+
// Level 0: Full detail
|
|
65
|
+
levels.push({
|
|
66
|
+
mesh: new THREE.Mesh(baseGeometry, material),
|
|
67
|
+
distance: t[0] || 0
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Level 1: Medium — use same geometry (simplification would need a library)
|
|
71
|
+
// In practice, city-env.js should pass pre-built simplified geometries
|
|
72
|
+
levels.push({
|
|
73
|
+
mesh: new THREE.Mesh(baseGeometry, material),
|
|
74
|
+
distance: t[1] || 20
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Level 2: Low-poly placeholder — simple box approximation
|
|
78
|
+
const bbox = new THREE.Box3().setFromBufferAttribute(baseGeometry.getAttribute('position'));
|
|
79
|
+
const size = new THREE.Vector3();
|
|
80
|
+
bbox.getSize(size);
|
|
81
|
+
const lowPolyGeo = new THREE.BoxGeometry(size.x, size.y, size.z);
|
|
82
|
+
levels.push({
|
|
83
|
+
mesh: new THREE.Mesh(lowPolyGeo, material),
|
|
84
|
+
distance: t[2] || 60
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Level 3: Empty object (invisible at far distance)
|
|
88
|
+
levels.push({
|
|
89
|
+
mesh: new THREE.Object3D(),
|
|
90
|
+
distance: t[3] || 150
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return levels;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Batch-update all managed LOD objects from camera position.
|
|
98
|
+
* Call this once per frame in the render loop.
|
|
99
|
+
* @param {THREE.Camera} camera
|
|
100
|
+
*/
|
|
101
|
+
export function updateAllLODs(camera) {
|
|
102
|
+
for (const [, entry] of managedLODs) {
|
|
103
|
+
entry.lod.update(camera);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove and dispose a managed LOD object.
|
|
109
|
+
* @param {string} id
|
|
110
|
+
* @param {THREE.Scene} scene
|
|
111
|
+
*/
|
|
112
|
+
export function disposeLOD(id, scene) {
|
|
113
|
+
const entry = managedLODs.get(id);
|
|
114
|
+
if (!entry) return;
|
|
115
|
+
|
|
116
|
+
scene.remove(entry.lod);
|
|
117
|
+
entry.lod.traverse(child => {
|
|
118
|
+
if (child.geometry) child.geometry.dispose();
|
|
119
|
+
if (child.material) {
|
|
120
|
+
if (Array.isArray(child.material)) child.material.forEach(m => m.dispose());
|
|
121
|
+
else child.material.dispose();
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
managedLODs.delete(id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get LOD stats for performance monitoring.
|
|
130
|
+
* @param {THREE.Camera} camera
|
|
131
|
+
* @returns {Array<{ id: string, currentLevel: number, distance: number }>}
|
|
132
|
+
*/
|
|
133
|
+
export function getLODStats(camera) {
|
|
134
|
+
const stats = [];
|
|
135
|
+
for (const [id, entry] of managedLODs) {
|
|
136
|
+
const dist = camera.position.distanceTo(entry.lod.position);
|
|
137
|
+
let currentLevel = 0;
|
|
138
|
+
for (let i = entry.levels.length - 1; i >= 0; i--) {
|
|
139
|
+
if (dist >= entry.levels[i].distance) {
|
|
140
|
+
currentLevel = i;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
stats.push({ id, currentLevel, distance: Math.round(dist) });
|
|
145
|
+
}
|
|
146
|
+
return stats;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Dispose all managed LODs.
|
|
151
|
+
* @param {THREE.Scene} scene
|
|
152
|
+
*/
|
|
153
|
+
export function disposeAllLODs(scene) {
|
|
154
|
+
for (const id of managedLODs.keys()) {
|
|
155
|
+
disposeLOD(id, scene);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get the default distance thresholds.
|
|
161
|
+
* @returns {number[]}
|
|
162
|
+
*/
|
|
163
|
+
export function getDefaultThresholds() {
|
|
164
|
+
return [...DEFAULT_THRESHOLDS];
|
|
165
|
+
}
|