[claude] Re-implement gravity anomaly zones (#478) #501

Closed
claude wants to merge 1 commits from claude/issue-478 into main

118
app.js
View File

@@ -39,6 +39,7 @@ let thoughtStreamMesh;
let harnessPulseMesh;
let powerMeterBars = [];
let particles, dustParticles;
let gravityZoneObjects = [];
let debugOverlay;
let frameCount = 0, lastFPSTime = 0, fps = 0;
let chatOpen = true;
@@ -101,6 +102,7 @@ async function init() {
const response = await fetch('./portals.json');
const portalData = await response.json();
createPortals(portalData);
rebuildGravityZones();
} catch (e) {
console.error('Failed to load portals.json:', e);
addChatMessage('error', 'Portal registry offline. Check logs.');
@@ -118,6 +120,7 @@ async function init() {
updateLoad(80);
createParticles();
createDustParticles();
createGravityZones();
updateLoad(85);
createAmbientStructures();
createAgentPresences();
@@ -954,6 +957,100 @@ function createDustParticles() {
scene.add(dustParticles);
}
// ═══ GRAVITY ANOMALY ZONES ═══
const GRAVITY_ANOMALY_FLOOR = 0.2;
const GRAVITY_ANOMALY_CEIL = 16.0;
const GRAVITY_ZONE_DEFS = [
{ x: -8, z: -6, radius: 3.5, color: 0x00ffcc, particleCount: 180 },
{ x: 10, z: 4, radius: 3.0, color: 0xaa44ff, particleCount: 160 },
{ x: -3, z: 9, radius: 2.5, color: 0xff8844, particleCount: 140 },
];
function createGravityZones() {
for (const zone of GRAVITY_ZONE_DEFS) {
const ringGeo = new THREE.RingGeometry(zone.radius - 0.15, zone.radius + 0.15, 64);
const ringMat = new THREE.MeshBasicMaterial({
color: zone.color, transparent: true, opacity: 0.4,
side: THREE.DoubleSide, depthWrite: false,
});
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.rotation.x = -Math.PI / 2;
ring.position.set(zone.x, GRAVITY_ANOMALY_FLOOR + 0.05, zone.z);
scene.add(ring);
const discGeo = new THREE.CircleGeometry(zone.radius - 0.15, 64);
const discMat = new THREE.MeshBasicMaterial({
color: zone.color, transparent: true, opacity: 0.04,
side: THREE.DoubleSide, depthWrite: false,
});
const disc = new THREE.Mesh(discGeo, discMat);
disc.rotation.x = -Math.PI / 2;
disc.position.set(zone.x, GRAVITY_ANOMALY_FLOOR + 0.04, zone.z);
scene.add(disc);
const count = particleCount(zone.particleCount);
const positions = new Float32Array(count * 3);
const driftPhases = new Float32Array(count);
const velocities = new Float32Array(count);
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const r = Math.sqrt(Math.random()) * zone.radius;
positions[i * 3] = zone.x + Math.cos(angle) * r;
positions[i * 3 + 1] = GRAVITY_ANOMALY_FLOOR + Math.random() * (GRAVITY_ANOMALY_CEIL - GRAVITY_ANOMALY_FLOOR);
positions[i * 3 + 2] = zone.z + Math.sin(angle) * r;
driftPhases[i] = Math.random() * Math.PI * 2;
velocities[i] = 0.03 + Math.random() * 0.04;
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const mat = new THREE.PointsMaterial({
color: zone.color, size: 0.10, sizeAttenuation: true,
transparent: true, opacity: 0.7, depthWrite: false,
});
const points = new THREE.Points(geo, mat);
scene.add(points);
gravityZoneObjects.push({ zone: { ...zone, particleCount: count }, ring, ringMat, disc, discMat, points, geo, driftPhases, velocities });
}
}
function rebuildGravityZones() {
if (portals.length === 0) return;
for (let i = 0; i < Math.min(portals.length, gravityZoneObjects.length); i++) {
const portal = portals[i];
const gz = gravityZoneObjects[i];
const portalColor = new THREE.Color(portal.config.color);
const isOnline = portal.config.status !== 'offline';
gz.ring.position.set(portal.config.position.x, GRAVITY_ANOMALY_FLOOR + 0.05, portal.config.position.z);
gz.disc.position.set(portal.config.position.x, GRAVITY_ANOMALY_FLOOR + 0.04, portal.config.position.z);
gz.zone.x = portal.config.position.x;
gz.zone.z = portal.config.position.z;
gz.ringMat.color.copy(portalColor);
gz.discMat.color.copy(portalColor);
gz.points.material.color.copy(portalColor);
gz.ringMat.opacity = isOnline ? 0.4 : 0.08;
gz.discMat.opacity = isOnline ? 0.04 : 0.01;
gz.points.material.opacity = isOnline ? 0.7 : 0.15;
const pos = gz.geo.attributes.position.array;
for (let j = 0; j < gz.zone.particleCount; j++) {
const angle = Math.random() * Math.PI * 2;
const r = Math.sqrt(Math.random()) * gz.zone.radius;
pos[j * 3] = gz.zone.x + Math.cos(angle) * r;
pos[j * 3 + 2] = gz.zone.z + Math.sin(angle) * r;
}
gz.geo.attributes.position.needsUpdate = true;
}
}
// ═══ AMBIENT STRUCTURES ═══
function createAmbientStructures() {
const crystalMat = new THREE.MeshPhysicalMaterial({
@@ -1429,6 +1526,27 @@ function gameLoop() {
portal.pSystem.geometry.attributes.position.needsUpdate = true;
});
// Animate Gravity Anomaly Zones
for (const gz of gravityZoneObjects) {
const pos = gz.geo.attributes.position.array;
const count = gz.zone.particleCount;
for (let i = 0; i < count; i++) {
pos[i * 3 + 1] += gz.velocities[i];
pos[i * 3] += Math.sin(elapsed * 0.5 + gz.driftPhases[i]) * 0.003;
pos[i * 3 + 2] += Math.cos(elapsed * 0.5 + gz.driftPhases[i]) * 0.003;
if (pos[i * 3 + 1] > GRAVITY_ANOMALY_CEIL) {
const angle = Math.random() * Math.PI * 2;
const r = Math.sqrt(Math.random()) * gz.zone.radius;
pos[i * 3] = gz.zone.x + Math.cos(angle) * r;
pos[i * 3 + 1] = GRAVITY_ANOMALY_FLOOR + Math.random() * 2.0;
pos[i * 3 + 2] = gz.zone.z + Math.sin(angle) * r;
}
}
gz.geo.attributes.position.needsUpdate = true;
gz.ringMat.opacity = 0.3 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.1;
gz.discMat.opacity = 0.02 + Math.sin(elapsed * 1.5 + gz.zone.x) * 0.015;
}
// Animate Vision Points
visionPoints.forEach(vp => {
vp.crystal.rotation.y = elapsed * 0.8;