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.
Files changed (166) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +158 -592
  3. package/SECURITY.md +3 -3
  4. package/USAGE.md +151 -0
  5. package/agent-contracts.js +447 -0
  6. package/api-agents.js +760 -0
  7. package/autonomy/decision-v2.js +380 -0
  8. package/autonomy/watchdog-policy.js +572 -0
  9. package/cli.js +454 -298
  10. package/conversation-templates/autonomous-feature.json +83 -22
  11. package/conversation-templates/code-review.json +69 -21
  12. package/conversation-templates/debug-squad.json +69 -21
  13. package/conversation-templates/feature-build.json +69 -21
  14. package/conversation-templates/research-write.json +69 -21
  15. package/dashboard.html +3148 -174
  16. package/dashboard.js +823 -786
  17. package/data-dir.js +58 -0
  18. package/docs/architecture/branch-semantics.md +157 -0
  19. package/docs/architecture/canonical-event-schema.md +88 -0
  20. package/docs/architecture/markdown-workspace.md +183 -0
  21. package/docs/architecture/runtime-contract.md +459 -0
  22. package/docs/architecture/runtime-migration-hardening.md +64 -0
  23. package/events/hooks.js +154 -0
  24. package/events/log.js +457 -0
  25. package/events/replay.js +33 -0
  26. package/events/schema.js +432 -0
  27. package/managed-team-integration.js +261 -0
  28. package/office/agents.js +704 -597
  29. package/office/animation.js +1 -1
  30. package/office/assets/arcade-cabinet.js +141 -0
  31. package/office/assets/archway.js +77 -0
  32. package/office/assets/bar-counter.js +91 -0
  33. package/office/assets/bar-stool.js +71 -0
  34. package/office/assets/beanbag.js +64 -0
  35. package/office/assets/bench.js +99 -0
  36. package/office/assets/bollard.js +87 -0
  37. package/office/assets/cactus.js +100 -0
  38. package/office/assets/carpet-tile.js +46 -0
  39. package/office/assets/chair.js +123 -0
  40. package/office/assets/chandelier.js +107 -0
  41. package/office/assets/coffee-machine.js +95 -0
  42. package/office/assets/coffee-table.js +81 -0
  43. package/office/assets/column.js +95 -0
  44. package/office/assets/desk-lamp.js +102 -0
  45. package/office/assets/desk.js +76 -0
  46. package/office/assets/dining-table.js +105 -0
  47. package/office/assets/door.js +70 -0
  48. package/office/assets/dual-monitor.js +72 -0
  49. package/office/assets/fence.js +76 -0
  50. package/office/assets/filing-cabinet.js +111 -0
  51. package/office/assets/floor-lamp.js +69 -0
  52. package/office/assets/floor-tile.js +54 -0
  53. package/office/assets/flower-pot.js +76 -0
  54. package/office/assets/foosball.js +95 -0
  55. package/office/assets/fridge.js +99 -0
  56. package/office/assets/gaming-chair.js +154 -0
  57. package/office/assets/gaming-desk.js +105 -0
  58. package/office/assets/glass-door.js +72 -0
  59. package/office/assets/glass-wall.js +64 -0
  60. package/office/assets/half-wall.js +49 -0
  61. package/office/assets/hanging-plant.js +112 -0
  62. package/office/assets/index.js +151 -0
  63. package/office/assets/indoor-tree.js +90 -0
  64. package/office/assets/l-sofa.js +153 -0
  65. package/office/assets/marble-floor.js +64 -0
  66. package/office/assets/materials.js +40 -0
  67. package/office/assets/meeting-table.js +88 -0
  68. package/office/assets/microwave.js +94 -0
  69. package/office/assets/monitor.js +67 -0
  70. package/office/assets/neon-strip.js +73 -0
  71. package/office/assets/painting.js +84 -0
  72. package/office/assets/palm-tree.js +108 -0
  73. package/office/assets/pc-tower.js +91 -0
  74. package/office/assets/pendant-light.js +67 -0
  75. package/office/assets/ping-pong.js +114 -0
  76. package/office/assets/plant.js +72 -0
  77. package/office/assets/planter-box.js +95 -0
  78. package/office/assets/pool-table.js +94 -0
  79. package/office/assets/printer.js +113 -0
  80. package/office/assets/reception-desk.js +133 -0
  81. package/office/assets/rug.js +78 -0
  82. package/office/assets/sculpture.js +85 -0
  83. package/office/assets/server-rack.js +98 -0
  84. package/office/assets/sink.js +109 -0
  85. package/office/assets/sofa.js +106 -0
  86. package/office/assets/speaker.js +83 -0
  87. package/office/assets/spotlight.js +83 -0
  88. package/office/assets/street-lamp.js +97 -0
  89. package/office/assets/trash-can.js +83 -0
  90. package/office/assets/treadmill.js +126 -0
  91. package/office/assets/trophy.js +89 -0
  92. package/office/assets/tv-screen.js +79 -0
  93. package/office/assets/vase.js +84 -0
  94. package/office/assets/wall-clock.js +84 -0
  95. package/office/assets/wall.js +53 -0
  96. package/office/assets/water-cooler.js +146 -0
  97. package/office/assets/whiteboard.js +115 -0
  98. package/office/assets.js +3 -431
  99. package/office/builder.js +791 -355
  100. package/office/campus-env.js +1012 -1119
  101. package/office/environment.js +2 -0
  102. package/office/gallery.js +997 -0
  103. package/office/index.js +165 -61
  104. package/office/navigation.js +173 -152
  105. package/office/player.js +178 -68
  106. package/office/robot-character.js +272 -0
  107. package/office/spectator-camera.js +33 -10
  108. package/office/state.js +2 -0
  109. package/office/world-save.js +35 -4
  110. package/package.json +57 -3
  111. package/providers/comfyui.js +383 -0
  112. package/providers/dalle.js +79 -0
  113. package/providers/gemini.js +181 -0
  114. package/providers/ollama.js +184 -0
  115. package/providers/replicate.js +115 -0
  116. package/providers/zai.js +183 -0
  117. package/runtime-descriptor.js +270 -0
  118. package/scripts/check-agent-contract-advisory.js +132 -0
  119. package/scripts/check-api-agent-parity.js +277 -0
  120. package/scripts/check-autonomy-v2-decision.js +207 -0
  121. package/scripts/check-autonomy-v2-execution.js +588 -0
  122. package/scripts/check-autonomy-v2-watchdog.js +224 -0
  123. package/scripts/check-branch-fork-snapshot.js +337 -0
  124. package/scripts/check-branch-isolation.js +787 -0
  125. package/scripts/check-branch-semantics.js +139 -0
  126. package/scripts/check-dashboard-control-plane.js +1304 -0
  127. package/scripts/check-docs-onboarding.js +490 -0
  128. package/scripts/check-event-schema.js +276 -0
  129. package/scripts/check-evidence-completion.js +239 -0
  130. package/scripts/check-invariants.js +992 -0
  131. package/scripts/check-lifecycle-hooks.js +525 -0
  132. package/scripts/check-managed-team-integration.js +166 -0
  133. package/scripts/check-markdown-workspace-export.js +548 -0
  134. package/scripts/check-markdown-workspace-safety.js +347 -0
  135. package/scripts/check-markdown-workspace.js +136 -0
  136. package/scripts/check-message-replay.js +429 -0
  137. package/scripts/check-migration-hardening.js +300 -0
  138. package/scripts/check-performance-indexing.js +272 -0
  139. package/scripts/check-provider-capabilities.js +316 -0
  140. package/scripts/check-runtime-contract.js +109 -0
  141. package/scripts/check-session-aware-context.js +172 -0
  142. package/scripts/check-session-lifecycle.js +210 -0
  143. package/scripts/export-markdown-workspace.js +84 -0
  144. package/scripts/fixtures/message-replay/clean.jsonl +2 -0
  145. package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
  146. package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
  147. package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
  148. package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
  149. package/scripts/migrate-legacy-to-canonical.js +201 -0
  150. package/scripts/run-verification-suite.js +242 -0
  151. package/scripts/sync-packaged-docs.js +69 -0
  152. package/server.js +9546 -7214
  153. package/state/agents.js +161 -0
  154. package/state/canonical.js +3068 -0
  155. package/state/dashboard-queries.js +441 -0
  156. package/state/evidence.js +56 -0
  157. package/state/io.js +69 -0
  158. package/state/markdown-workspace.js +951 -0
  159. package/state/messages.js +669 -0
  160. package/state/sessions.js +683 -0
  161. package/state/tasks-workflows.js +92 -0
  162. package/templates/debate.json +2 -2
  163. package/templates/managed.json +4 -4
  164. package/templates/pair.json +2 -2
  165. package/templates/review.json +2 -2
  166. package/templates/team.json +3 -3
