feat(city): add humanoid worker robots as city agents
Seed five worker robot agents (TR-01 to TR-05) with Portuguese system prompts and active status. Extend the 3D humanoid renderer with metallic materials, glowing visor, antenna beacon, and chest status light for the worker_robot type. Add feature tests covering payload serialization and city bounds validation.
This commit is contained in:
+62
-6
@@ -1817,14 +1817,17 @@ function buildHumanoid(agent, showLabel = true) {
|
||||
surfer: { skin: 0xf1c27d, hair: 0x3b2314, shirt: 0x3fb6c9, pants: 0x1c1c1c, scale: 1 },
|
||||
basket: { skin: 0xd2956b, hair: 0x111111, shirt: 0xe8802a, pants: 0xf2f2f2, scale: 1 },
|
||||
atc: { skin: 0xe0ac69, hair: 0x1a1a1a, shirt: 0x223366, pants: 0x0f1420, scale: 1.02 },
|
||||
// Humanoid worker robots: brushed metal body + safety-orange vest.
|
||||
worker_robot: { skin: 0xc9d1d9, hair: 0x9aa4b2, shirt: 0xd97706, pants: 0x47515e, scale: 1, metalness: 0.5 },
|
||||
};
|
||||
const cfgc = configs[type] || configs.pedestrian;
|
||||
const s = cfgc.scale;
|
||||
const metalness = cfgc.metalness ?? 0;
|
||||
|
||||
const skinMat = new THREE.MeshStandardMaterial({ color: cfgc.skin, roughness: 0.7 });
|
||||
const hairMat = new THREE.MeshStandardMaterial({ color: cfgc.hair, roughness: 0.6 });
|
||||
const shirtMat = new THREE.MeshStandardMaterial({ color: cfgc.shirt, roughness: 0.8 });
|
||||
const pantsMat = new THREE.MeshStandardMaterial({ color: cfgc.pants, roughness: 0.85 });
|
||||
const skinMat = new THREE.MeshStandardMaterial({ color: cfgc.skin, roughness: 0.7 - metalness * 0.4, metalness });
|
||||
const hairMat = new THREE.MeshStandardMaterial({ color: cfgc.hair, roughness: 0.6, metalness });
|
||||
const shirtMat = new THREE.MeshStandardMaterial({ color: cfgc.shirt, roughness: 0.8 - metalness * 0.3, metalness });
|
||||
const pantsMat = new THREE.MeshStandardMaterial({ color: cfgc.pants, roughness: 0.85 - metalness * 0.3, metalness });
|
||||
const shoeMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.7 });
|
||||
|
||||
const head = new THREE.Mesh(new THREE.SphereGeometry(0.28 * s, 12, 12), skinMat);
|
||||
@@ -1837,6 +1840,50 @@ function buildHumanoid(agent, showLabel = true) {
|
||||
hair.castShadow = true;
|
||||
g.add(hair);
|
||||
|
||||
if (type === 'worker_robot') {
|
||||
// Glowing visor across the face.
|
||||
const visor = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(0.34 * s, 0.1 * s, 0.06 * s),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x0e7490, emissive: 0x22d3ee, emissiveIntensity: 1.5, roughness: 0.3,
|
||||
})
|
||||
);
|
||||
visor.position.set(0, 1.63 * s, 0.255 * s);
|
||||
g.add(visor);
|
||||
|
||||
// Antenna with a blinking beacon (ramps/pulses with the sign system).
|
||||
const antennaRod = new THREE.Mesh(
|
||||
new THREE.CylinderGeometry(0.014, 0.02, 0.24, 6),
|
||||
new THREE.MeshStandardMaterial({ color: 0x6b7280, roughness: 0.5, metalness: 0.7 })
|
||||
);
|
||||
antennaRod.position.set(0, 1.97 * s, 0);
|
||||
g.add(antennaRod);
|
||||
const beacon = new THREE.Mesh(
|
||||
new THREE.SphereGeometry(0.045, 8, 8),
|
||||
new THREE.MeshStandardMaterial({ color: 0xff3333, emissive: 0xff3333, emissiveIntensity: 2 })
|
||||
);
|
||||
beacon.position.set(0, 2.1 * s, 0);
|
||||
g.add(beacon);
|
||||
signMaterials.push({
|
||||
mat: beacon.material, base: 1.2, nightBoost: 1.0, pulse: 0.9,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
});
|
||||
|
||||
// Chest status light (amber, softer pulse).
|
||||
const chest = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(0.15, 0.11, 0.05),
|
||||
new THREE.MeshStandardMaterial({
|
||||
color: 0x78350f, emissive: 0xf59e0b, emissiveIntensity: 1.1, roughness: 0.4,
|
||||
})
|
||||
);
|
||||
chest.position.set(0, 1.2 * s, 0.21 * s);
|
||||
g.add(chest);
|
||||
signMaterials.push({
|
||||
mat: chest.material, base: 0.8, nightBoost: 0.7, pulse: 0.35,
|
||||
phase: Math.random() * Math.PI * 2,
|
||||
});
|
||||
}
|
||||
|
||||
const torso = new THREE.Mesh(new THREE.CapsuleGeometry(0.24 * s, 0.55 * s, 4, 8), shirtMat);
|
||||
torso.position.y = 1.12 * s;
|
||||
torso.castShadow = true;
|
||||
@@ -2029,7 +2076,7 @@ function initPedestrians(g, agents) {
|
||||
if (i % 3 === 0) routes.push({ route: crossingRoute(sq, g), kind: 'cross' });
|
||||
});
|
||||
|
||||
const pedAgents = agents.filter(a => a.type === 'pedestrian');
|
||||
const pedAgents = agents.filter(a => a.type === 'pedestrian' || a.type === 'worker_robot');
|
||||
pedAgents.forEach((a, i) => {
|
||||
const pick = routes[i % routes.length];
|
||||
const mesh = buildHumanoid(a, true);
|
||||
@@ -2038,6 +2085,7 @@ function initPedestrians(g, agents) {
|
||||
const w = {
|
||||
mesh, route: pick.route, wp: 0, speed: 1.15 + Math.random() * 0.4,
|
||||
wait: Math.random() * 2, pauseAt: 4 + Math.random() * 8, pauseTimer: 0,
|
||||
robot: a.type === 'worker_robot',
|
||||
};
|
||||
const start = w.route.waypoints[0];
|
||||
mesh.position.set(start.x, 0, start.z);
|
||||
@@ -4909,7 +4957,7 @@ function buildCity(data) {
|
||||
roofBatch.flush(worldGroup);
|
||||
|
||||
// ---- Agents (mayor, shopkeepers handled in their systems; pedestrians walk) ----
|
||||
const walkingTypes = new Set(['pedestrian', 'mayor', 'shopkeeper']);
|
||||
const walkingTypes = new Set(['pedestrian', 'mayor', 'shopkeeper', 'worker_robot']);
|
||||
const placed = new Set();
|
||||
|
||||
// Shopkeepers + mayor + pedestrians get behaviours; any leftover static agents render in place.
|
||||
@@ -6643,6 +6691,14 @@ window.__city10 = {
|
||||
sidewalkSegments: sidewalkSegmentCount,
|
||||
beach: beachSystem ? beachSystem.counts() : null,
|
||||
flights: airplaneSystem ? airplaneSystem.flights() : null,
|
||||
robots: pedestrianSystem
|
||||
? pedestrianSystem.walkers.filter(w => w.robot).length
|
||||
: 0,
|
||||
robotWalkers: pedestrianSystem
|
||||
? pedestrianSystem.walkers
|
||||
.filter(w => w.robot)
|
||||
.map(w => ({ x: +w.mesh.position.x.toFixed(1), z: +w.mesh.position.z.toFixed(1) }))
|
||||
: [],
|
||||
signs: {
|
||||
stores: signCounts.stores,
|
||||
plazas: signCounts.plazas,
|
||||
|
||||
Reference in New Issue
Block a user