Add distinct theme core animations and fix retro redraw

This commit is contained in:
Claude
2026-09-03 23:54:27 -07:00
parent 97b0faa4a6
commit 198081706c
7 changed files with 680 additions and 46 deletions
+64 -34
View File
@@ -13,6 +13,9 @@ class StarshipVisualizer {
this.warpParticles = [];
this.mode = 'warp-core'; // 'warp-core' or 'time-rotor'
this.rotorPhase = 0;
this.coreTime = 0;
this.coreLoad = 0.6;
this.lastFrameTime = null;
// Hook into warp core pulse callback
if (this.warpSynth) {
@@ -72,6 +75,8 @@ class StarshipVisualizer {
}
spawnWarpPulses() {
// The new machines render their own flow; only legacy particle modes need a pool.
if (this.mode !== 'warp-core' && this.mode !== 'time-rotor') return;
for (let i = 0; i < 6; i++) {
this.warpParticles.push({
y: 0.5, // Center matter/antimatter reaction plane
@@ -83,32 +88,48 @@ class StarshipVisualizer {
}
startRenderLoop() {
const render = () => {
this.lastFrameTime = null;
const render = (now) => {
const elapsed = this.lastFrameTime === null ? 0 : Math.min((now - this.lastFrameTime) / 1000, 0.05);
this.lastFrameTime = now;
const volume = this.warpSynth && !this.warpSynth.isMuted ? this.warpSynth.params.volume : 0;
const targetLoad = Math.max(0, Math.min(1, volume * (0.65 + this.pulseEnergy * 0.35)));
this.coreLoad += (targetLoad - this.coreLoad) * (1 - Math.exp(-elapsed * 6));
this.coreTime += elapsed * (this.mode === 'tactical-flywheel' ? 0.6 + this.coreLoad * 0.8 : 1);
this.renderSpectrum();
switch (this.mode) {
case 'time-rotor':
this.renderTimeRotor();
break;
case 'industrial-reactor':
this.renderIndustrialReactor();
break;
case 'bio-heart':
this.renderBioHeart();
break;
case 'retro-oscilloscope':
this.renderRetroOscilloscope();
break;
case 'singularity-core':
this.renderSingularityCore();
break;
case 'warp-core':
default:
this.renderWarpCore();
break;
if (window.CoreAnimations && CoreAnimations.has(this.mode)) {
if (this.warpCoreCanvas && this.warpCoreCtx) {
CoreAnimations.render(this.warpCoreCtx, this.mode,
this.warpCoreCanvas.width / window.devicePixelRatio,
this.warpCoreCanvas.height / window.devicePixelRatio,
this.coreTime, this.coreLoad);
}
} else {
switch (this.mode) {
case 'time-rotor':
this.renderTimeRotor();
break;
case 'industrial-reactor':
this.renderIndustrialReactor();
break;
case 'bio-heart':
this.renderBioHeart();
break;
case 'retro-oscilloscope':
this.renderRetroOscilloscope();
break;
case 'singularity-core':
this.renderSingularityCore();
break;
case 'warp-core':
default:
this.renderWarpCore();
break;
}
}
// Decay pulse energy smoothly
this.pulseEnergy = Math.max(0.15, this.pulseEnergy * 0.94);
this.pulseEnergy = Math.max(0.15, this.pulseEnergy * Math.pow(0.94, elapsed * 60));
this.animationFrameId = requestAnimationFrame(render);
};
@@ -135,7 +156,7 @@ class StarshipVisualizer {
if (this.mode === 'time-rotor' || this.mode === 'singularity-core') gridCol = 'rgba(0, 229, 255, 0.12)';
else if (this.mode === 'bio-heart') gridCol = 'rgba(16, 185, 129, 0.12)';
else if (this.mode === 'retro-oscilloscope') gridCol = 'rgba(34, 197, 94, 0.15)';
else if (this.mode === 'industrial-reactor') gridCol = 'rgba(245, 158, 11, 0.15)';
else if (this.mode === 'industrial-reactor' || this.mode === 'compression-furnace' || this.mode === 'station-hub') gridCol = 'rgba(245, 158, 11, 0.15)';
ctx.strokeStyle = gridCol;
ctx.lineWidth = 1;
@@ -171,7 +192,7 @@ class StarshipVisualizer {
else col = '#c084fc';
} else if (this.mode === 'retro-oscilloscope') {
col = i < 28 ? '#22c55e' : '#86efac';
} else if (this.mode === 'industrial-reactor') {
} else if (this.mode === 'industrial-reactor' || this.mode === 'compression-furnace' || this.mode === 'station-hub') {
if (i < 8) col = '#d97706';
else if (i < 20) col = '#f59e0b';
else if (i < 28) col = '#fbbf24';
@@ -502,8 +523,8 @@ class StarshipVisualizer {
* Retro Oscilloscope & Analog Astrogator (Jupiter 2, Discovery One)
* 1950s/60s green phosphor CRT screen with glowing Lissajous audio wave rings
*/
renderRetroOscilloscope() {
if (!this.warpCoreCanvas || !this.warpCoreCtx || !this.am.analyser) return;
renderRetroOscilloscope() {
if (!this.warpCoreCanvas || !this.warpCoreCtx) return;
const ctx = this.warpCoreCtx;
const w = this.warpCoreCanvas.width / window.devicePixelRatio;
const h = this.warpCoreCanvas.height / window.devicePixelRatio;
@@ -532,10 +553,12 @@ class StarshipVisualizer {
ctx.lineTo(centerX, centerY + crtRadius);
ctx.stroke();
// Lissajous audio waveform
const bufferLength = this.am.analyser.fftSize;
const dataArray = new Uint8Array(bufferLength);
this.am.analyser.getByteTimeDomainData(dataArray);
// Draw a neutral trace before audio initialization, replacing the previous theme.
const analyser = this.am.analyser;
const bufferLength = analyser ? analyser.fftSize : 128;
const dataArray = new Uint8Array(bufferLength);
if (analyser) analyser.getByteTimeDomainData(dataArray);
else dataArray.fill(128);
ctx.strokeStyle = '#86efac';
ctx.shadowColor = '#22c55e';
@@ -547,16 +570,23 @@ class StarshipVisualizer {
for (let i = 0; i < points; i++) {
const idx = Math.floor((i / points) * (bufferLength / 2));
const v = (dataArray[idx] / 128.0) - 1.0;
const angle = (i / points) * Math.PI * 2 + this.rotorPhase;
const angle = (i / points) * Math.PI * 2 + this.coreTime;
const r = (crtRadius * 0.65) + (v * 28 * (0.8 + this.pulseEnergy));
const x = centerX + Math.cos(angle) * r;
const y = centerY + Math.sin(angle) * r;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.stroke();
ctx.shadowBlur = 0;
ctx.closePath();
ctx.stroke();
// A sweep marker makes rotation legible even when the audio trace is silent.
const sweepRadius = (crtRadius * 0.65) + ((dataArray[0] / 128.0) - 1.0) * 28 * (0.8 + this.pulseEnergy);
ctx.fillStyle = '#d1fae5';
ctx.beginPath();
ctx.arc(centerX + Math.cos(this.coreTime) * sweepRadius,
centerY + Math.sin(this.coreTime) * sweepRadius, 2.5, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
/**