@@ -0,0 +1,78 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'rug',
6
+ name: 'Rug',
7
+ category: 'decor',
8
+ icon: 'Rg',
9
+ gridW: 3, gridD: 2, height: 0.02,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Main rug body — dark burgundy
14
+ var body = new THREE.Mesh(
15
+ new THREE.BoxGeometry(3.0, 0.012, 2.0),
16
+ mat(0x4a0a18, { roughness: 0.98 })
17
+ );
18
+ body.position.y = 0.006;
19
+ body.receiveShadow = true;
20
+ g.add(body);
21
+
22
+ // Gold outer border (4 strips)
23
+ var borderMat = mat(0xc9a227, { roughness: 0.60, metalness: 0.15 });
24
+
25
+ var borderN = new THREE.Mesh(
26
+ new THREE.BoxGeometry(3.0, 0.013, 0.12),
27
+ borderMat
28
+ );
29
+ borderN.position.set(0, 0.007, -0.94);
30
+ borderN.receiveShadow = true;
31
+ g.add(borderN);
32
+
33
+ var borderS = new THREE.Mesh(
34
+ new THREE.BoxGeometry(3.0, 0.013, 0.12),
35
+ borderMat
36
+ );
37
+ borderS.position.set(0, 0.007, 0.94);
38
+ borderS.receiveShadow = true;
39
+ g.add(borderS);
40
+
41
+ var borderW = new THREE.Mesh(
42
+ new THREE.BoxGeometry(0.12, 0.013, 2.0),
43
+ borderMat
44
+ );
45
+ borderW.position.set(-1.44, 0.007, 0);
46
+ borderW.receiveShadow = true;
47
+ g.add(borderW);
48
+
49
+ var borderE = new THREE.Mesh(
50
+ new THREE.BoxGeometry(0.12, 0.013, 2.0),
51
+ borderMat
52
+ );
53
+ borderE.position.set(1.44, 0.007, 0);
54
+ borderE.receiveShadow = true;
55
+ g.add(borderE);
56
+
57
+ // Inner accent border (thin dark line)
58
+ var innerMat = mat(0x2a0510, { roughness: 0.98 });
59
+
60
+ var inN = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.014, 0.04), innerMat);
61
+ inN.position.set(0, 0.008, -0.78);
62
+ g.add(inN);
63
+
64
+ var inS = new THREE.Mesh(new THREE.BoxGeometry(2.6, 0.014, 0.04), innerMat);
65
+ inS.position.set(0, 0.008, 0.78);
66
+ g.add(inS);
67
+
68
+ var inW = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.014, 1.6), innerMat);
69
+ inW.position.set(-1.28, 0.008, 0);
70
+ g.add(inW);
71
+
72
+ var inE = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.014, 1.6), innerMat);
73
+ inE.position.set(1.28, 0.008, 0);
74
+ g.add(inE);
75
+
76
+ return g;
77
+ }
78
+ };
@@ -0,0 +1,85 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'sculpture',
6
+ name: 'Sculpture',
7
+ category: 'decor',
8
+ icon: 'Sc',
9
+ gridW: 1, gridD: 1, height: 1.2,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Pedestal base — dark slab
14
+ var pedBase = new THREE.Mesh(
15
+ new THREE.BoxGeometry(0.55, 0.06, 0.55),
16
+ PAL.marbleBlack()
17
+ );
18
+ pedBase.position.y = 0.03;
19
+ pedBase.castShadow = true;
20
+ pedBase.receiveShadow = true;
21
+ g.add(pedBase);
22
+
23
+ // Pedestal body
24
+ var pedBody = new THREE.Mesh(
25
+ new THREE.BoxGeometry(0.5, 1.0, 0.5),
26
+ mat(0x15171e, { roughness: 0.25, metalness: 0.05 })
27
+ );
28
+ pedBody.position.y = 0.56;
29
+ pedBody.castShadow = true;
30
+ pedBody.receiveShadow = true;
31
+ g.add(pedBody);
32
+
33
+ // Pedestal top cap
34
+ var pedTop = new THREE.Mesh(
35
+ new THREE.BoxGeometry(0.55, 0.05, 0.55),
36
+ PAL.marbleBlack()
37
+ );
38
+ pedTop.position.y = 1.085;
39
+ pedTop.castShadow = true;
40
+ g.add(pedTop);
41
+
42
+ // Chrome sphere (abstract sculpture)
43
+ var sphere = new THREE.Mesh(
44
+ new THREE.SphereGeometry(0.2, 32, 32),
45
+ PAL.chrome()
46
+ );
47
+ sphere.position.y = 1.31;
48
+ sphere.castShadow = true;
49
+ g.add(sphere);
50
+
51
+ // Small accent ring around base of sphere
52
+ var ring = new THREE.Mesh(
53
+ new THREE.TorusGeometry(0.21, 0.015, 12, 40),
54
+ PAL.gold()
55
+ );
56
+ ring.position.y = 1.11;
57
+ ring.rotation.x = Math.PI / 2;
58
+ ring.castShadow = true;
59
+ g.add(ring);
60
+
61
+ // Abstract protruding spike (artistic detail)
62
+ var spike = new THREE.Mesh(
63
+ new THREE.ConeGeometry(0.03, 0.25, 8),
64
+ PAL.chromeBrushed()
65
+ );
66
+ spike.position.set(0.12, 1.48, 0.08);
67
+ spike.rotation.z = -0.5;
68
+ spike.rotation.x = 0.3;
69
+ spike.castShadow = true;
70
+ g.add(spike);
71
+
72
+ // Second smaller spike
73
+ var spike2 = new THREE.Mesh(
74
+ new THREE.ConeGeometry(0.02, 0.18, 8),
75
+ PAL.chromeBrushed()
76
+ );
77
+ spike2.position.set(-0.14, 1.44, -0.06);
78
+ spike2.rotation.z = 0.6;
79
+ spike2.rotation.x = -0.2;
80
+ spike2.castShadow = true;
81
+ g.add(spike2);
82
+
83
+ return g;
84
+ }
85
+ };
@@ -0,0 +1,98 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'server_rack',
6
+ name: 'Server Rack',
7
+ category: 'tech',
8
+ icon: 'SR',
9
+ gridW: 1, gridD: 1, height: 1.5,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ var W = 0.60;
14
+ var H = 1.50;
15
+ var D = 0.55;
16
+ var halfH = H / 2;
17
+
18
+ var cabinetMat = mat(0x0e1014, { roughness: 0.45, metalness: 0.30 });
19
+ var frameMat = PAL.chromeBrushed();
20
+ var ventMat = mat(0x1a1d22, { roughness: 0.60, metalness: 0.15 });
21
+
22
+ // Main cabinet body
23
+ var body = new THREE.Mesh(new THREE.BoxGeometry(W, H, D), cabinetMat);
24
+ body.position.y = halfH;
25
+ body.castShadow = true;
26
+ g.add(body);
27
+
28
+ // Front door frame
29
+ var doorFrame = new THREE.Mesh(new THREE.BoxGeometry(W + 0.01, H + 0.01, 0.015), frameMat);
30
+ doorFrame.position.set(0, halfH, D / 2 + 0.005);
31
+ g.add(doorFrame);
32
+
33
+ // Front perforated panel (dark inset)
34
+ var frontPanel = new THREE.Mesh(new THREE.BoxGeometry(W - 0.04, H - 0.04, 0.010), ventMat);
35
+ frontPanel.position.set(0, halfH, D / 2 + 0.006);
36
+ g.add(frontPanel);
37
+
38
+ // Ventilation grille rows (horizontal slits on front)
39
+ var grilleMat = mat(0x090b0e, { roughness: 0.50 });
40
+ var grillCount = 10;
41
+ for (var i = 0; i < grillCount; i++) {
42
+ var gy = 0.10 + i * (H - 0.18) / (grillCount - 1);
43
+ var grille = new THREE.Mesh(
44
+ new THREE.BoxGeometry(W - 0.08, 0.012, 0.015),
45
+ grilleMat
46
+ );
47
+ grille.position.set(0, gy, D / 2 + 0.012);
48
+ g.add(grille);
49
+ }
50
+
51
+ // 1U server unit rails (thin horizontal dividers)
52
+ var railMat = mat(0x2a2e36, { roughness: 0.55, metalness: 0.25 });
53
+ var railCount = 6;
54
+ for (var r = 0; r < railCount; r++) {
55
+ var ry = 0.15 + r * (H - 0.25) / railCount;
56
+ var rail = new THREE.Mesh(new THREE.BoxGeometry(W - 0.02, 0.006, 0.008), railMat);
57
+ rail.position.set(0, ry, D / 2 + 0.003);
58
+ g.add(rail);
59
+ }
60
+
61
+ // LED indicator dots (4, stacked vertically on right side of front)
62
+ var ledColors = [0x00ff88, 0x00aaff, 0xffcc00, 0xff3300];
63
+ ledColors.forEach(function(col, idx) {
64
+ var led = new THREE.Mesh(
65
+ new THREE.SphereGeometry(0.009, 10, 10),
66
+ mat(col, { emissive: col, emissiveIntensity: 1.0 })
67
+ );
68
+ led.position.set(W / 2 - 0.045, 0.95 + idx * 0.10, D / 2 + 0.018);
69
+ g.add(led);
70
+ });
71
+
72
+ // Side vent strips (left and right faces)
73
+ [-1, 1].forEach(function(side) {
74
+ var sideVent = new THREE.Mesh(
75
+ new THREE.BoxGeometry(0.008, H * 0.65, D - 0.08),
76
+ ventMat
77
+ );
78
+ sideVent.position.set(side * (W / 2 + 0.001), halfH, 0);
79
+ g.add(sideVent);
80
+ });
81
+
82
+ // Top exhaust panel
83
+ var topVent = new THREE.Mesh(new THREE.BoxGeometry(W - 0.06, 0.010, D - 0.06), ventMat);
84
+ topVent.position.y = H + 0.001;
85
+ g.add(topVent);
86
+
87
+ // Caster feet (4 corners)
88
+ var casterMat = PAL.rubber();
89
+ [[-W * 0.38, -D * 0.38], [W * 0.38, -D * 0.38],
90
+ [-W * 0.38, D * 0.38], [W * 0.38, D * 0.38]].forEach(function(pos) {
91
+ var caster = new THREE.Mesh(new THREE.CylinderGeometry(0.025, 0.025, 0.040, 12), casterMat);
92
+ caster.position.set(pos[0], 0.020, pos[1]);
93
+ g.add(caster);
94
+ });
95
+
96
+ return g;
97
+ }
98
+ };
@@ -0,0 +1,109 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'sink',
6
+ name: 'Kitchen Sink',
7
+ category: 'kitchen',
8
+ icon: 'Sk',
9
+ gridW: 1, gridD: 1, height: 0.9,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Cabinet base body
14
+ var cabinet = new THREE.Mesh(
15
+ new THREE.BoxGeometry(0.80, 0.82, 0.60),
16
+ mat(0x16181d, { roughness: 0.50 })
17
+ );
18
+ cabinet.position.y = 0.41;
19
+ cabinet.castShadow = true;
20
+ cabinet.receiveShadow = true;
21
+ g.add(cabinet);
22
+
23
+ // Cabinet door face
24
+ var door = new THREE.Mesh(
25
+ new THREE.BoxGeometry(0.74, 0.70, 0.012),
26
+ mat(0x1c1f26, { roughness: 0.45, metalness: 0.10 })
27
+ );
28
+ door.position.set(0, 0.42, 0.306);
29
+ g.add(door);
30
+
31
+ // Door handle (small chrome bar)
32
+ var doorHandle = new THREE.Mesh(
33
+ new THREE.BoxGeometry(0.18, 0.022, 0.022),
34
+ PAL.chrome()
35
+ );
36
+ doorHandle.position.set(0, 0.60, 0.320);
37
+ g.add(doorHandle);
38
+
39
+ // Countertop surface
40
+ var countertop = new THREE.Mesh(
41
+ new THREE.BoxGeometry(0.80, 0.04, 0.60),
42
+ mat(0x1e2128, { roughness: 0.25, metalness: 0.15 })
43
+ );
44
+ countertop.position.y = 0.86;
45
+ countertop.castShadow = true;
46
+ g.add(countertop);
47
+
48
+ // Basin recess (dark stainless interior)
49
+ var basin = new THREE.Mesh(
50
+ new THREE.BoxGeometry(0.52, 0.16, 0.36),
51
+ mat(0x888888, { roughness: 0.20, metalness: 0.75 })
52
+ );
53
+ basin.position.set(0, 0.80, -0.02);
54
+ basin.castShadow = true;
55
+ g.add(basin);
56
+
57
+ // Basin inner bottom
58
+ var basinBase = new THREE.Mesh(
59
+ new THREE.BoxGeometry(0.50, 0.012, 0.34),
60
+ mat(0x9a9a9a, { roughness: 0.18, metalness: 0.80 })
61
+ );
62
+ basinBase.position.set(0, 0.722, -0.02);
63
+ g.add(basinBase);
64
+
65
+ // Basin drain dot
66
+ var drain = new THREE.Mesh(
67
+ new THREE.CylinderGeometry(0.025, 0.025, 0.01, 12),
68
+ mat(0x444444, { roughness: 0.30, metalness: 0.60 })
69
+ );
70
+ drain.position.set(0.12, 0.716, 0.06);
71
+ g.add(drain);
72
+
73
+ // Chrome faucet base
74
+ var faucetBase = new THREE.Mesh(
75
+ new THREE.CylinderGeometry(0.030, 0.035, 0.04, 12),
76
+ PAL.chrome()
77
+ );
78
+ faucetBase.position.set(0, 0.892, -0.20);
79
+ g.add(faucetBase);
80
+
81
+ // Faucet neck (vertical rise)
82
+ var neck = new THREE.Mesh(
83
+ new THREE.CylinderGeometry(0.014, 0.014, 0.18, 10),
84
+ PAL.chrome()
85
+ );
86
+ neck.position.set(0, 0.98, -0.20);
87
+ g.add(neck);
88
+
89
+ // Faucet arc (horizontal spout)
90
+ var spout = new THREE.Mesh(
91
+ new THREE.CylinderGeometry(0.012, 0.012, 0.22, 10),
92
+ PAL.chrome()
93
+ );
94
+ spout.rotation.x = Math.PI / 2;
95
+ spout.position.set(0, 1.07, -0.09);
96
+ spout.castShadow = true;
97
+ g.add(spout);
98
+
99
+ // Faucet spout tip (pointing down)
100
+ var tip = new THREE.Mesh(
101
+ new THREE.CylinderGeometry(0.018, 0.012, 0.04, 8),
102
+ PAL.chrome()
103
+ );
104
+ tip.position.set(0, 1.055, 0.02);
105
+ g.add(tip);
106
+
107
+ return g;
108
+ }
109
+ };
@@ -0,0 +1,106 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'sofa',
6
+ name: 'Sofa',
7
+ category: 'furniture',
8
+ icon: 'Sf',
9
+ gridW: 3, gridD: 1, height: 0.9,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Base platform (structural frame)
14
+ var base = new THREE.Mesh(
15
+ new THREE.BoxGeometry(3.0, 0.18, 0.9),
16
+ mat(0x17191f, { roughness: 0.6, metalness: 0.05 })
17
+ );
18
+ base.position.y = 0.09;
19
+ base.castShadow = true;
20
+ base.receiveShadow = true;
21
+ g.add(base);
22
+
23
+ // Seat cushion surface (3 visible cushions merged as one pad, split by seams)
24
+ var seatPad = new THREE.Mesh(
25
+ new THREE.BoxGeometry(2.88, 0.16, 0.76),
26
+ PAL.fabric()
27
+ );
28
+ seatPad.position.y = 0.26;
29
+ seatPad.castShadow = true;
30
+ seatPad.receiveShadow = true;
31
+ g.add(seatPad);
32
+
33
+ // 3 cushion seams (thin dark lines on top of seat pad)
34
+ var seamMat = mat(0x1a1c22, { roughness: 0.9 });
35
+ [-0.96, 0.96].forEach(function(x) {
36
+ var seam = new THREE.Mesh(new THREE.BoxGeometry(0.015, 0.162, 0.76), seamMat);
37
+ seam.position.set(x, 0.26, 0);
38
+ g.add(seam);
39
+ });
40
+
41
+ // Backrest (full width)
42
+ var back = new THREE.Mesh(
43
+ new THREE.BoxGeometry(2.88, 0.56, 0.18),
44
+ PAL.fabric()
45
+ );
46
+ back.position.set(0, 0.62, -0.35);
47
+ back.castShadow = true;
48
+ g.add(back);
49
+
50
+ // Back top cap (slightly overhanging)
51
+ var backCap = new THREE.Mesh(
52
+ new THREE.BoxGeometry(3.0, 0.06, 0.20),
53
+ mat(0x1e2028, { roughness: 0.5 })
54
+ );
55
+ backCap.position.set(0, 0.92, -0.34);
56
+ backCap.castShadow = true;
57
+ g.add(backCap);
58
+
59
+ // Left arm
60
+ var armMat = PAL.fabric();
61
+ var leftArm = new THREE.Mesh(
62
+ new THREE.BoxGeometry(0.18, 0.46, 0.9),
63
+ armMat
64
+ );
65
+ leftArm.position.set(-1.41, 0.41, 0);
66
+ leftArm.castShadow = true;
67
+ g.add(leftArm);
68
+
69
+ // Right arm
70
+ var rightArm = leftArm.clone();
71
+ rightArm.position.x = 1.41;
72
+ g.add(rightArm);
73
+
74
+ // Arm top caps
75
+ var armCapMat = mat(0x1e2028, { roughness: 0.45 });
76
+ var leftArmCap = new THREE.Mesh(new THREE.BoxGeometry(0.20, 0.04, 0.92), armCapMat);
77
+ leftArmCap.position.set(-1.41, 0.65, 0);
78
+ g.add(leftArmCap);
79
+ var rightArmCap = leftArmCap.clone();
80
+ rightArmCap.position.x = 1.41;
81
+ g.add(rightArmCap);
82
+
83
+ // 3 seat cushions (individual, sitting on the pad)
84
+ var cushionMat = mat(0x252830, { roughness: 0.92 });
85
+ [-0.96, 0, 0.96].forEach(function(x) {
86
+ var cushion = new THREE.Mesh(
87
+ new THREE.BoxGeometry(0.88, 0.10, 0.72),
88
+ cushionMat
89
+ );
90
+ cushion.position.set(x, 0.37, 0.01);
91
+ cushion.castShadow = true;
92
+ g.add(cushion);
93
+ });
94
+
95
+ // 4 low chrome legs
96
+ var legMat = PAL.chrome();
97
+ var legGeo = new THREE.CylinderGeometry(0.025, 0.02, 0.09, 8);
98
+ [[-1.35, -0.35], [1.35, -0.35], [-1.35, 0.38], [1.35, 0.38]].forEach(function(p) {
99
+ var leg = new THREE.Mesh(legGeo, legMat);
100
+ leg.position.set(p[0], 0.045, p[1]);
101
+ g.add(leg);
102
+ });
103
+
104
+ return g;
105
+ }
106
+ };
@@ -0,0 +1,83 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'speaker',
6
+ name: 'Bluetooth Speaker',
7
+ category: 'tech',
8
+ icon: 'Sp',
9
+ gridW: 1, gridD: 1, height: 0.25,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ var R = 0.075; // radius
14
+ var LEN = 0.15; // cylinder length (axis = Z)
15
+
16
+ // Main body — anodised dark cylinder
17
+ var body = new THREE.Mesh(
18
+ new THREE.CylinderGeometry(R, R, LEN, 32),
19
+ mat(0x1a1c20, { roughness: 0.38, metalness: 0.30 })
20
+ );
21
+ body.rotation.x = Math.PI / 2;
22
+ body.position.y = R;
23
+ body.castShadow = true;
24
+ g.add(body);
25
+
26
+ // Woven fabric grille band (slightly lighter, rough)
27
+ var grille = new THREE.Mesh(
28
+ new THREE.CylinderGeometry(R + 0.001, R + 0.001, LEN * 0.70, 32),
29
+ mat(0x2a2d32, { roughness: 0.92 })
30
+ );
31
+ grille.rotation.x = Math.PI / 2;
32
+ grille.position.y = R;
33
+ g.add(grille);
34
+
35
+ // Driver cone (front face)
36
+ var cone = new THREE.Mesh(
37
+ new THREE.CylinderGeometry(0.045, 0.035, 0.010, 32),
38
+ mat(0x0e0f10, { roughness: 0.50, metalness: 0.20 })
39
+ );
40
+ cone.rotation.x = Math.PI / 2;
41
+ cone.position.set(0, R, LEN / 2 + 0.004);
42
+ g.add(cone);
43
+
44
+ // Dust cap center of driver
45
+ var dustCap = new THREE.Mesh(
46
+ new THREE.SphereGeometry(0.018, 16, 8, 0, Math.PI * 2, 0, Math.PI / 2),
47
+ mat(0x080808, { roughness: 0.60 })
48
+ );
49
+ dustCap.rotation.x = Math.PI / 2;
50
+ dustCap.position.set(0, R, LEN / 2 + 0.014);
51
+ g.add(dustCap);
52
+
53
+ // LED ring (around driver, glowing cyan)
54
+ var ledRing = new THREE.Mesh(
55
+ new THREE.TorusGeometry(0.048, 0.005, 8, 32),
56
+ mat(0x00e5ff, { emissive: 0x00c8e0, emissiveIntensity: 0.85 })
57
+ );
58
+ ledRing.rotation.x = Math.PI / 2;
59
+ ledRing.position.set(0, R, LEN / 2 + 0.006);
60
+ g.add(ledRing);
61
+
62
+ // End caps (chrome rings each side)
63
+ [-1, 1].forEach(function(side) {
64
+ var cap = new THREE.Mesh(
65
+ new THREE.CylinderGeometry(R, R, 0.012, 32),
66
+ PAL.chrome()
67
+ );
68
+ cap.rotation.x = Math.PI / 2;
69
+ cap.position.set(0, R, side * (LEN / 2 + 0.004));
70
+ g.add(cap);
71
+ });
72
+
73
+ // Volume button strip on top of body
74
+ var volStrip = new THREE.Mesh(
75
+ new THREE.BoxGeometry(0.055, 0.009, 0.014),
76
+ mat(0x888888, { roughness: 0.25, metalness: 0.70 })
77
+ );
78
+ volStrip.position.set(0, R * 2 - 0.005, 0.02);
79
+ g.add(volStrip);
80
+
81
+ return g;
82
+ }
83
+ };
@@ -0,0 +1,83 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'spotlight',
6
+ name: 'Track Spotlight',
7
+ category: 'lighting',
8
+ icon: 'SL',
9
+ gridW: 1, gridD: 1, height: 2.5,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ var mountY = 2.50; // ceiling mount height
14
+ var headY = mountY - 0.08;
15
+ var chromeMat = PAL.chrome();
16
+ var bodyMat = mat(0x1c1e24, { roughness: 0.35, metalness: 0.55 });
17
+
18
+ // Track rail segment (short, ceiling-mounted)
19
+ var track = new THREE.Mesh(
20
+ new THREE.BoxGeometry(0.35, 0.028, 0.038),
21
+ mat(0x888888, { roughness: 0.28, metalness: 0.70 })
22
+ );
23
+ track.position.y = mountY;
24
+ g.add(track);
25
+
26
+ // Mount clip connecting housing to track
27
+ var clip = new THREE.Mesh(
28
+ new THREE.BoxGeometry(0.028, 0.050, 0.028),
29
+ chromeMat
30
+ );
31
+ clip.position.y = mountY - 0.025;
32
+ g.add(clip);
33
+
34
+ // Main spotlight housing (tapered cylinder)
35
+ var housing = new THREE.Mesh(
36
+ new THREE.CylinderGeometry(0.040, 0.055, 0.110, 20),
37
+ bodyMat
38
+ );
39
+ housing.position.y = headY - 0.055;
40
+ housing.castShadow = true;
41
+ g.add(housing);
42
+
43
+ // Chrome housing ring (top)
44
+ var topRing = new THREE.Mesh(
45
+ new THREE.TorusGeometry(0.042, 0.005, 8, 20),
46
+ chromeMat
47
+ );
48
+ topRing.position.y = headY;
49
+ g.add(topRing);
50
+
51
+ // Inner reflector bowl
52
+ var reflector = new THREE.Mesh(
53
+ new THREE.CylinderGeometry(0.030, 0.048, 0.070, 20, 1, true),
54
+ mat(0xc8c8c8, { roughness: 0.05, metalness: 0.95, side: THREE.BackSide })
55
+ );
56
+ reflector.position.y = headY - 0.060;
57
+ g.add(reflector);
58
+
59
+ // Lens glass (front of housing)
60
+ var lens = new THREE.Mesh(
61
+ new THREE.CircleGeometry(0.038, 24),
62
+ mat(0xdde8ff, { transparent: true, opacity: 0.35, roughness: 0.02, emissive: 0xffffff, emissiveIntensity: 0.25 })
63
+ );
64
+ lens.position.y = headY - 0.112;
65
+ lens.rotation.x = Math.PI / 2;
66
+ g.add(lens);
67
+
68
+ // Focus ring (chrome band around lens end)
69
+ var focusRing = new THREE.Mesh(
70
+ new THREE.TorusGeometry(0.040, 0.006, 8, 20),
71
+ chromeMat
72
+ );
73
+ focusRing.position.y = headY - 0.113;
74
+ g.add(focusRing);
75
+
76
+ // Focused point light (aimed downward)
77
+ var light = new THREE.PointLight(0xffffff, 0.4, 6);
78
+ light.position.y = headY - 0.12;
79
+ g.add(light);
80
+
81
+ return g;
82
+ }
83
+ };