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.
Files changed (39) hide show
  1. package/CHANGELOG.md +640 -540
  2. package/README.md +592 -415
  3. package/cli.js +1089 -589
  4. package/conversation-templates/autonomous-feature.json +22 -0
  5. package/conversation-templates/code-review.json +21 -11
  6. package/conversation-templates/debug-squad.json +21 -11
  7. package/conversation-templates/feature-build.json +21 -11
  8. package/conversation-templates/research-write.json +21 -11
  9. package/dashboard.html +9250 -7771
  10. package/dashboard.js +1232 -29
  11. package/office/agents.js +148 -4
  12. package/office/animation.js +68 -0
  13. package/office/assets.js +431 -0
  14. package/office/builder.js +355 -0
  15. package/office/building-interior.js +261 -0
  16. package/office/campus-env.js +119 -23
  17. package/office/car-hud.js +368 -0
  18. package/office/daynight.js +221 -0
  19. package/office/economy-hud.js +432 -0
  20. package/office/economy-ui.js +238 -0
  21. package/office/environment.js +818 -808
  22. package/office/face.js +65 -0
  23. package/office/fast-travel.js +215 -0
  24. package/office/hq-building.js +295 -0
  25. package/office/index.js +1095 -423
  26. package/office/instancing.js +160 -0
  27. package/office/lod-manager.js +165 -0
  28. package/office/multiplayer-hud.js +428 -0
  29. package/office/net-client.js +299 -0
  30. package/office/particles.js +172 -0
  31. package/office/player.js +658 -436
  32. package/office/post-processing.js +82 -0
  33. package/office/sky.js +319 -0
  34. package/office/street-furniture.js +308 -0
  35. package/office/vehicle.js +455 -0
  36. package/office/world-save.js +91 -0
  37. package/package.json +59 -59
  38. package/server.js +7190 -4685
  39. 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
+ }