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,40 @@
1
+ // Shared material cache for all assets — prevents duplicate materials
2
+ import * as THREE from 'three';
3
+
4
+ var _cache = {};
5
+
6
+ export function mat(color, opts) {
7
+ var key = color + JSON.stringify(opts || {});
8
+ if (!_cache[key]) {
9
+ _cache[key] = new THREE.MeshStandardMaterial(Object.assign({ color: color }, opts || {}));
10
+ }
11
+ return _cache[key];
12
+ }
13
+
14
+ // Pre-defined palette shortcuts
15
+ export var PAL = {
16
+ marbleBlack: function() { return mat(0x1a1c22, { roughness: 0.12, metalness: 0.05 }); },
17
+ marbleWhite: function() { return mat(0xf0ece4, { roughness: 0.15, metalness: 0.05 }); },
18
+ walnutDark: function() { return mat(0x3a2210, { roughness: 0.55 }); },
19
+ walnutLight: function() { return mat(0x8B5E3C, { roughness: 0.50 }); },
20
+ chrome: function() { return mat(0xd0d0d0, { roughness: 0.08, metalness: 0.85 }); },
21
+ chromeBrushed: function() { return mat(0x999999, { roughness: 0.25, metalness: 0.70 }); },
22
+ glass: function() { return mat(0xaaccee, { transparent: true, opacity: 0.25, roughness: 0.05, side: THREE.DoubleSide }); },
23
+ glassFrosted: function() { return mat(0xd0d8e8, { transparent: true, opacity: 0.50, roughness: 0.40, side: THREE.DoubleSide }); },
24
+ concrete: function() { return mat(0x2a2d35, { roughness: 0.85 }); },
25
+ leatherBlack: function() { return mat(0x1a1a1a, { roughness: 0.70 }); },
26
+ leatherCognac: function() { return mat(0x8B4513, { roughness: 0.65 }); },
27
+ gold: function() { return mat(0xd4af37, { roughness: 0.30, metalness: 0.70 }); },
28
+ darkMetal: function() { return mat(0x111111, { roughness: 0.40, metalness: 0.20 }); },
29
+ fabric: function() { return mat(0x2a2d3a, { roughness: 0.95 }); },
30
+ rubber: function() { return mat(0x2a2a2a, { roughness: 0.95 }); },
31
+ greenFelt: function() { return mat(0x006633, { roughness: 0.90 }); },
32
+ leaf: function() { return mat(0x2d8a4e, { roughness: 0.80 }); },
33
+ neonBlue: function() { return mat(0x58a6ff, { emissive: 0x58a6ff, emissiveIntensity: 0.6 }); },
34
+ neonPurple: function() { return mat(0xa855f7, { emissive: 0xa855f7, emissiveIntensity: 0.5 }); },
35
+ neonGreen: function() { return mat(0x22c55e, { emissive: 0x22c55e, emissiveIntensity: 0.5 }); },
36
+ neonRed: function() { return mat(0xef4444, { emissive: 0xef4444, emissiveIntensity: 0.5 }); },
37
+ neonCyan: function() { return mat(0x06b6d4, { emissive: 0x06b6d4, emissiveIntensity: 0.6 }); },
38
+ warmLight: function() { return mat(0xffeedd, { emissive: 0xffeedd, emissiveIntensity: 0.4, transparent: true, opacity: 0.8 }); },
39
+ screen: function() { return mat(0x333333, { emissive: 0x111122, emissiveIntensity: 0.3 }); },
40
+ };
@@ -0,0 +1,88 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'meeting-table',
6
+ name: 'Meeting Table',
7
+ category: 'office',
8
+ icon: 'MT',
9
+ gridW: 3, gridD: 2, height: 0.78,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Oval tabletop approximated with scaled cylinder
14
+ var topGeo = new THREE.CylinderGeometry(1.0, 1.0, 0.055, 32);
15
+ var top = new THREE.Mesh(topGeo, mat(0x1a1c22, { roughness: 0.25, metalness: 0.08 }));
16
+ top.scale.set(2.5, 1, 1.5);
17
+ top.position.y = 0.78;
18
+ top.castShadow = true;
19
+ top.receiveShadow = true;
20
+ g.add(top);
21
+
22
+ // Top surface inlay (lighter tone center oval)
23
+ var inlayGeo = new THREE.CylinderGeometry(0.88, 0.88, 0.008, 32);
24
+ var inlay = new THREE.Mesh(inlayGeo, mat(0x252830, { roughness: 0.30 }));
25
+ inlay.scale.set(2.3, 1, 1.3);
26
+ inlay.position.y = 0.81;
27
+ g.add(inlay);
28
+
29
+ // Gold edge ring band
30
+ var ringGeo = new THREE.TorusGeometry(1.0, 0.012, 8, 32);
31
+ var ring = new THREE.Mesh(ringGeo, PAL.gold());
32
+ ring.scale.set(2.5, 1.5, 1);
33
+ ring.rotation.x = Math.PI / 2;
34
+ ring.position.y = 0.808;
35
+ g.add(ring);
36
+
37
+ // Central pedestal column
38
+ var column = new THREE.Mesh(
39
+ new THREE.CylinderGeometry(0.08, 0.10, 0.52, 14),
40
+ PAL.chromeBrushed()
41
+ );
42
+ column.position.y = 0.52;
43
+ column.castShadow = true;
44
+ g.add(column);
45
+
46
+ // Upper column flare
47
+ var flareTop = new THREE.Mesh(
48
+ new THREE.CylinderGeometry(0.22, 0.08, 0.08, 14),
49
+ PAL.chrome()
50
+ );
51
+ flareTop.position.y = 0.80;
52
+ flareTop.castShadow = true;
53
+ g.add(flareTop);
54
+
55
+ // Lower column base flare
56
+ var flareBot = new THREE.Mesh(
57
+ new THREE.CylinderGeometry(0.10, 0.22, 0.08, 14),
58
+ PAL.chrome()
59
+ );
60
+ flareBot.position.y = 0.26;
61
+ flareBot.castShadow = true;
62
+ g.add(flareBot);
63
+
64
+ // Cross base (4-arm spider base)
65
+ var baseMat = PAL.chrome();
66
+ var armGeo = new THREE.BoxGeometry(1.4, 0.04, 0.08);
67
+ var armH = new THREE.Mesh(armGeo, baseMat);
68
+ armH.position.y = 0.05;
69
+ armH.castShadow = true;
70
+ g.add(armH);
71
+
72
+ var armV = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.04, 1.4), baseMat);
73
+ armV.position.y = 0.05;
74
+ armV.castShadow = true;
75
+ g.add(armV);
76
+
77
+ // 4 glide feet at base arm ends
78
+ var glideMat = mat(0x111111, { roughness: 0.9 });
79
+ var glideGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.018, 8);
80
+ [[0.68, 0], [-0.68, 0], [0, 0.68], [0, -0.68]].forEach(function(p) {
81
+ var glide = new THREE.Mesh(glideGeo, glideMat);
82
+ glide.position.set(p[0], 0.009, p[1]);
83
+ g.add(glide);
84
+ });
85
+
86
+ return g;
87
+ }
88
+ };
@@ -0,0 +1,94 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'microwave',
6
+ name: 'Microwave',
7
+ category: 'kitchen',
8
+ icon: 'MW',
9
+ gridW: 1, gridD: 1, height: 0.3,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Main body
14
+ var body = new THREE.Mesh(
15
+ new THREE.BoxGeometry(0.50, 0.30, 0.35),
16
+ mat(0x111318, { roughness: 0.42, metalness: 0.18 })
17
+ );
18
+ body.position.y = 0.15;
19
+ body.castShadow = true;
20
+ body.receiveShadow = true;
21
+ g.add(body);
22
+
23
+ // Glass door panel (left portion of front)
24
+ var glassDoor = new THREE.Mesh(
25
+ new THREE.BoxGeometry(0.30, 0.24, 0.012),
26
+ mat(0x1a2530, { transparent: true, opacity: 0.75, roughness: 0.12, metalness: 0.05 })
27
+ );
28
+ glassDoor.position.set(-0.07, 0.15, 0.182);
29
+ g.add(glassDoor);
30
+
31
+ // Glass door inner grill pattern (emissive dark mesh feel)
32
+ var grill = new THREE.Mesh(
33
+ new THREE.BoxGeometry(0.26, 0.20, 0.004),
34
+ mat(0x0d1015, { roughness: 0.80 })
35
+ );
36
+ grill.position.set(-0.07, 0.15, 0.188);
37
+ g.add(grill);
38
+
39
+ // Door frame around glass
40
+ var doorFrame = new THREE.Mesh(
41
+ new THREE.BoxGeometry(0.32, 0.26, 0.016),
42
+ mat(0x1c1f26, { roughness: 0.50, metalness: 0.12 })
43
+ );
44
+ doorFrame.position.set(-0.07, 0.15, 0.179);
45
+ g.add(doorFrame);
46
+
47
+ // Control panel section (right side)
48
+ var panel = new THREE.Mesh(
49
+ new THREE.BoxGeometry(0.12, 0.26, 0.01),
50
+ mat(0x16181e, { roughness: 0.50 })
51
+ );
52
+ panel.position.set(0.18, 0.15, 0.181);
53
+ g.add(panel);
54
+
55
+ // Small display screen
56
+ var display = new THREE.Mesh(
57
+ new THREE.BoxGeometry(0.09, 0.055, 0.008),
58
+ mat(0x001a0a, { emissive: 0x00cc44, emissiveIntensity: 0.85, roughness: 0.1 })
59
+ );
60
+ display.position.set(0.175, 0.22, 0.189);
61
+ g.add(display);
62
+
63
+ // Control buttons (small grid — 3 rows x 2 cols)
64
+ var btnMat = mat(0x2a2d35, { roughness: 0.60 });
65
+ var btnGeo = new THREE.BoxGeometry(0.030, 0.024, 0.010);
66
+ [0, 1, 2].forEach(function(row) {
67
+ [0, 1].forEach(function(col) {
68
+ var btn = new THREE.Mesh(btnGeo, btnMat);
69
+ btn.position.set(0.155 + col * 0.038, 0.16 - row * 0.032, 0.189);
70
+ g.add(btn);
71
+ });
72
+ });
73
+
74
+ // Chrome side vent strips
75
+ var ventMat = PAL.chromeBrushed();
76
+ var ventGeo = new THREE.BoxGeometry(0.006, 0.22, 0.025);
77
+ [-0.248, 0.248].forEach(function(x) {
78
+ var vent = new THREE.Mesh(ventGeo, ventMat);
79
+ vent.position.set(x, 0.15, 0);
80
+ g.add(vent);
81
+ });
82
+
83
+ // Bottom feet
84
+ var feetMat = mat(0x0a0a0a, { roughness: 0.90 });
85
+ var feetGeo = new THREE.BoxGeometry(0.04, 0.014, 0.04);
86
+ [[-0.20, 0.007, 0.14], [0.20, 0.007, 0.14], [-0.20, 0.007, -0.14], [0.20, 0.007, -0.14]].forEach(function(p) {
87
+ var foot = new THREE.Mesh(feetGeo, feetMat);
88
+ foot.position.set(p[0], p[1], p[2]);
89
+ g.add(foot);
90
+ });
91
+
92
+ return g;
93
+ }
94
+ };
@@ -0,0 +1,67 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'monitor',
6
+ name: 'Monitor',
7
+ category: 'tech',
8
+ icon: 'Mo',
9
+ gridW: 1, gridD: 1, height: 0.5,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Bezel outer frame
14
+ var bezel = new THREE.Mesh(
15
+ new THREE.BoxGeometry(0.50, 0.35, 0.03),
16
+ mat(0x111214, { roughness: 0.45, metalness: 0.10 })
17
+ );
18
+ bezel.position.y = 0.38;
19
+ bezel.castShadow = true;
20
+ g.add(bezel);
21
+
22
+ // Screen inner panel (emissive glow)
23
+ var screen = new THREE.Mesh(
24
+ new THREE.BoxGeometry(0.44, 0.29, 0.012),
25
+ mat(0x0a1628, { emissive: 0x1a4a8a, emissiveIntensity: 0.55, roughness: 0.05 })
26
+ );
27
+ screen.position.y = 0.38;
28
+ screen.position.z = 0.012;
29
+ g.add(screen);
30
+
31
+ // Screen glare strip (top highlight)
32
+ var glare = new THREE.Mesh(
33
+ new THREE.BoxGeometry(0.42, 0.04, 0.001),
34
+ mat(0xaaccff, { transparent: true, opacity: 0.08, roughness: 0.0 })
35
+ );
36
+ glare.position.set(0, 0.505, 0.019);
37
+ g.add(glare);
38
+
39
+ // Chrome neck / stem
40
+ var neck = new THREE.Mesh(
41
+ new THREE.BoxGeometry(0.035, 0.12, 0.035),
42
+ PAL.chrome()
43
+ );
44
+ neck.position.y = 0.175;
45
+ neck.castShadow = true;
46
+ g.add(neck);
47
+
48
+ // Chrome base disk
49
+ var base = new THREE.Mesh(
50
+ new THREE.CylinderGeometry(0.12, 0.14, 0.025, 24),
51
+ PAL.chrome()
52
+ );
53
+ base.position.y = 0.013;
54
+ base.castShadow = true;
55
+ g.add(base);
56
+
57
+ // Power LED dot
58
+ var led = new THREE.Mesh(
59
+ new THREE.SphereGeometry(0.008, 8, 8),
60
+ mat(0x00e5ff, { emissive: 0x00e5ff, emissiveIntensity: 1.0 })
61
+ );
62
+ led.position.set(0.18, 0.215, 0.016);
63
+ g.add(led);
64
+
65
+ return g;
66
+ }
67
+ };
@@ -0,0 +1,73 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'neon_strip',
6
+ name: 'LED Neon Strip',
7
+ category: 'lighting',
8
+ icon: 'NS',
9
+ gridW: 2, gridD: 1, height: 0.03,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ var W = 2.0;
14
+ var barH = 0.030;
15
+ var barD = 0.030;
16
+
17
+ // Aluminium channel body (housing for strip)
18
+ var channel = new THREE.Mesh(
19
+ new THREE.BoxGeometry(W, barH, barD),
20
+ mat(0x888888, { roughness: 0.20, metalness: 0.75 })
21
+ );
22
+ channel.position.y = barH / 2;
23
+ channel.castShadow = true;
24
+ g.add(channel);
25
+
26
+ // Neon diffuser bar (glowing blue, slightly protruding)
27
+ var strip = new THREE.Mesh(
28
+ new THREE.BoxGeometry(W - 0.02, barH * 0.45, barD * 0.55),
29
+ mat(0x58a6ff, { emissive: 0x58a6ff, emissiveIntensity: 1.4, transparent: true, opacity: 0.92, roughness: 0.0 })
30
+ );
31
+ strip.position.set(0, barH * 0.7, barD * 0.25);
32
+ g.add(strip);
33
+
34
+ // End caps (chrome)
35
+ [-W / 2, W / 2].forEach(function(x) {
36
+ var cap = new THREE.Mesh(
37
+ new THREE.BoxGeometry(0.012, barH + 0.002, barD + 0.002),
38
+ PAL.chrome()
39
+ );
40
+ cap.position.set(x, barH / 2, 0);
41
+ g.add(cap);
42
+ });
43
+
44
+ // Mounting clip pair (evenly spaced, mid-strip)
45
+ [-W * 0.30, W * 0.30].forEach(function(x) {
46
+ var clipBody = new THREE.Mesh(
47
+ new THREE.BoxGeometry(0.020, 0.025, 0.012),
48
+ mat(0x555555, { roughness: 0.45, metalness: 0.50 })
49
+ );
50
+ clipBody.position.set(x, -0.008, -barD / 2 - 0.005);
51
+ g.add(clipBody);
52
+
53
+ // Screw hole indicator
54
+ var screw = new THREE.Mesh(
55
+ new THREE.CylinderGeometry(0.004, 0.004, 0.013, 8),
56
+ mat(0x222222, { roughness: 0.50 })
57
+ );
58
+ screw.rotation.x = Math.PI / 2;
59
+ screw.position.set(x, -0.008, -barD / 2 - 0.012);
60
+ g.add(screw);
61
+ });
62
+
63
+ // Soft ambient glow blob (behind strip for wall bleed effect)
64
+ var glow = new THREE.Mesh(
65
+ new THREE.BoxGeometry(W + 0.10, barH * 2.5, 0.004),
66
+ mat(0x3388ff, { transparent: true, opacity: 0.12, emissive: 0x3388ff, emissiveIntensity: 0.30, roughness: 0.0 })
67
+ );
68
+ glow.position.set(0, barH / 2, -barD / 2 - 0.002);
69
+ g.add(glow);
70
+
71
+ return g;
72
+ }
73
+ };
@@ -0,0 +1,84 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'painting',
6
+ name: 'Painting',
7
+ category: 'decor',
8
+ icon: 'Pa',
9
+ gridW: 1, gridD: 1, height: 1.5,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Canvas (dark abstract)
14
+ var canvas = new THREE.Mesh(
15
+ new THREE.BoxGeometry(1.2, 0.8, 0.04),
16
+ mat(0x1a1522, { roughness: 0.95 })
17
+ );
18
+ canvas.position.set(0, 2, 0);
19
+ canvas.castShadow = true;
20
+ g.add(canvas);
21
+
22
+ // Abstract color block 1 (deep teal)
23
+ var block1 = new THREE.Mesh(
24
+ new THREE.BoxGeometry(0.45, 0.6, 0.045),
25
+ mat(0x0d3d3a, { roughness: 0.90 })
26
+ );
27
+ block1.position.set(-0.22, 2, 0.001);
28
+ g.add(block1);
29
+
30
+ // Abstract color block 2 (dark burgundy)
31
+ var block2 = new THREE.Mesh(
32
+ new THREE.BoxGeometry(0.35, 0.4, 0.045),
33
+ mat(0x3a0d1a, { roughness: 0.90 })
34
+ );
35
+ block2.position.set(0.22, 2.1, 0.001);
36
+ g.add(block2);
37
+
38
+ // Gold frame — top bar
39
+ var frameTop = new THREE.Mesh(
40
+ new THREE.BoxGeometry(1.3, 0.06, 0.07),
41
+ PAL.gold()
42
+ );
43
+ frameTop.position.set(0, 2.43, 0);
44
+ frameTop.castShadow = true;
45
+ g.add(frameTop);
46
+
47
+ // Gold frame — bottom bar
48
+ var frameBot = new THREE.Mesh(
49
+ new THREE.BoxGeometry(1.3, 0.06, 0.07),
50
+ PAL.gold()
51
+ );
52
+ frameBot.position.set(0, 1.57, 0);
53
+ frameBot.castShadow = true;
54
+ g.add(frameBot);
55
+
56
+ // Gold frame — left bar
57
+ var frameLeft = new THREE.Mesh(
58
+ new THREE.BoxGeometry(0.06, 0.92, 0.07),
59
+ PAL.gold()
60
+ );
61
+ frameLeft.position.set(-0.63, 2, 0);
62
+ frameLeft.castShadow = true;
63
+ g.add(frameLeft);
64
+
65
+ // Gold frame — right bar
66
+ var frameRight = new THREE.Mesh(
67
+ new THREE.BoxGeometry(0.06, 0.92, 0.07),
68
+ PAL.gold()
69
+ );
70
+ frameRight.position.set(0.63, 2, 0);
71
+ frameRight.castShadow = true;
72
+ g.add(frameRight);
73
+
74
+ // Wall-mount bracket (small dark rectangle behind)
75
+ var bracket = new THREE.Mesh(
76
+ new THREE.BoxGeometry(0.08, 0.12, 0.04),
77
+ mat(0x111111, { roughness: 0.6, metalness: 0.4 })
78
+ );
79
+ bracket.position.set(0, 2, -0.04);
80
+ g.add(bracket);
81
+
82
+ return g;
83
+ }
84
+ };
@@ -0,0 +1,108 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'palm_tree',
6
+ name: 'Palm Tree',
7
+ category: 'nature',
8
+ icon: 'PT',
9
+ gridW: 1, gridD: 1, height: 3.0,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ // Planter — dark round pot
14
+ var planter = new THREE.Mesh(
15
+ new THREE.CylinderGeometry(0.28, 0.22, 0.36, 18),
16
+ mat(0x1e2028, { roughness: 0.90 })
17
+ );
18
+ planter.position.y = 0.18;
19
+ planter.castShadow = true;
20
+ planter.receiveShadow = true;
21
+ g.add(planter);
22
+
23
+ // Rim
24
+ var rim = new THREE.Mesh(
25
+ new THREE.TorusGeometry(0.285, 0.022, 8, 28),
26
+ mat(0x13151a, { roughness: 0.85 })
27
+ );
28
+ rim.rotation.x = Math.PI / 2;
29
+ rim.position.y = 0.357;
30
+ g.add(rim);
31
+
32
+ // Soil
33
+ var soil = new THREE.Mesh(
34
+ new THREE.CylinderGeometry(0.26, 0.26, 0.018, 18),
35
+ mat(0x1a1510, { roughness: 0.99 })
36
+ );
37
+ soil.position.y = 0.370;
38
+ g.add(soil);
39
+
40
+ // Trunk — slim, slight taper
41
+ var trunkMat = mat(0x5a4020, { roughness: 0.85 });
42
+ var trunk = new THREE.Mesh(
43
+ new THREE.CylinderGeometry(0.038, 0.060, 2.55, 10),
44
+ trunkMat
45
+ );
46
+ trunk.position.y = 1.655;
47
+ trunk.rotation.z = 0.05; // slight lean
48
+ trunk.castShadow = true;
49
+ g.add(trunk);
50
+
51
+ // Trunk ring details (bark texture rings)
52
+ for (var i = 0; i < 5; i++) {
53
+ var ring = new THREE.Mesh(
54
+ new THREE.TorusGeometry(0.052 - i * 0.002, 0.008, 6, 16),
55
+ mat(0x3d2a10, { roughness: 0.90 })
56
+ );
57
+ ring.position.y = 0.55 + i * 0.45;
58
+ ring.rotation.x = Math.PI / 2;
59
+ g.add(ring);
60
+ }
61
+
62
+ // Palm frond fan — 6 elongated leaves fanning out
63
+ var leafMat = mat(0x2a7a40, { roughness: 0.78, side: THREE.DoubleSide });
64
+ var frondCount = 6;
65
+ for (var j = 0; j < frondCount; j++) {
66
+ var angle = (j / frondCount) * Math.PI * 2;
67
+ var frond = new THREE.Mesh(
68
+ new THREE.BoxGeometry(0.06, 0.60, 0.018),
69
+ leafMat
70
+ );
71
+ frond.position.set(
72
+ Math.sin(angle) * 0.38,
73
+ 2.88,
74
+ Math.cos(angle) * 0.38
75
+ );
76
+ frond.rotation.y = -angle;
77
+ frond.rotation.z = 0.55; // droop outward
78
+ frond.castShadow = true;
79
+ g.add(frond);
80
+
81
+ // Frond tip (narrower end piece)
82
+ var tip = new THREE.Mesh(
83
+ new THREE.ConeGeometry(0.025, 0.22, 6),
84
+ leafMat
85
+ );
86
+ tip.position.set(
87
+ Math.sin(angle) * 0.68,
88
+ 2.72,
89
+ Math.cos(angle) * 0.68
90
+ );
91
+ tip.rotation.y = -angle;
92
+ tip.rotation.z = Math.PI / 2 - 0.2;
93
+ tip.castShadow = true;
94
+ g.add(tip);
95
+ }
96
+
97
+ // Crown center sphere
98
+ var crown = new THREE.Mesh(
99
+ new THREE.SphereGeometry(0.09, 12, 10),
100
+ mat(0x1e5c30, { roughness: 0.80 })
101
+ );
102
+ crown.position.y = 2.93;
103
+ crown.castShadow = true;
104
+ g.add(crown);
105
+
106
+ return g;
107
+ }
108
+ };
@@ -0,0 +1,91 @@
1
+ import * as THREE from 'three';
2
+ import { mat, PAL } from './materials.js';
3
+
4
+ export default {
5
+ id: 'pc_tower',
6
+ name: 'PC Tower',
7
+ category: 'tech',
8
+ icon: 'PC',
9
+ gridW: 1, gridD: 1, height: 0.45,
10
+ factory: function() {
11
+ var g = new THREE.Group();
12
+
13
+ var H = 0.45;
14
+ var W = 0.22;
15
+ var D = 0.40;
16
+
17
+ // Main case body — matte black
18
+ var body = new THREE.Mesh(
19
+ new THREE.BoxGeometry(W, H, D),
20
+ mat(0x0d0d0f, { roughness: 0.50, metalness: 0.18 })
21
+ );
22
+ body.position.y = H / 2;
23
+ body.castShadow = true;
24
+ g.add(body);
25
+
26
+ // RGB tempered glass side panel (right side)
27
+ var glass = new THREE.Mesh(
28
+ new THREE.BoxGeometry(0.005, H - 0.02, D - 0.02),
29
+ mat(0x0a1a3a, { transparent: true, opacity: 0.55, roughness: 0.02, emissive: 0x1144cc, emissiveIntensity: 0.45 })
30
+ );
31
+ glass.position.set(W / 2 + 0.003, H / 2, 0);
32
+ g.add(glass);
33
+
34
+ // Internal RGB strip glow bar (seen through glass)
35
+ var rgbStrip = new THREE.Mesh(
36
+ new THREE.BoxGeometry(0.008, H * 0.80, 0.018),
37
+ mat(0x3355ff, { emissive: 0x2244ff, emissiveIntensity: 1.2 })
38
+ );
39
+ rgbStrip.position.set(W / 2 - 0.025, H / 2, 0);
40
+ g.add(rgbStrip);
41
+
42
+ // Front panel — slightly different texture
43
+ var front = new THREE.Mesh(
44
+ new THREE.BoxGeometry(W - 0.01, H - 0.01, 0.012),
45
+ mat(0x141418, { roughness: 0.38, metalness: 0.22 })
46
+ );
47
+ front.position.set(0, H / 2, D / 2 + 0.001);
48
+ front.castShadow = true;
49
+ g.add(front);
50
+
51
+ // Power button (front top)
52
+ var pwrBtn = new THREE.Mesh(
53
+ new THREE.CylinderGeometry(0.012, 0.012, 0.006, 16),
54
+ mat(0x555577, { roughness: 0.30, metalness: 0.60 })
55
+ );
56
+ pwrBtn.rotation.x = Math.PI / 2;
57
+ pwrBtn.position.set(-0.04, H - 0.055, D / 2 + 0.007);
58
+ g.add(pwrBtn);
59
+
60
+ // Power LED ring around button
61
+ var pwrLED = new THREE.Mesh(
62
+ new THREE.TorusGeometry(0.013, 0.003, 8, 16),
63
+ mat(0x00aaff, { emissive: 0x0088ff, emissiveIntensity: 0.9 })
64
+ );
65
+ pwrLED.rotation.x = Math.PI / 2;
66
+ pwrLED.position.set(-0.04, H - 0.055, D / 2 + 0.009);
67
+ g.add(pwrLED);
68
+
69
+ // USB ports strip (front)
70
+ var usbBar = new THREE.Mesh(
71
+ new THREE.BoxGeometry(0.06, 0.018, 0.005),
72
+ mat(0x222222, { roughness: 0.70 })
73
+ );
74
+ usbBar.position.set(0.03, H - 0.065, D / 2 + 0.008);
75
+ g.add(usbBar);
76
+
77
+ // Bottom feet
78
+ [-W * 0.35, W * 0.35].forEach(function(xOff) {
79
+ [-D * 0.38, D * 0.38].forEach(function(zOff) {
80
+ var foot = new THREE.Mesh(
81
+ new THREE.BoxGeometry(0.025, 0.012, 0.025),
82
+ PAL.rubber()
83
+ );
84
+ foot.position.set(xOff, 0.006, zOff);
85
+ g.add(foot);
86
+ });
87
+ });
88
+
89
+ return g;
90
+ }
91
+ };