Files
the-nexus/modules/effects/matrix-rain.js
Alexander Whitestone 0408ceb5bc
All checks were successful
CI / validate (pull_request) Successful in 6s
CI / auto-merge (pull_request) Successful in 10s
feat: add effects modules — matrix rain, lightning, beam, runes, gravity, shockwave
Phase 4 of app.js modularization. Extracts all visual effects into self-contained
ES modules under modules/effects/ following the init(scene,state,theme)/update(elapsed,delta)
contract defined in CLAUDE.md.

Modules created:
- matrix-rain.js  — commit-density-driven 2D canvas rain (DATA-TETHERED AESTHETIC)
- lightning.js    — floating crystals + lightning arcs (DATA-TETHERED AESTHETIC)
- energy-beam.js  — Batcave terminal beam (DATA-TETHERED AESTHETIC)
- rune-ring.js    — portal-tethered orbiting rune sprites (DATA-TETHERED AESTHETIC)
- gravity-zones.js — portal-position rising particle zones (DATA-TETHERED AESTHETIC)
- shockwave.js    — shockwave ripple, fireworks, merge flash (DATA-TETHERED AESTHETIC)

All modules read data tethers from the state bus (state.zoneIntensity,
state.portals, state.activeAgentCount, state.commitHashes). No mocked data.
app.js unchanged — final wiring happens in Phase 5 slim-down.

Refs #423

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-24 14:18:42 -04:00

107 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* matrix-rain.js — Commit-density-driven 2D canvas matrix rain
*
* Category: DATA-TETHERED AESTHETIC
* Data source: state.zoneIntensity (commit activity) + state.commitHashes
*
* Renders a Katakana/hex character rain behind the Three.js canvas.
* Density and speed are tethered to commit zone activity.
* Real commit hashes are occasionally injected as characters.
*/
const MATRIX_CHARS = 'アイウエオカキクケコサシスセソタチツテトナニヌネハヒフヘホマミムメモヤユヨラリルレロワヲン0123456789ABCDEF';
const MATRIX_FONT_SIZE = 14;
let _state = null;
let _canvas = null;
let _ctx = null;
let _drops = [];
/**
* Computes mean activity [0..1] across all agent zones via state.
* @returns {number}
*/
function _totalActivity() {
if (!_state) return 0;
if (typeof _state.totalActivity === 'function') return _state.totalActivity();
const zi = _state.zoneIntensity;
if (!zi) return 0;
const vals = Object.values(zi);
return vals.reduce((s, v) => s + v, 0) / Math.max(vals.length, 1);
}
function _draw() {
if (!_canvas || !_ctx) return;
const activity = _totalActivity();
const commitHashes = _state?.commitHashes ?? [];
// Fade previous frame — creates the trailing glow
_ctx.fillStyle = 'rgba(0, 0, 8, 0.05)';
_ctx.fillRect(0, 0, _canvas.width, _canvas.height);
_ctx.font = `${MATRIX_FONT_SIZE}px monospace`;
const density = 0.1 + activity * 0.9;
const activeColCount = Math.max(1, Math.floor(_drops.length * density));
for (let i = 0; i < _drops.length; i++) {
if (i >= activeColCount) {
if (_drops[i] * MATRIX_FONT_SIZE > _canvas.height) continue;
}
let char;
if (commitHashes.length > 0 && Math.random() < 0.02) {
const hash = commitHashes[Math.floor(Math.random() * commitHashes.length)];
char = hash[Math.floor(Math.random() * hash.length)];
} else {
char = MATRIX_CHARS[Math.floor(Math.random() * MATRIX_CHARS.length)];
}
_ctx.fillStyle = '#aaffaa';
_ctx.fillText(char, i * MATRIX_FONT_SIZE, _drops[i] * MATRIX_FONT_SIZE);
const resetThreshold = 0.975 - activity * 0.015;
if (_drops[i] * MATRIX_FONT_SIZE > _canvas.height && Math.random() > resetThreshold) {
_drops[i] = 0;
}
_drops[i]++;
}
}
function _resetDrops() {
const colCount = Math.floor(window.innerWidth / MATRIX_FONT_SIZE);
_drops = new Array(colCount).fill(1);
}
/**
* @param {THREE.Scene} _scene (unused — 2D canvas effect)
* @param {object} state Shared state bus
* @param {object} _theme (unused — color is hardcoded green for matrix aesthetic)
*/
export function init(_scene, state, _theme) {
_state = state;
_canvas = document.createElement('canvas');
_canvas.id = 'matrix-rain';
_canvas.width = window.innerWidth;
_canvas.height = window.innerHeight;
document.body.appendChild(_canvas);
_ctx = _canvas.getContext('2d');
_resetDrops();
window.addEventListener('resize', () => {
_canvas.width = window.innerWidth;
_canvas.height = window.innerHeight;
_resetDrops();
});
// Run at ~20 fps independent of the Three.js RAF loop
setInterval(_draw, 50);
}
/**
* update() is a no-op — rain runs on its own setInterval.
*/
export function update(_elapsed, _delta) {}