Update Timmy's appearance to match reference with new colors and details

Refactors the `buildTimmy` function to update Timmy's robe color to royal purple, add celestial gold star decorations, and implement a silver beard and hair, along with a pulsing orange magic orb effect.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 418bf6f8-212b-4bb0-a7a5-8231a061da4e
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 7cc95df8-ef94-4761-8b47-9c13fedbba9a
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/9f85e954-647c-46a5-90a7-396e495a805a/418bf6f8-212b-4bb0-a7a5-8231a061da4e/Q83Uqvu
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
alexpaynex
2026-03-19 03:23:28 +00:00
parent 6e982ff772
commit 93bd48f8ea

View File

@@ -78,15 +78,63 @@ function buildTimmy(sc) {
const group = new THREE.Group();
group.position.copy(TIMMY_POS);
// Robe
const robeMat = new THREE.MeshStandardMaterial({ color: 0x2d1b4e, roughness: 0.82, metalness: 0.08 });
// ── Robe — royal purple with subtle inner glow ────────────────────────────
const robeMat = new THREE.MeshStandardMaterial({
color: 0x5c14b0, // vivid royal purple
emissive: 0x2a0060,
emissiveIntensity: 0.12,
roughness: 0.72,
metalness: 0.05,
});
const robe = new THREE.Mesh(new THREE.CylinderGeometry(0.32, 0.72, 2.2, 8), robeMat);
robe.position.y = 1.1;
robe.castShadow = true;
group.add(robe);
// Head (pivot for face — tilts via rotation.z in updateAgents)
const headMat = new THREE.MeshStandardMaterial({ color: 0xf2d0a0, roughness: 0.7 });
// ── Celestial decorations on robe (tiny gold stars/symbols) ──────────────
const celestialMat = new THREE.MeshStandardMaterial({
color: 0xffd060,
emissive: 0xffaa00,
emissiveIntensity: 0.9,
roughness: 0.3,
metalness: 0.6,
});
// Scattered octahedra on robe surface — chest, shoulders, lower body
const celestialPositions = [
[ 0.00, 1.80, 0.30 ], // chest centre (moon clasp)
[ -0.22, 1.60, 0.26 ], // chest left
[ 0.22, 1.60, 0.26 ], // chest right
[ -0.28, 1.30, 0.22 ], // mid left
[ 0.28, 1.30, 0.22 ], // mid right
[ 0.00, 0.95, 0.32 ], // lower centre
[ -0.18, 0.70, 0.30 ], // lower left
[ 0.18, 0.70, 0.30 ], // lower right
];
celestialPositions.forEach(([x, y, z]) => {
const sz = y > 1.5 ? 0.038 : 0.026; // chest stars larger
const cs = new THREE.Mesh(new THREE.OctahedronGeometry(sz, 0), celestialMat);
cs.position.set(x, y, z);
cs.rotation.y = Math.random() * Math.PI;
group.add(cs);
});
// Moon crescent on chest — a torus segment (big gold torus, small tube)
const moonMat = new THREE.MeshStandardMaterial({
color: 0xffd700,
emissive: 0xffcc00,
emissiveIntensity: 0.8,
roughness: 0.25,
metalness: 0.7,
});
const moon = new THREE.Mesh(new THREE.TorusGeometry(0.065, 0.016, 5, 14, Math.PI * 1.3), moonMat);
moon.position.set(-0.06, 1.82, 0.32);
moon.rotation.x = -0.3;
moon.rotation.z = -0.5;
group.add(moon);
// ── Head ─────────────────────────────────────────────────────────────────
// Slightly older, weathered skin tone
const headMat = new THREE.MeshStandardMaterial({ color: 0xd8a878, roughness: 0.72 });
const head = new THREE.Mesh(new THREE.SphereGeometry(0.38, 16, 16), headMat);
head.position.y = 2.6;
head.castShadow = true;
@@ -94,7 +142,6 @@ function buildTimmy(sc) {
// ── Face (ALL parented to head — follow head tilt naturally) ─────────────
// White sclera background for each eye
const scleraMat = new THREE.MeshStandardMaterial({
color: 0xf5f2e8,
emissive: 0x777777,
@@ -103,8 +150,6 @@ function buildTimmy(sc) {
});
const scleraGeo = new THREE.SphereGeometry(0.079, 10, 10);
// Head-relative positions: head center = (0,0,0), face = +z direction
// group y=2.65, z=0.31 → head-relative y=0.05, z=0.31
const eyeL = new THREE.Mesh(scleraGeo, scleraMat);
eyeL.position.set(-0.14, 0.05, 0.31);
head.add(eyeL);
@@ -113,53 +158,140 @@ function buildTimmy(sc) {
eyeR.position.set(0.14, 0.05, 0.31);
head.add(eyeR);
// Dark pupils — children of sclera so they scale with eye Y (squint effect)
const pupilMat = new THREE.MeshBasicMaterial({ color: 0x07070f });
const pupilGeo = new THREE.SphereGeometry(0.037, 8, 8);
const pupilL = new THREE.Mesh(pupilGeo, pupilMat);
pupilL.position.set(0, 0, 0.057); // forward from sclera center
pupilL.position.set(0, 0, 0.057);
eyeL.add(pupilL);
const pupilR = new THREE.Mesh(pupilGeo, pupilMat.clone());
pupilR.position.set(0, 0, 0.057);
eyeR.add(pupilR);
// Mouth — TubeGeometry arc, child of head so it follows head tilt
const mouthMat = new THREE.MeshStandardMaterial({
color: 0x8a4a28,
roughness: 0.7,
metalness: 0.0,
});
const mouth = new THREE.Mesh(_pickMouthGeo(0.08), mouthMat); // start at idle smile
// Position in head-local space: lower face (y=-0.18), on the face surface (z≈0.30)
// Mouth arc
const mouthMat = new THREE.MeshStandardMaterial({ color: 0x8a4a28, roughness: 0.7, metalness: 0.0 });
const mouth = new THREE.Mesh(_pickMouthGeo(0.08), mouthMat);
mouth.position.set(0, -0.18, 0.30);
head.add(mouth);
// ── Hat ──────────────────────────────────────────────────────────────────
// ── Beard — silver-white, below chin ─────────────────────────────────────
const beardMat = new THREE.MeshStandardMaterial({
color: 0xd8d4cc,
emissive: 0x888880,
emissiveIntensity: 0.08,
roughness: 0.88,
});
// Main beard volume: wide cone hanging below chin
const beard = new THREE.Mesh(new THREE.ConeGeometry(0.18, 0.38, 7), beardMat);
beard.position.set(0, -0.32, 0.20);
beard.rotation.x = 0.22; // tip slightly forward
head.add(beard);
// Moustache: small cylinder across upper lip
const moustache = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, 0.22, 6), beardMat.clone());
moustache.position.set(0, -0.10, 0.35);
moustache.rotation.z = Math.PI / 2;
head.add(moustache);
const hatMat = new THREE.MeshStandardMaterial({ color: 0x1a0a2e, roughness: 0.82 });
const brim = new THREE.Mesh(new THREE.TorusGeometry(0.46, 0.07, 6, 24), hatMat);
// ── Hair — silver-white wisps at sides ───────────────────────────────────
const hairMat = new THREE.MeshStandardMaterial({
color: 0xc8c4bc,
emissive: 0x888880,
emissiveIntensity: 0.06,
roughness: 0.90,
});
// Side hair puffs
[[-0.34, 0.04, 0.06], [0.34, 0.04, 0.06], [-0.30, -0.10, -0.08], [0.30, -0.10, -0.08]].forEach(([x, y, z]) => {
const h = new THREE.Mesh(new THREE.SphereGeometry(0.14, 7, 7), hairMat);
h.position.set(x, y, z);
h.scale.set(1, 0.7, 0.9);
head.add(h);
});
// Back of head hair mass
const hairBack = new THREE.Mesh(new THREE.SphereGeometry(0.30, 8, 8), hairMat.clone());
hairBack.position.set(0, 0.0, -0.22);
hairBack.scale.set(1, 0.8, 0.7);
head.add(hairBack);
// ── Hat — deep royal purple, taller cone ─────────────────────────────────
const hatMat = new THREE.MeshStandardMaterial({
color: 0x3a0880,
emissive: 0x18044a,
emissiveIntensity: 0.10,
roughness: 0.78,
});
const brim = new THREE.Mesh(new THREE.TorusGeometry(0.47, 0.07, 6, 24), hatMat);
brim.position.y = 2.94;
brim.rotation.x = Math.PI / 2;
group.add(brim);
const hat = new THREE.Mesh(new THREE.ConeGeometry(0.33, 0.78, 8), hatMat.clone());
hat.position.y = 3.33;
const hat = new THREE.Mesh(new THREE.ConeGeometry(0.33, 0.82, 8), hatMat.clone());
hat.position.y = 3.35;
group.add(hat);
const starMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xffaa00, emissiveIntensity: 1.0 });
const star = new THREE.Mesh(new THREE.OctahedronGeometry(0.07, 0), starMat);
star.position.y = 3.76;
// Hat band — thin gold torus just above brim
const hatBandMat = new THREE.MeshStandardMaterial({
color: 0xffd700, emissive: 0xffaa00, emissiveIntensity: 0.7,
roughness: 0.3, metalness: 0.6,
});
const hatBand = new THREE.Mesh(new THREE.TorusGeometry(0.36, 0.022, 5, 20), hatBandMat);
hatBand.position.y = 3.02;
hatBand.rotation.x = Math.PI / 2;
group.add(hatBand);
// Star on hat tip
const starMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xffcc00, emissiveIntensity: 1.2 });
const star = new THREE.Mesh(new THREE.OctahedronGeometry(0.08, 0), starMat);
star.position.y = 3.80;
group.add(star);
// Belt
const beltMat = new THREE.MeshStandardMaterial({ color: 0xffd700, emissive: 0xaa8800, emissiveIntensity: 0.4 });
const belt = new THREE.Mesh(new THREE.TorusGeometry(0.52, 0.04, 6, 24), beltMat);
// ── Belt — wide gold with central gemstone ────────────────────────────────
const beltMat = new THREE.MeshStandardMaterial({
color: 0xffd700,
emissive: 0xcc8800,
emissiveIntensity: 0.5,
roughness: 0.28,
metalness: 0.65,
});
const belt = new THREE.Mesh(new THREE.TorusGeometry(0.52, 0.055, 6, 24), beltMat);
belt.position.y = 0.72;
belt.rotation.x = Math.PI / 2;
group.add(belt);
// Belt gemstone clasp — glowing teal
const claspMat = new THREE.MeshStandardMaterial({
color: 0x22ddcc,
emissive: 0x008888,
emissiveIntensity: 1.2,
roughness: 0.1,
metalness: 0.0,
});
const clasp = new THREE.Mesh(new THREE.OctahedronGeometry(0.055, 0), claspMat);
clasp.position.set(0, 0.72, 0.52);
group.add(clasp);
// ── Magic energy hand — right side, glowing orange ───────────────────────
const magicMat = new THREE.MeshStandardMaterial({
color: 0xff8800,
emissive: 0xff5500,
emissiveIntensity: 2.2,
roughness: 0.1,
metalness: 0.0,
});
const magicOrb = new THREE.Mesh(new THREE.SphereGeometry(0.10, 10, 10), magicMat);
magicOrb.position.set(0.55, 1.55, 0.30); // right-hand side, chest height
group.add(magicOrb);
const magicLight = new THREE.PointLight(0xff6600, 1.4, 3.5);
magicOrb.add(magicLight);
// Trailing sparks — two tiny satellite orbs
[[0.12, 0.10, 0.06], [-0.08, 0.14, 0.05]].forEach(([dx, dy, dz]) => {
const spark = new THREE.Mesh(new THREE.SphereGeometry(0.033, 6, 6), magicMat.clone());
spark.position.set(0.55 + dx, 1.55 + dy, 0.30 + dz);
group.add(spark);
});
sc.add(group);
// ── Crystal ball ─────────────────────────────────────────────────────────
@@ -222,16 +354,14 @@ function buildTimmy(sc) {
group, robe, head, hat, star,
eyeL, eyeR, pupilL, pupilR,
mouth,
magicOrb, magicLight,
cb, cbMat, crystalGroup, crystalLight,
pip, pipLight, pipMat,
bubble, bubbleCanvas, bubbleTex, bubbleMat,
pulsePhase: Math.random() * Math.PI * 2,
speechTimer: 0,
// Current lerped face parameters
faceParams: { lidScale: 0.44, pupilScale: 0.90, smileAmount: 0.08 },
// Target face parameters (set by emotion or derived state)
faceTarget: { lidScale: 0.44, pupilScale: 0.90, smileAmount: 0.08 },
// External override: when non-null, setFaceEmotion() takes precedence over derived state
_overrideMood: null,
};
}
@@ -285,8 +415,14 @@ export function updateAgents(time) {
const cbScale = 1 + Math.sin(t * cbPulseSpeed) * 0.022;
timmy.cb.scale.setScalar(cbScale);
timmy.robe.material.emissive = new THREE.Color(0x4400aa);
timmy.robe.material.emissiveIntensity = robeGlow;
timmy.robe.material.emissive = new THREE.Color(0x5c14b0);
timmy.robe.material.emissiveIntensity = 0.10 + robeGlow;
// Magic energy orb — pulsing orange glow in Timmy's hand
const magicPulse = 0.85 + Math.sin(t * 3.4) * 0.35;
timmy.magicOrb.scale.setScalar(magicPulse * (vs === 'working' ? 1.35 : 1.0));
timmy.magicLight.intensity = 1.0 + Math.sin(t * 4.2) * 0.5 + (vs === 'working' ? 0.8 : 0.0);
timmy.magicOrb.position.y = 1.55 + Math.sin(t * 2.8) * 0.04;
const pipX = Math.sin(t * 0.38 + 1.4) * 3.2;
const pipZ = Math.sin(t * 0.65 + 0.8) * 2.2 - 1.8;