[claude] Re-implement shockwave and fireworks on PR merge (#479) #500

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

130
app.js
View File

@@ -45,6 +45,17 @@ let chatOpen = true;
let loadProgress = 0;
let performanceTier = 'high';
// ═══ CELEBRATIONS STATE ═══
const shockwaveRings = [];
const fireworkBursts = [];
const SHOCKWAVE_DURATION = 2.5;
const SHOCKWAVE_MAX_RADIUS = 14;
const SHOCKWAVE_RING_COUNT = 3;
const FIREWORK_BURST_PARTICLES = 80;
const FIREWORK_BURST_DURATION = 2.2;
const FIREWORK_GRAVITY = -5.0;
const FIREWORK_COLORS = [0xff4466, 0xffaa00, 0x00ffaa, 0x4488ff, 0xff44ff, 0xffff44, 0x00ffff];
// ═══ NAVIGATION SYSTEM ═══
const NAV_MODES = ['walk', 'orbit', 'fly'];
let navModeIdx = 0;
@@ -141,6 +152,17 @@ async function init() {
window.addEventListener('resize', onResize);
debugOverlay = document.getElementById('debug-overlay');
window.addEventListener('pr-notification', (event) => {
console.log('[nexus] PR notification:', event.detail);
if (event.detail && event.detail.action === 'merged') {
triggerMergeFlash();
}
});
window.addEventListener('milestone-complete', () => {
triggerFireworks();
});
updateLoad(100);
setTimeout(() => {
@@ -1480,6 +1502,47 @@ function gameLoop() {
core.material.emissiveIntensity = 1.5 + Math.sin(elapsed * 2) * 0.5;
}
// Update shockwave rings
for (let i = shockwaveRings.length - 1; i >= 0; i--) {
const ring = shockwaveRings[i];
const age = elapsed - ring.startTime - ring.delay;
if (age < 0) continue;
const t = Math.min(age / SHOCKWAVE_DURATION, 1);
if (t >= 1) {
scene.remove(ring.mesh);
ring.mat.dispose();
ring.mesh.geometry.dispose();
shockwaveRings.splice(i, 1);
continue;
}
const radius = t * SHOCKWAVE_MAX_RADIUS;
ring.mesh.scale.setScalar(radius);
ring.mat.opacity = (1 - t) * 0.8;
}
// Update firework bursts
for (let i = fireworkBursts.length - 1; i >= 0; i--) {
const burst = fireworkBursts[i];
const age = elapsed - burst.startTime;
const t = Math.min(age / FIREWORK_BURST_DURATION, 1);
if (t >= 1) {
scene.remove(burst.points);
burst.mat.dispose();
burst.geo.dispose();
fireworkBursts.splice(i, 1);
continue;
}
burst.mat.opacity = 1 - t * t;
const halfGAge2 = 0.5 * FIREWORK_GRAVITY * age * age;
const posArr = burst.geo.attributes.position.array;
for (let j = 0; j < FIREWORK_BURST_PARTICLES; j++) {
posArr[j * 3] = burst.origins[j * 3] + burst.velocities[j * 3] * age;
posArr[j * 3 + 1] = burst.origins[j * 3 + 1] + burst.velocities[j * 3 + 1] * age + halfGAge2;
posArr[j * 3 + 2] = burst.origins[j * 3 + 2] + burst.velocities[j * 3 + 2] * age;
}
burst.geo.attributes.position.needsUpdate = true;
}
composer.render();
frameCount++;
@@ -1679,6 +1742,73 @@ function addAgentLog(agentId, text) {
}
}
// ═══ CELEBRATIONS ═══
function triggerShockwave() {
const now = clock.getElapsedTime();
for (let i = 0; i < SHOCKWAVE_RING_COUNT; i++) {
const mat = new THREE.MeshBasicMaterial({
color: 0x00ffff, transparent: true, opacity: 0,
side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending,
});
const geo = new THREE.RingGeometry(0.9, 1.0, 64);
const mesh = new THREE.Mesh(geo, mat);
mesh.rotation.x = -Math.PI / 2;
mesh.position.y = 0.02;
scene.add(mesh);
shockwaveRings.push({ mesh, mat, startTime: now, delay: i * 0.35 });
}
}
function spawnFireworkBurst(origin, color) {
const now = clock.getElapsedTime();
const count = FIREWORK_BURST_PARTICLES;
const positions = new Float32Array(count * 3);
const origins = new Float32Array(count * 3);
const velocities = new Float32Array(count * 3);
for (let i = 0; i < count; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
const speed = 2.5 + Math.random() * 3.5;
velocities[i * 3] = Math.sin(phi) * Math.cos(theta) * speed;
velocities[i * 3 + 1] = Math.sin(phi) * Math.sin(theta) * speed;
velocities[i * 3 + 2] = Math.cos(phi) * speed;
origins[i * 3] = origin.x;
origins[i * 3 + 1] = origin.y;
origins[i * 3 + 2] = origin.z;
positions[i * 3] = origin.x;
positions[i * 3 + 1] = origin.y;
positions[i * 3 + 2] = origin.z;
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const mat = new THREE.PointsMaterial({
color, size: 0.35, sizeAttenuation: true,
transparent: true, opacity: 1.0,
blending: THREE.AdditiveBlending, depthWrite: false,
});
const points = new THREE.Points(geo, mat);
scene.add(points);
fireworkBursts.push({ points, geo, mat, origins, velocities, startTime: now });
}
function triggerFireworks() {
for (let i = 0; i < 6; i++) {
const delay = i * 0.35;
setTimeout(() => {
const x = (Math.random() - 0.5) * 12;
const y = 8 + Math.random() * 6;
const z = (Math.random() - 0.5) * 12;
const color = FIREWORK_COLORS[Math.floor(Math.random() * FIREWORK_COLORS.length)];
spawnFireworkBurst(new THREE.Vector3(x, y, z), color);
}, delay * 1000);
}
}
function triggerMergeFlash() {
triggerShockwave();
triggerFireworks();
}
function triggerHarnessPulse() {
if (!harnessPulseMesh) return;
harnessPulseMesh.scale.setScalar(0.1);