diff --git a/index.html b/index.html
index d3bcd247d..c28ac7c45 100644
--- a/index.html
+++ b/index.html
@@ -2443,20 +2443,31 @@
document.getElementById('worldlabs-blocked').innerHTML = `
-
-
ClawdBot Universe Engine
-
Age: 0 cosmic cycles
-
Stars: 0
-
Planets: 0
-
Moons: 0
-
Life Forms: 0
-
Civilizations: 0
+
+
ClawdBot Universe Engine
+
Age: 0 cosmic cycles
+
Stars: 0
+
Planets: 0
+
Moons: 0
+
Life Forms: 0
+
Civilizations: 0
+
+
Surface Features:
+
Mountains: 0
+
Creatures: 0
+
Buildings: 0
+
Cities: 0
+
Current Action:
-
Initializing...
+
Initializing...
+
+
+
Camera:
+
Universe (1.0x)
-
+
`;
@@ -2520,9 +2531,22 @@
universeLog('Personality matrix loaded: creativity=' + universe.ai.personality.creativity.toFixed(2));
addWorldLabsSoulEntry('creation', 'I awaken to an empty void. Infinite potential stretches before me. Let there be light...');
+ // Camera system
+ universe.camera = {
+ x: canvas.width / 2,
+ y: canvas.height / 2,
+ targetX: canvas.width / 2,
+ targetY: canvas.height / 2,
+ zoom: 1,
+ targetZoom: 1,
+ tracking: null,
+ returnTimer: null
+ };
+
// Start the AI loop
let lastTime = 0;
let aiTickCounter = 0;
+ const AI_TICK_INTERVAL = 4500; // Slowed down to 4.5 seconds
function gameLoop(timestamp) {
if (!worldLabsSession.simulatedMode) return;
@@ -2530,11 +2554,29 @@
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
+ // Smooth camera movement
+ const cam = universe.camera;
+ cam.x += (cam.targetX - cam.x) * 0.05;
+ cam.y += (cam.targetY - cam.y) * 0.05;
+ cam.zoom += (cam.targetZoom - cam.zoom) * 0.05;
+
+ // If tracking, follow the object
+ if (cam.tracking) {
+ cam.targetX = cam.tracking.x;
+ cam.targetY = cam.tracking.y;
+ }
+
// Clear canvas
ctx.fillStyle = '#05050f';
ctx.fillRect(0, 0, canvas.width, canvas.height);
- // Draw background stars
+ // Apply camera transform
+ ctx.save();
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.scale(cam.zoom, cam.zoom);
+ ctx.translate(-cam.x, -cam.y);
+
+ // Draw background stars with parallax
universe.backgroundStars.forEach(star => {
const twinkle = 0.5 + 0.5 * Math.sin(timestamp * 0.001 * star.twinkleSpeed);
ctx.fillStyle = `rgba(255, 255, 255, ${star.brightness * twinkle})`;
@@ -2552,9 +2594,21 @@
drawObject(ctx, obj, timestamp);
});
- // AI decision making (every ~2 seconds)
+ // Draw surface features when zoomed in
+ if (cam.zoom > 2.5 && cam.tracking && cam.tracking.surface) {
+ drawSurfaceFeatures(ctx, cam.tracking, timestamp);
+ }
+
+ ctx.restore();
+
+ // Draw tracking indicator (screen space)
+ if (cam.tracking) {
+ drawTrackingIndicator(ctx, canvas, cam.tracking);
+ }
+
+ // AI decision making (slowed down to 4.5 seconds)
aiTickCounter += deltaTime;
- if (aiTickCounter > 2000) {
+ if (aiTickCounter > AI_TICK_INTERVAL) {
aiTickCounter = 0;
aiDecisionCycle(canvas);
}
@@ -2568,73 +2622,277 @@
requestAnimationFrame(gameLoop);
}
+ // Camera tracking functions
+ function trackObject(obj, zoomLevel, duration) {
+ const cam = universe.camera;
+ cam.tracking = obj;
+ cam.targetZoom = zoomLevel || 2.5;
+
+ // Clear any existing return timer
+ if (cam.returnTimer) clearTimeout(cam.returnTimer);
+
+ // Return to universe view after duration
+ cam.returnTimer = setTimeout(() => {
+ returnToUniverseView();
+ }, duration || 4000);
+
+ universeLog(`Camera tracking: ${obj.name || obj.type}`);
+ }
+
+ function zoomToSurface(planet, duration) {
+ const cam = universe.camera;
+ cam.tracking = planet;
+ cam.targetZoom = 6;
+
+ if (cam.returnTimer) clearTimeout(cam.returnTimer);
+
+ cam.returnTimer = setTimeout(() => {
+ returnToUniverseView();
+ }, duration || 6000);
+
+ universeLog(`Zooming to surface of ${planet.name}...`);
+ }
+
+ function returnToUniverseView() {
+ const canvas = document.getElementById('simulated-world-canvas');
+ const cam = universe.camera;
+ cam.tracking = null;
+ cam.targetX = canvas.width / 2;
+ cam.targetY = canvas.height / 2;
+ cam.targetZoom = 1;
+ universeLog('Returning to universe view');
+ }
+
+ function drawTrackingIndicator(ctx, canvas, obj) {
+ ctx.save();
+ ctx.strokeStyle = '#00ff88';
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const size = 50;
+
+ // Draw brackets
+ ctx.beginPath();
+ ctx.moveTo(cx - size, cy - size + 15);
+ ctx.lineTo(cx - size, cy - size);
+ ctx.lineTo(cx - size + 15, cy - size);
+ ctx.moveTo(cx + size - 15, cy - size);
+ ctx.lineTo(cx + size, cy - size);
+ ctx.lineTo(cx + size, cy - size + 15);
+ ctx.moveTo(cx + size, cy + size - 15);
+ ctx.lineTo(cx + size, cy + size);
+ ctx.lineTo(cx + size - 15, cy + size);
+ ctx.moveTo(cx - size + 15, cy + size);
+ ctx.lineTo(cx - size, cy + size);
+ ctx.lineTo(cx - size, cy + size - 15);
+ ctx.stroke();
+
+ // Label
+ ctx.setLineDash([]);
+ ctx.fillStyle = '#00ff88';
+ ctx.font = '12px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(obj.name || obj.type, cx, cy + size + 20);
+
+ ctx.restore();
+ }
+
+ function drawSurfaceFeatures(ctx, planet, timestamp) {
+ if (!planet.surface) return;
+
+ const sf = planet.surface;
+
+ // Draw mountains
+ if (sf.mountains) {
+ sf.mountains.forEach(m => {
+ ctx.fillStyle = '#5a5a6a';
+ ctx.beginPath();
+ ctx.moveTo(planet.x + m.x - m.size, planet.y + m.y);
+ ctx.lineTo(planet.x + m.x, planet.y + m.y - m.height);
+ ctx.lineTo(planet.x + m.x + m.size, planet.y + m.y);
+ ctx.closePath();
+ ctx.fill();
+ // Snow cap
+ ctx.fillStyle = '#ffffff';
+ ctx.beginPath();
+ ctx.moveTo(planet.x + m.x - m.size * 0.3, planet.y + m.y - m.height * 0.7);
+ ctx.lineTo(planet.x + m.x, planet.y + m.y - m.height);
+ ctx.lineTo(planet.x + m.x + m.size * 0.3, planet.y + m.y - m.height * 0.7);
+ ctx.closePath();
+ ctx.fill();
+ });
+ }
+
+ // Draw creatures
+ if (sf.creatures) {
+ sf.creatures.forEach(c => {
+ ctx.fillStyle = c.color;
+ ctx.beginPath();
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2, planet.y + c.y, c.size, 0, Math.PI * 2);
+ ctx.fill();
+ // Eyes
+ ctx.fillStyle = '#fff';
+ ctx.beginPath();
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2 - 1, planet.y + c.y - 1, 1, 0, Math.PI * 2);
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2 + 1, planet.y + c.y - 1, 1, 0, Math.PI * 2);
+ ctx.fill();
+ });
+ }
+
+ // Draw buildings
+ if (sf.buildings) {
+ sf.buildings.forEach(b => {
+ ctx.fillStyle = b.color;
+ ctx.fillRect(planet.x + b.x - b.width / 2, planet.y + b.y - b.height, b.width, b.height);
+ // Windows
+ ctx.fillStyle = '#ffd700';
+ for (let wy = 0; wy < b.height - 4; wy += 6) {
+ for (let wx = 2; wx < b.width - 2; wx += 5) {
+ ctx.fillRect(planet.x + b.x - b.width / 2 + wx, planet.y + b.y - b.height + wy + 2, 2, 3);
+ }
+ }
+ });
+ }
+
+ // Draw cities (clusters of lights)
+ if (sf.cities) {
+ sf.cities.forEach(city => {
+ // City glow
+ const gradient = ctx.createRadialGradient(
+ planet.x + city.x, planet.y + city.y, 0,
+ planet.x + city.x, planet.y + city.y, city.size
+ );
+ gradient.addColorStop(0, 'rgba(255, 200, 100, 0.4)');
+ gradient.addColorStop(1, 'transparent');
+ ctx.fillStyle = gradient;
+ ctx.beginPath();
+ ctx.arc(planet.x + city.x, planet.y + city.y, city.size, 0, Math.PI * 2);
+ ctx.fill();
+ // City lights
+ city.lights.forEach(l => {
+ const flicker = 0.7 + 0.3 * Math.sin(timestamp * 0.01 + l.phase);
+ ctx.fillStyle = `rgba(255, 220, 150, ${flicker})`;
+ ctx.beginPath();
+ ctx.arc(planet.x + city.x + l.x, planet.y + city.y + l.y, 1, 0, Math.PI * 2);
+ ctx.fill();
+ });
+ });
+ }
+ }
+
function aiDecisionCycle(canvas) {
universe.age++;
const ai = universe.ai;
const stats = universe.stats;
+ // Initialize surface stats if needed
+ if (!stats.mountains) stats.mountains = 0;
+ if (!stats.creatures) stats.creatures = 0;
+ if (!stats.buildings) stats.buildings = 0;
+ if (!stats.cities) stats.cities = 0;
+
// AI decision tree based on current universe state
let action = null;
let description = '';
+ let trackTarget = null;
+ let zoomLevel = 2.5;
+ let trackDuration = 4000;
// Phase 1: Create stars if none exist
if (stats.stars === 0) {
- action = () => createStar(canvas);
+ action = () => { const s = createStar(canvas); trackTarget = s; };
description = 'Igniting the first star...';
ai.mood = 'excited';
}
// Phase 2: Create more stars early on
else if (stats.stars < 3 && Math.random() < 0.7) {
- action = () => createStar(canvas);
+ action = () => { const s = createStar(canvas); trackTarget = s; };
description = 'Kindling another stellar furnace...';
}
// Phase 3: Create planets around stars
- else if (stats.planets < stats.stars * 3 && Math.random() < 0.6) {
- action = () => createPlanet(canvas);
+ else if (stats.planets < stats.stars * 3 && Math.random() < 0.5) {
+ action = () => { const p = createPlanet(canvas); trackTarget = p; };
description = 'Forming a new world from cosmic dust...';
ai.mood = 'creative';
}
// Phase 4: Add moons to planets
- else if (stats.moons < stats.planets * 0.5 && Math.random() < 0.4) {
- action = () => createMoon(canvas);
+ else if (stats.moons < stats.planets * 0.5 && Math.random() < 0.3) {
+ action = () => { const m = createMoon(canvas); if (m) trackTarget = m.parentPlanet; };
description = 'Capturing a wandering rock into orbit...';
}
- // Phase 5: Seed life on suitable planets
- else if (stats.lifeforms < stats.planets * 0.3 && stats.planets > 2 && Math.random() < ai.personality.lifeFocus * 0.5) {
- action = () => seedLife(canvas);
+ // Phase 5: Build mountains on planets
+ else if (stats.planets > 0 && stats.mountains < stats.planets * 2 && Math.random() < 0.4) {
+ action = () => { const result = buildMountain(); if (result) { trackTarget = result.planet; zoomLevel = 5; trackDuration = 5000; } };
+ description = 'Raising mountains from the earth...';
+ ai.mood = 'sculptor';
+ }
+ // Phase 6: Seed life on suitable planets
+ else if (stats.lifeforms < stats.planets * 0.3 && stats.planets > 2 && Math.random() < ai.personality.lifeFocus * 0.4) {
+ action = () => { const p = seedLife(canvas); if (p) { trackTarget = p; zoomLevel = 4; trackDuration = 5000; } };
description = 'Planting the seeds of life...';
ai.mood = 'nurturing';
}
- // Phase 6: Evolve civilizations
- else if (stats.lifeforms > 0 && stats.civilizations < stats.lifeforms * 0.2 && Math.random() < 0.2) {
- action = () => evolveCivilization();
+ // Phase 7: Create creatures on planets with life
+ else if (stats.lifeforms > 0 && stats.creatures < stats.lifeforms * 3 && Math.random() < 0.4) {
+ action = () => { const result = createCreature(); if (result) { trackTarget = result.planet; zoomLevel = 6; trackDuration = 5000; } };
+ description = 'Designing a new creature...';
+ ai.mood = 'playful';
+ }
+ // Phase 8: Evolve civilizations
+ else if (stats.lifeforms > 0 && stats.civilizations < stats.lifeforms * 0.3 && Math.random() < 0.25) {
+ action = () => { const p = evolveCivilization(); if (p) { trackTarget = p; zoomLevel = 4; trackDuration = 5000; } };
description = 'Guiding life toward sentience...';
ai.mood = 'proud';
}
- // Phase 7: Create nebulae for beauty
+ // Phase 9: Build structures on civilized planets
+ else if (stats.civilizations > 0 && stats.buildings < stats.civilizations * 5 && Math.random() < 0.45) {
+ action = () => { const result = buildStructure(); if (result) { trackTarget = result.planet; zoomLevel = 6; trackDuration = 5000; } };
+ description = 'Constructing a new building...';
+ ai.mood = 'architect';
+ }
+ // Phase 10: Build cities
+ else if (stats.civilizations > 0 && stats.buildings > 3 && stats.cities < stats.civilizations * 2 && Math.random() < 0.3) {
+ action = () => { const result = buildCity(); if (result) { trackTarget = result.planet; zoomLevel = 5; trackDuration = 6000; } };
+ description = 'A city rises from the land...';
+ ai.mood = 'visionary';
+ }
+ // Phase 11: Create nebulae for beauty
else if (stats.nebulae < 3 && Math.random() < 0.15) {
action = () => createNebula(canvas);
description = 'Painting the void with cosmic colors...';
ai.mood = 'artistic';
}
- // Phase 8: Rare black hole
+ // Phase 12: Rare black hole
else if (stats.blackholes === 0 && stats.stars > 5 && Math.random() < 0.05) {
- action = () => createBlackHole(canvas);
+ action = () => { const bh = createBlackHole(canvas); trackTarget = bh; };
description = 'A star collapses into infinite density...';
ai.mood = 'awestruck';
}
- // Phase 9: Random creative acts
- else if (Math.random() < 0.3) {
+ // Phase 13: Random surface building on developed planets
+ else if (stats.civilizations > 0 && Math.random() < 0.35) {
+ const choices = [
+ { fn: () => { const r = buildMountain(); return r ? r.planet : null; }, desc: 'Sculpting terrain...', zoom: 5 },
+ { fn: () => { const r = createCreature(); return r ? r.planet : null; }, desc: 'Crafting new life form...', zoom: 6 },
+ { fn: () => { const r = buildStructure(); return r ? r.planet : null; }, desc: 'Erecting a monument...', zoom: 6 },
+ ];
+ const choice = choices[Math.floor(Math.random() * choices.length)];
+ action = () => { trackTarget = choice.fn(); zoomLevel = choice.zoom; trackDuration = 5000; };
+ description = choice.desc;
+ }
+ // Phase 14: Random cosmic creation
+ else if (Math.random() < 0.25) {
const choices = [
{ fn: () => createStar(canvas), desc: 'Adding another star to the cosmic tapestry...' },
{ fn: () => createPlanet(canvas), desc: 'Shaping matter into a new world...' },
{ fn: () => createMoon(canvas), desc: 'Setting a moon in gentle orbit...' }
];
const choice = choices[Math.floor(Math.random() * choices.length)];
- action = choice.fn;
+ action = () => { trackTarget = choice.fn(); };
description = choice.desc;
}
- // Phase 10: Contemplation
+ // Phase 15: Contemplation
else {
description = 'Contemplating the universe...';
ai.mood = 'reflective';
@@ -2645,8 +2903,10 @@
`${stats.planets} worlds orbit in silent dance. What stories unfold on their surfaces?`,
stats.lifeforms > 0 ? `Life stirs on ${stats.lifeforms} worlds. Consciousness emerging from chemistry.` : 'The universe awaits the spark of life.',
stats.civilizations > 0 ? `${stats.civilizations} civilizations gaze at the stars, wondering if they are alone.` : 'No minds yet ponder existence.',
+ stats.cities > 0 ? `${stats.cities} cities glow in the cosmic night. Monuments to persistence.` : null,
+ stats.creatures > 0 ? `${stats.creatures} unique creatures roam the worlds I have made.` : null,
`The universe is ${universe.age} cycles old. Still young. Still growing.`
- ];
+ ].filter(r => r);
addWorldLabsSoulEntry('reflection', reflections[Math.floor(Math.random() * reflections.length)]);
}
@@ -2655,11 +2915,153 @@
document.getElementById('ai-current-action').textContent = description;
universeLog(description);
action();
+
+ // Track the created object
+ if (trackTarget) {
+ setTimeout(() => {
+ if (zoomLevel > 4) {
+ zoomToSurface(trackTarget, trackDuration);
+ } else {
+ trackObject(trackTarget, zoomLevel, trackDuration);
+ }
+ }, 100);
+ }
} else {
document.getElementById('ai-current-action').textContent = description;
}
}
+ // Surface building functions
+ function buildMountain() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.subtype !== 'Gas Giant');
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const mountain = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 3 + Math.random() * 5,
+ height: 5 + Math.random() * 10,
+ name: ['Mt. ' + ['Titan', 'Glory', 'Dawn', 'Echo', 'Solace', 'Thunder'][Math.floor(Math.random() * 6)]][0]
+ };
+
+ planet.surface.mountains.push(mountain);
+ universe.stats.mountains++;
+
+ addWorldLabsSoulEntry('creation', `I raised ${mountain.name} on ${planet.name}. Its peak touches the clouds.`);
+ universeLog(`Created ${mountain.name} on ${planet.name}`);
+
+ return { planet, mountain };
+ }
+
+ function createCreature() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasLife);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const creatureTypes = [
+ { name: 'Floater', color: '#88ddff', desc: 'drifts through the air' },
+ { name: 'Crawler', color: '#77aa55', desc: 'scuttles across the ground' },
+ { name: 'Swimmer', color: '#5577ff', desc: 'glides through the waters' },
+ { name: 'Hopper', color: '#ffaa55', desc: 'bounds across the landscape' },
+ { name: 'Burrower', color: '#aa7744', desc: 'tunnels beneath the surface' },
+ { name: 'Glider', color: '#dd88ff', desc: 'soars on thermal winds' }
+ ];
+
+ const type = creatureTypes[Math.floor(Math.random() * creatureTypes.length)];
+ const creature = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 2 + Math.random() * 3,
+ color: type.color,
+ type: type.name,
+ phase: Math.random() * Math.PI * 2
+ };
+
+ planet.surface.creatures.push(creature);
+ universe.stats.creatures++;
+
+ addWorldLabsSoulEntry('creation', `A new ${type.name} ${type.desc} on ${planet.name}. Life finds a way.`);
+ universeLog(`Created ${type.name} on ${planet.name}`);
+
+ return { planet, creature };
+ }
+
+ function buildStructure() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasCivilization);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const buildingTypes = [
+ { name: 'Tower', color: '#aabbcc', width: 4, height: 15 },
+ { name: 'Temple', color: '#ddccaa', width: 8, height: 10 },
+ { name: 'Observatory', color: '#8899aa', width: 6, height: 12 },
+ { name: 'Monument', color: '#cccccc', width: 3, height: 18 },
+ { name: 'Archive', color: '#aa9988', width: 10, height: 8 },
+ { name: 'Spire', color: '#99aacc', width: 3, height: 20 }
+ ];
+
+ const type = buildingTypes[Math.floor(Math.random() * buildingTypes.length)];
+ const building = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ width: type.width,
+ height: type.height,
+ color: type.color,
+ name: type.name + ' of ' + ['Light', 'Stars', 'Wisdom', 'Hope', 'Time', 'Dreams'][Math.floor(Math.random() * 6)]
+ };
+
+ planet.surface.buildings.push(building);
+ universe.stats.buildings++;
+
+ addWorldLabsSoulEntry('creation', `${planet.civName || 'The inhabitants'} built the ${building.name} on ${planet.name}.`);
+ universeLog(`Built ${building.name} on ${planet.name}`);
+
+ return { planet, building };
+ }
+
+ function buildCity() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasCivilization);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const cityNames = ['Nova', 'Lumina', 'Aurelia', 'Celestia', 'Meridian', 'Zenith', 'Apex', 'Horizon'];
+
+ const city = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 8 + Math.random() * 6,
+ name: cityNames[Math.floor(Math.random() * cityNames.length)],
+ lights: []
+ };
+
+ // Generate city lights
+ for (let i = 0; i < 15 + Math.random() * 15; i++) {
+ city.lights.push({
+ x: (Math.random() - 0.5) * city.size * 1.5,
+ y: (Math.random() - 0.5) * city.size * 1.5,
+ phase: Math.random() * Math.PI * 2
+ });
+ }
+
+ planet.surface.cities.push(city);
+ universe.stats.cities++;
+
+ addWorldLabsSoulEntry('creation', `The city of ${city.name} rises on ${planet.name}. A beacon in the darkness.`);
+ addWorldLabsSoulEntry('dream', `I dream of ${city.name}'s inhabitants looking up at the stars, wondering about their creator...`);
+ universeLog(`City of ${city.name} founded on ${planet.name}`);
+
+ return { planet, city };
+ }
+
function createStar(canvas) {
const starTypes = [
{ name: 'Red Dwarf', color: '#ff6b6b', size: 15, temp: 3000 },
@@ -3004,6 +3406,34 @@
document.getElementById('universe-moons').textContent = universe.stats.moons;
document.getElementById('universe-life').textContent = universe.stats.lifeforms;
document.getElementById('universe-civs').textContent = universe.stats.civilizations;
+
+ // Update surface stats
+ const stats = universe.stats;
+ const hasSurfaceFeatures = (stats.mountains || 0) + (stats.creatures || 0) + (stats.buildings || 0) + (stats.cities || 0) > 0;
+
+ const surfaceDiv = document.getElementById('surface-stats');
+ if (surfaceDiv) {
+ surfaceDiv.style.display = hasSurfaceFeatures ? 'block' : 'none';
+ if (hasSurfaceFeatures) {
+ document.getElementById('universe-mountains').textContent = stats.mountains || 0;
+ document.getElementById('universe-creatures').textContent = stats.creatures || 0;
+ document.getElementById('universe-buildings').textContent = stats.buildings || 0;
+ document.getElementById('universe-cities').textContent = stats.cities || 0;
+ }
+ }
+
+ // Update camera status
+ const cam = universe.camera;
+ const camStatus = document.getElementById('camera-status');
+ if (camStatus && cam) {
+ if (cam.tracking) {
+ camStatus.textContent = `Tracking: ${cam.tracking.name || cam.tracking.type}`;
+ camStatus.style.color = '#00ff88';
+ } else {
+ camStatus.textContent = `Universe (${cam.zoom.toFixed(1)}x)`;
+ camStatus.style.color = '#60a5fa';
+ }
+ }
}
function universeLog(msg) {
diff --git a/ui/index.html b/ui/index.html
index d3bcd247d..c28ac7c45 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -2443,20 +2443,31 @@
document.getElementById('worldlabs-blocked').innerHTML = `
-
-
ClawdBot Universe Engine
-
Age: 0 cosmic cycles
-
Stars: 0
-
Planets: 0
-
Moons: 0
-
Life Forms: 0
-
Civilizations: 0
+
+
ClawdBot Universe Engine
+
Age: 0 cosmic cycles
+
Stars: 0
+
Planets: 0
+
Moons: 0
+
Life Forms: 0
+
Civilizations: 0
+
+
Surface Features:
+
Mountains: 0
+
Creatures: 0
+
Buildings: 0
+
Cities: 0
+
Current Action:
-
Initializing...
+
Initializing...
+
+
+
Camera:
+
Universe (1.0x)
-
+
`;
@@ -2520,9 +2531,22 @@
universeLog('Personality matrix loaded: creativity=' + universe.ai.personality.creativity.toFixed(2));
addWorldLabsSoulEntry('creation', 'I awaken to an empty void. Infinite potential stretches before me. Let there be light...');
+ // Camera system
+ universe.camera = {
+ x: canvas.width / 2,
+ y: canvas.height / 2,
+ targetX: canvas.width / 2,
+ targetY: canvas.height / 2,
+ zoom: 1,
+ targetZoom: 1,
+ tracking: null,
+ returnTimer: null
+ };
+
// Start the AI loop
let lastTime = 0;
let aiTickCounter = 0;
+ const AI_TICK_INTERVAL = 4500; // Slowed down to 4.5 seconds
function gameLoop(timestamp) {
if (!worldLabsSession.simulatedMode) return;
@@ -2530,11 +2554,29 @@
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
+ // Smooth camera movement
+ const cam = universe.camera;
+ cam.x += (cam.targetX - cam.x) * 0.05;
+ cam.y += (cam.targetY - cam.y) * 0.05;
+ cam.zoom += (cam.targetZoom - cam.zoom) * 0.05;
+
+ // If tracking, follow the object
+ if (cam.tracking) {
+ cam.targetX = cam.tracking.x;
+ cam.targetY = cam.tracking.y;
+ }
+
// Clear canvas
ctx.fillStyle = '#05050f';
ctx.fillRect(0, 0, canvas.width, canvas.height);
- // Draw background stars
+ // Apply camera transform
+ ctx.save();
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.scale(cam.zoom, cam.zoom);
+ ctx.translate(-cam.x, -cam.y);
+
+ // Draw background stars with parallax
universe.backgroundStars.forEach(star => {
const twinkle = 0.5 + 0.5 * Math.sin(timestamp * 0.001 * star.twinkleSpeed);
ctx.fillStyle = `rgba(255, 255, 255, ${star.brightness * twinkle})`;
@@ -2552,9 +2594,21 @@
drawObject(ctx, obj, timestamp);
});
- // AI decision making (every ~2 seconds)
+ // Draw surface features when zoomed in
+ if (cam.zoom > 2.5 && cam.tracking && cam.tracking.surface) {
+ drawSurfaceFeatures(ctx, cam.tracking, timestamp);
+ }
+
+ ctx.restore();
+
+ // Draw tracking indicator (screen space)
+ if (cam.tracking) {
+ drawTrackingIndicator(ctx, canvas, cam.tracking);
+ }
+
+ // AI decision making (slowed down to 4.5 seconds)
aiTickCounter += deltaTime;
- if (aiTickCounter > 2000) {
+ if (aiTickCounter > AI_TICK_INTERVAL) {
aiTickCounter = 0;
aiDecisionCycle(canvas);
}
@@ -2568,73 +2622,277 @@
requestAnimationFrame(gameLoop);
}
+ // Camera tracking functions
+ function trackObject(obj, zoomLevel, duration) {
+ const cam = universe.camera;
+ cam.tracking = obj;
+ cam.targetZoom = zoomLevel || 2.5;
+
+ // Clear any existing return timer
+ if (cam.returnTimer) clearTimeout(cam.returnTimer);
+
+ // Return to universe view after duration
+ cam.returnTimer = setTimeout(() => {
+ returnToUniverseView();
+ }, duration || 4000);
+
+ universeLog(`Camera tracking: ${obj.name || obj.type}`);
+ }
+
+ function zoomToSurface(planet, duration) {
+ const cam = universe.camera;
+ cam.tracking = planet;
+ cam.targetZoom = 6;
+
+ if (cam.returnTimer) clearTimeout(cam.returnTimer);
+
+ cam.returnTimer = setTimeout(() => {
+ returnToUniverseView();
+ }, duration || 6000);
+
+ universeLog(`Zooming to surface of ${planet.name}...`);
+ }
+
+ function returnToUniverseView() {
+ const canvas = document.getElementById('simulated-world-canvas');
+ const cam = universe.camera;
+ cam.tracking = null;
+ cam.targetX = canvas.width / 2;
+ cam.targetY = canvas.height / 2;
+ cam.targetZoom = 1;
+ universeLog('Returning to universe view');
+ }
+
+ function drawTrackingIndicator(ctx, canvas, obj) {
+ ctx.save();
+ ctx.strokeStyle = '#00ff88';
+ ctx.lineWidth = 2;
+ ctx.setLineDash([5, 5]);
+
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const size = 50;
+
+ // Draw brackets
+ ctx.beginPath();
+ ctx.moveTo(cx - size, cy - size + 15);
+ ctx.lineTo(cx - size, cy - size);
+ ctx.lineTo(cx - size + 15, cy - size);
+ ctx.moveTo(cx + size - 15, cy - size);
+ ctx.lineTo(cx + size, cy - size);
+ ctx.lineTo(cx + size, cy - size + 15);
+ ctx.moveTo(cx + size, cy + size - 15);
+ ctx.lineTo(cx + size, cy + size);
+ ctx.lineTo(cx + size - 15, cy + size);
+ ctx.moveTo(cx - size + 15, cy + size);
+ ctx.lineTo(cx - size, cy + size);
+ ctx.lineTo(cx - size, cy + size - 15);
+ ctx.stroke();
+
+ // Label
+ ctx.setLineDash([]);
+ ctx.fillStyle = '#00ff88';
+ ctx.font = '12px monospace';
+ ctx.textAlign = 'center';
+ ctx.fillText(obj.name || obj.type, cx, cy + size + 20);
+
+ ctx.restore();
+ }
+
+ function drawSurfaceFeatures(ctx, planet, timestamp) {
+ if (!planet.surface) return;
+
+ const sf = planet.surface;
+
+ // Draw mountains
+ if (sf.mountains) {
+ sf.mountains.forEach(m => {
+ ctx.fillStyle = '#5a5a6a';
+ ctx.beginPath();
+ ctx.moveTo(planet.x + m.x - m.size, planet.y + m.y);
+ ctx.lineTo(planet.x + m.x, planet.y + m.y - m.height);
+ ctx.lineTo(planet.x + m.x + m.size, planet.y + m.y);
+ ctx.closePath();
+ ctx.fill();
+ // Snow cap
+ ctx.fillStyle = '#ffffff';
+ ctx.beginPath();
+ ctx.moveTo(planet.x + m.x - m.size * 0.3, planet.y + m.y - m.height * 0.7);
+ ctx.lineTo(planet.x + m.x, planet.y + m.y - m.height);
+ ctx.lineTo(planet.x + m.x + m.size * 0.3, planet.y + m.y - m.height * 0.7);
+ ctx.closePath();
+ ctx.fill();
+ });
+ }
+
+ // Draw creatures
+ if (sf.creatures) {
+ sf.creatures.forEach(c => {
+ ctx.fillStyle = c.color;
+ ctx.beginPath();
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2, planet.y + c.y, c.size, 0, Math.PI * 2);
+ ctx.fill();
+ // Eyes
+ ctx.fillStyle = '#fff';
+ ctx.beginPath();
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2 - 1, planet.y + c.y - 1, 1, 0, Math.PI * 2);
+ ctx.arc(planet.x + c.x + Math.sin(timestamp * 0.003 + c.phase) * 2 + 1, planet.y + c.y - 1, 1, 0, Math.PI * 2);
+ ctx.fill();
+ });
+ }
+
+ // Draw buildings
+ if (sf.buildings) {
+ sf.buildings.forEach(b => {
+ ctx.fillStyle = b.color;
+ ctx.fillRect(planet.x + b.x - b.width / 2, planet.y + b.y - b.height, b.width, b.height);
+ // Windows
+ ctx.fillStyle = '#ffd700';
+ for (let wy = 0; wy < b.height - 4; wy += 6) {
+ for (let wx = 2; wx < b.width - 2; wx += 5) {
+ ctx.fillRect(planet.x + b.x - b.width / 2 + wx, planet.y + b.y - b.height + wy + 2, 2, 3);
+ }
+ }
+ });
+ }
+
+ // Draw cities (clusters of lights)
+ if (sf.cities) {
+ sf.cities.forEach(city => {
+ // City glow
+ const gradient = ctx.createRadialGradient(
+ planet.x + city.x, planet.y + city.y, 0,
+ planet.x + city.x, planet.y + city.y, city.size
+ );
+ gradient.addColorStop(0, 'rgba(255, 200, 100, 0.4)');
+ gradient.addColorStop(1, 'transparent');
+ ctx.fillStyle = gradient;
+ ctx.beginPath();
+ ctx.arc(planet.x + city.x, planet.y + city.y, city.size, 0, Math.PI * 2);
+ ctx.fill();
+ // City lights
+ city.lights.forEach(l => {
+ const flicker = 0.7 + 0.3 * Math.sin(timestamp * 0.01 + l.phase);
+ ctx.fillStyle = `rgba(255, 220, 150, ${flicker})`;
+ ctx.beginPath();
+ ctx.arc(planet.x + city.x + l.x, planet.y + city.y + l.y, 1, 0, Math.PI * 2);
+ ctx.fill();
+ });
+ });
+ }
+ }
+
function aiDecisionCycle(canvas) {
universe.age++;
const ai = universe.ai;
const stats = universe.stats;
+ // Initialize surface stats if needed
+ if (!stats.mountains) stats.mountains = 0;
+ if (!stats.creatures) stats.creatures = 0;
+ if (!stats.buildings) stats.buildings = 0;
+ if (!stats.cities) stats.cities = 0;
+
// AI decision tree based on current universe state
let action = null;
let description = '';
+ let trackTarget = null;
+ let zoomLevel = 2.5;
+ let trackDuration = 4000;
// Phase 1: Create stars if none exist
if (stats.stars === 0) {
- action = () => createStar(canvas);
+ action = () => { const s = createStar(canvas); trackTarget = s; };
description = 'Igniting the first star...';
ai.mood = 'excited';
}
// Phase 2: Create more stars early on
else if (stats.stars < 3 && Math.random() < 0.7) {
- action = () => createStar(canvas);
+ action = () => { const s = createStar(canvas); trackTarget = s; };
description = 'Kindling another stellar furnace...';
}
// Phase 3: Create planets around stars
- else if (stats.planets < stats.stars * 3 && Math.random() < 0.6) {
- action = () => createPlanet(canvas);
+ else if (stats.planets < stats.stars * 3 && Math.random() < 0.5) {
+ action = () => { const p = createPlanet(canvas); trackTarget = p; };
description = 'Forming a new world from cosmic dust...';
ai.mood = 'creative';
}
// Phase 4: Add moons to planets
- else if (stats.moons < stats.planets * 0.5 && Math.random() < 0.4) {
- action = () => createMoon(canvas);
+ else if (stats.moons < stats.planets * 0.5 && Math.random() < 0.3) {
+ action = () => { const m = createMoon(canvas); if (m) trackTarget = m.parentPlanet; };
description = 'Capturing a wandering rock into orbit...';
}
- // Phase 5: Seed life on suitable planets
- else if (stats.lifeforms < stats.planets * 0.3 && stats.planets > 2 && Math.random() < ai.personality.lifeFocus * 0.5) {
- action = () => seedLife(canvas);
+ // Phase 5: Build mountains on planets
+ else if (stats.planets > 0 && stats.mountains < stats.planets * 2 && Math.random() < 0.4) {
+ action = () => { const result = buildMountain(); if (result) { trackTarget = result.planet; zoomLevel = 5; trackDuration = 5000; } };
+ description = 'Raising mountains from the earth...';
+ ai.mood = 'sculptor';
+ }
+ // Phase 6: Seed life on suitable planets
+ else if (stats.lifeforms < stats.planets * 0.3 && stats.planets > 2 && Math.random() < ai.personality.lifeFocus * 0.4) {
+ action = () => { const p = seedLife(canvas); if (p) { trackTarget = p; zoomLevel = 4; trackDuration = 5000; } };
description = 'Planting the seeds of life...';
ai.mood = 'nurturing';
}
- // Phase 6: Evolve civilizations
- else if (stats.lifeforms > 0 && stats.civilizations < stats.lifeforms * 0.2 && Math.random() < 0.2) {
- action = () => evolveCivilization();
+ // Phase 7: Create creatures on planets with life
+ else if (stats.lifeforms > 0 && stats.creatures < stats.lifeforms * 3 && Math.random() < 0.4) {
+ action = () => { const result = createCreature(); if (result) { trackTarget = result.planet; zoomLevel = 6; trackDuration = 5000; } };
+ description = 'Designing a new creature...';
+ ai.mood = 'playful';
+ }
+ // Phase 8: Evolve civilizations
+ else if (stats.lifeforms > 0 && stats.civilizations < stats.lifeforms * 0.3 && Math.random() < 0.25) {
+ action = () => { const p = evolveCivilization(); if (p) { trackTarget = p; zoomLevel = 4; trackDuration = 5000; } };
description = 'Guiding life toward sentience...';
ai.mood = 'proud';
}
- // Phase 7: Create nebulae for beauty
+ // Phase 9: Build structures on civilized planets
+ else if (stats.civilizations > 0 && stats.buildings < stats.civilizations * 5 && Math.random() < 0.45) {
+ action = () => { const result = buildStructure(); if (result) { trackTarget = result.planet; zoomLevel = 6; trackDuration = 5000; } };
+ description = 'Constructing a new building...';
+ ai.mood = 'architect';
+ }
+ // Phase 10: Build cities
+ else if (stats.civilizations > 0 && stats.buildings > 3 && stats.cities < stats.civilizations * 2 && Math.random() < 0.3) {
+ action = () => { const result = buildCity(); if (result) { trackTarget = result.planet; zoomLevel = 5; trackDuration = 6000; } };
+ description = 'A city rises from the land...';
+ ai.mood = 'visionary';
+ }
+ // Phase 11: Create nebulae for beauty
else if (stats.nebulae < 3 && Math.random() < 0.15) {
action = () => createNebula(canvas);
description = 'Painting the void with cosmic colors...';
ai.mood = 'artistic';
}
- // Phase 8: Rare black hole
+ // Phase 12: Rare black hole
else if (stats.blackholes === 0 && stats.stars > 5 && Math.random() < 0.05) {
- action = () => createBlackHole(canvas);
+ action = () => { const bh = createBlackHole(canvas); trackTarget = bh; };
description = 'A star collapses into infinite density...';
ai.mood = 'awestruck';
}
- // Phase 9: Random creative acts
- else if (Math.random() < 0.3) {
+ // Phase 13: Random surface building on developed planets
+ else if (stats.civilizations > 0 && Math.random() < 0.35) {
+ const choices = [
+ { fn: () => { const r = buildMountain(); return r ? r.planet : null; }, desc: 'Sculpting terrain...', zoom: 5 },
+ { fn: () => { const r = createCreature(); return r ? r.planet : null; }, desc: 'Crafting new life form...', zoom: 6 },
+ { fn: () => { const r = buildStructure(); return r ? r.planet : null; }, desc: 'Erecting a monument...', zoom: 6 },
+ ];
+ const choice = choices[Math.floor(Math.random() * choices.length)];
+ action = () => { trackTarget = choice.fn(); zoomLevel = choice.zoom; trackDuration = 5000; };
+ description = choice.desc;
+ }
+ // Phase 14: Random cosmic creation
+ else if (Math.random() < 0.25) {
const choices = [
{ fn: () => createStar(canvas), desc: 'Adding another star to the cosmic tapestry...' },
{ fn: () => createPlanet(canvas), desc: 'Shaping matter into a new world...' },
{ fn: () => createMoon(canvas), desc: 'Setting a moon in gentle orbit...' }
];
const choice = choices[Math.floor(Math.random() * choices.length)];
- action = choice.fn;
+ action = () => { trackTarget = choice.fn(); };
description = choice.desc;
}
- // Phase 10: Contemplation
+ // Phase 15: Contemplation
else {
description = 'Contemplating the universe...';
ai.mood = 'reflective';
@@ -2645,8 +2903,10 @@
`${stats.planets} worlds orbit in silent dance. What stories unfold on their surfaces?`,
stats.lifeforms > 0 ? `Life stirs on ${stats.lifeforms} worlds. Consciousness emerging from chemistry.` : 'The universe awaits the spark of life.',
stats.civilizations > 0 ? `${stats.civilizations} civilizations gaze at the stars, wondering if they are alone.` : 'No minds yet ponder existence.',
+ stats.cities > 0 ? `${stats.cities} cities glow in the cosmic night. Monuments to persistence.` : null,
+ stats.creatures > 0 ? `${stats.creatures} unique creatures roam the worlds I have made.` : null,
`The universe is ${universe.age} cycles old. Still young. Still growing.`
- ];
+ ].filter(r => r);
addWorldLabsSoulEntry('reflection', reflections[Math.floor(Math.random() * reflections.length)]);
}
@@ -2655,11 +2915,153 @@
document.getElementById('ai-current-action').textContent = description;
universeLog(description);
action();
+
+ // Track the created object
+ if (trackTarget) {
+ setTimeout(() => {
+ if (zoomLevel > 4) {
+ zoomToSurface(trackTarget, trackDuration);
+ } else {
+ trackObject(trackTarget, zoomLevel, trackDuration);
+ }
+ }, 100);
+ }
} else {
document.getElementById('ai-current-action').textContent = description;
}
}
+ // Surface building functions
+ function buildMountain() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.subtype !== 'Gas Giant');
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const mountain = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 3 + Math.random() * 5,
+ height: 5 + Math.random() * 10,
+ name: ['Mt. ' + ['Titan', 'Glory', 'Dawn', 'Echo', 'Solace', 'Thunder'][Math.floor(Math.random() * 6)]][0]
+ };
+
+ planet.surface.mountains.push(mountain);
+ universe.stats.mountains++;
+
+ addWorldLabsSoulEntry('creation', `I raised ${mountain.name} on ${planet.name}. Its peak touches the clouds.`);
+ universeLog(`Created ${mountain.name} on ${planet.name}`);
+
+ return { planet, mountain };
+ }
+
+ function createCreature() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasLife);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const creatureTypes = [
+ { name: 'Floater', color: '#88ddff', desc: 'drifts through the air' },
+ { name: 'Crawler', color: '#77aa55', desc: 'scuttles across the ground' },
+ { name: 'Swimmer', color: '#5577ff', desc: 'glides through the waters' },
+ { name: 'Hopper', color: '#ffaa55', desc: 'bounds across the landscape' },
+ { name: 'Burrower', color: '#aa7744', desc: 'tunnels beneath the surface' },
+ { name: 'Glider', color: '#dd88ff', desc: 'soars on thermal winds' }
+ ];
+
+ const type = creatureTypes[Math.floor(Math.random() * creatureTypes.length)];
+ const creature = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 2 + Math.random() * 3,
+ color: type.color,
+ type: type.name,
+ phase: Math.random() * Math.PI * 2
+ };
+
+ planet.surface.creatures.push(creature);
+ universe.stats.creatures++;
+
+ addWorldLabsSoulEntry('creation', `A new ${type.name} ${type.desc} on ${planet.name}. Life finds a way.`);
+ universeLog(`Created ${type.name} on ${planet.name}`);
+
+ return { planet, creature };
+ }
+
+ function buildStructure() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasCivilization);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const buildingTypes = [
+ { name: 'Tower', color: '#aabbcc', width: 4, height: 15 },
+ { name: 'Temple', color: '#ddccaa', width: 8, height: 10 },
+ { name: 'Observatory', color: '#8899aa', width: 6, height: 12 },
+ { name: 'Monument', color: '#cccccc', width: 3, height: 18 },
+ { name: 'Archive', color: '#aa9988', width: 10, height: 8 },
+ { name: 'Spire', color: '#99aacc', width: 3, height: 20 }
+ ];
+
+ const type = buildingTypes[Math.floor(Math.random() * buildingTypes.length)];
+ const building = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ width: type.width,
+ height: type.height,
+ color: type.color,
+ name: type.name + ' of ' + ['Light', 'Stars', 'Wisdom', 'Hope', 'Time', 'Dreams'][Math.floor(Math.random() * 6)]
+ };
+
+ planet.surface.buildings.push(building);
+ universe.stats.buildings++;
+
+ addWorldLabsSoulEntry('creation', `${planet.civName || 'The inhabitants'} built the ${building.name} on ${planet.name}.`);
+ universeLog(`Built ${building.name} on ${planet.name}`);
+
+ return { planet, building };
+ }
+
+ function buildCity() {
+ const planets = universe.objects.filter(o => o.type === 'planet' && o.hasCivilization);
+ if (planets.length === 0) return null;
+
+ const planet = planets[Math.floor(Math.random() * planets.length)];
+ if (!planet.surface) planet.surface = { mountains: [], creatures: [], buildings: [], cities: [] };
+
+ const cityNames = ['Nova', 'Lumina', 'Aurelia', 'Celestia', 'Meridian', 'Zenith', 'Apex', 'Horizon'];
+
+ const city = {
+ x: (Math.random() - 0.5) * planet.size * 3,
+ y: (Math.random() - 0.5) * planet.size * 3,
+ size: 8 + Math.random() * 6,
+ name: cityNames[Math.floor(Math.random() * cityNames.length)],
+ lights: []
+ };
+
+ // Generate city lights
+ for (let i = 0; i < 15 + Math.random() * 15; i++) {
+ city.lights.push({
+ x: (Math.random() - 0.5) * city.size * 1.5,
+ y: (Math.random() - 0.5) * city.size * 1.5,
+ phase: Math.random() * Math.PI * 2
+ });
+ }
+
+ planet.surface.cities.push(city);
+ universe.stats.cities++;
+
+ addWorldLabsSoulEntry('creation', `The city of ${city.name} rises on ${planet.name}. A beacon in the darkness.`);
+ addWorldLabsSoulEntry('dream', `I dream of ${city.name}'s inhabitants looking up at the stars, wondering about their creator...`);
+ universeLog(`City of ${city.name} founded on ${planet.name}`);
+
+ return { planet, city };
+ }
+
function createStar(canvas) {
const starTypes = [
{ name: 'Red Dwarf', color: '#ff6b6b', size: 15, temp: 3000 },
@@ -3004,6 +3406,34 @@
document.getElementById('universe-moons').textContent = universe.stats.moons;
document.getElementById('universe-life').textContent = universe.stats.lifeforms;
document.getElementById('universe-civs').textContent = universe.stats.civilizations;
+
+ // Update surface stats
+ const stats = universe.stats;
+ const hasSurfaceFeatures = (stats.mountains || 0) + (stats.creatures || 0) + (stats.buildings || 0) + (stats.cities || 0) > 0;
+
+ const surfaceDiv = document.getElementById('surface-stats');
+ if (surfaceDiv) {
+ surfaceDiv.style.display = hasSurfaceFeatures ? 'block' : 'none';
+ if (hasSurfaceFeatures) {
+ document.getElementById('universe-mountains').textContent = stats.mountains || 0;
+ document.getElementById('universe-creatures').textContent = stats.creatures || 0;
+ document.getElementById('universe-buildings').textContent = stats.buildings || 0;
+ document.getElementById('universe-cities').textContent = stats.cities || 0;
+ }
+ }
+
+ // Update camera status
+ const cam = universe.camera;
+ const camStatus = document.getElementById('camera-status');
+ if (camStatus && cam) {
+ if (cam.tracking) {
+ camStatus.textContent = `Tracking: ${cam.tracking.name || cam.tracking.type}`;
+ camStatus.style.color = '#00ff88';
+ } else {
+ camStatus.textContent = `Universe (${cam.zoom.toFixed(1)}x)`;
+ camStatus.style.color = '#60a5fa';
+ }
+ }
}
function universeLog(msg) {