From 846241459429f36be3e1cfa90f144a61d591424d Mon Sep 17 00:00:00 2001 From: alanamind7 Date: Sat, 27 Jun 2026 05:39:34 +0000 Subject: [PATCH 1/2] Add 8D banana audio and chess lab --- docs/banana8d.js | 216 ++++++++++++++++++++++++++++++++++++++ docs/index.html | 50 +++++++++ docs/styles.css | 126 ++++++++++++++++++++++ package.json | 3 + scripts/check-8d-site.mjs | 51 +++++++++ 5 files changed, 446 insertions(+) create mode 100644 docs/banana8d.js create mode 100644 scripts/check-8d-site.mjs diff --git a/docs/banana8d.js b/docs/banana8d.js new file mode 100644 index 00000000..292f07e3 --- /dev/null +++ b/docs/banana8d.js @@ -0,0 +1,216 @@ +(() => { + const audioToggle = document.getElementById("audio8d-toggle"); + const audioReadout = document.getElementById("audio8d-readout"); + const hrtfInput = document.getElementById("hrtf-input"); + const board = document.getElementById("chess8d-board"); + const chessReadout = document.getElementById("chess8d-readout"); + const prevButton = document.getElementById("chess8d-prev"); + const nextButton = document.getElementById("chess8d-next"); + + const TAU = Math.PI * 2; + const BOARD_SIZE = 8; + const PLAYER_COUNT = 8; + + function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); + } + + function parseVector(value) { + const parsed = String(value) + .split(/[,\s]+/) + .map((part) => Number(part.trim())) + .filter((part) => Number.isFinite(part)) + .slice(0, 8); + + while (parsed.length < 8) { + parsed.push(0); + } + + return parsed.map((part) => clamp(part, -1.5, 1.5)); + } + + function rotate8d(vector, time) { + const next = vector.slice(0, 8); + for (let axis = 0; axis < 8; axis += 2) { + const angle = time * (0.17 + axis * 0.025) + axis * 0.19; + const a = next[axis]; + const b = next[axis + 1]; + next[axis] = a * Math.cos(angle) - b * Math.sin(angle); + next[axis + 1] = a * Math.sin(angle) + b * Math.cos(angle); + } + return next; + } + + function project8d(vector) { + const weightsX = [1, 0.72, -0.48, 0.31, -0.2, 0.14, -0.1, 0.07]; + const weightsY = [0.18, -0.42, 0.96, 0.54, -0.36, 0.22, 0.11, -0.08]; + const x = vector.reduce((total, value, index) => total + value * weightsX[index], 0); + const y = vector.reduce((total, value, index) => total + value * weightsY[index], 0); + const depth = vector.reduce((total, value, index) => total + value * (index + 1), 0) / 18; + return { x: clamp(x / 2.4, -1, 1), y: clamp(y / 2.4, -1, 1), depth }; + } + + function bananaMotif(time, hrtfVector) { + const base = [1, 0.75, 0.45, 0.18, -0.18, -0.45, -0.75, -1]; + return base.map((value, index) => { + const wobble = Math.sin(time * (0.8 + index * 0.07) + index) * 0.35; + return value + wobble + hrtfVector[index] * 0.22; + }); + } + + let audioContext = null; + let oscillator = null; + let gain = null; + let panner = null; + let audioFrame = 0; + let playing = false; + + function stopAudio() { + playing = false; + if (oscillator) { + oscillator.stop(); + oscillator.disconnect(); + oscillator = null; + } + if (gain) { + gain.disconnect(); + gain = null; + } + if (panner) { + panner.disconnect(); + panner = null; + } + if (audioToggle) { + audioToggle.textContent = "Start 8D banana audio"; + } + if (audioReadout) { + audioReadout.textContent = "audio idle"; + } + cancelAnimationFrame(audioFrame); + } + + function updateAudio() { + if (!playing || !audioContext || !gain || !panner) { + return; + } + const time = audioContext.currentTime; + const hrtfVector = parseVector(hrtfInput?.value || ""); + const projected = project8d(rotate8d(bananaMotif(time, hrtfVector), time)); + panner.pan.setTargetAtTime(projected.x, time, 0.025); + gain.gain.setTargetAtTime(0.18 + Math.max(0, projected.depth) * 0.04, time, 0.03); + if (oscillator) { + oscillator.frequency.setTargetAtTime(220 + (projected.y + 1) * 110, time, 0.04); + } + if (audioReadout) { + audioReadout.textContent = `pan ${projected.x.toFixed(2)} · pitch ${(220 + (projected.y + 1) * 110).toFixed(0)} Hz`; + } + audioFrame = requestAnimationFrame(updateAudio); + } + + async function startAudio() { + audioContext = audioContext || new AudioContext(); + await audioContext.resume(); + oscillator = audioContext.createOscillator(); + gain = audioContext.createGain(); + panner = audioContext.createStereoPanner(); + oscillator.type = "triangle"; + gain.gain.value = 0.16; + oscillator.connect(gain); + gain.connect(panner); + panner.connect(audioContext.destination); + oscillator.start(); + playing = true; + if (audioToggle) { + audioToggle.textContent = "Stop 8D banana audio"; + } + updateAudio(); + } + + audioToggle?.addEventListener("click", async () => { + if (playing) { + stopAudio(); + return; + } + try { + await startAudio(); + } catch (error) { + if (audioReadout) { + audioReadout.textContent = `audio unavailable: ${error.message}`; + } + } + }); + + const pieces = [ + { player: 1, piece: "K", vector: [0, 0, 0, 0, 0, 0, 0, 0] }, + { player: 2, piece: "Q", vector: [7, 7, 7, 7, 7, 7, 7, 7] }, + { player: 3, piece: "R", vector: [0, 7, 0, 7, 0, 7, 0, 7] }, + { player: 4, piece: "B", vector: [7, 0, 7, 0, 7, 0, 7, 0] }, + { player: 5, piece: "N", vector: [2, 5, 3, 6, 1, 4, 0, 7] }, + { player: 6, piece: "P", vector: [5, 2, 6, 3, 4, 1, 7, 0] }, + { player: 7, piece: "Ω", vector: [1, 6, 2, 5, 3, 4, 7, 0] }, + { player: 8, piece: "♙", vector: [6, 1, 5, 2, 4, 3, 0, 7] }, + ]; + + function normalizeBoardVector(vector) { + return vector.map((value) => (value / (BOARD_SIZE - 1)) * 2 - 1); + } + + function cellIndexForVector(vector) { + const projected = project8d(normalizeBoardVector(vector)); + const x = clamp(Math.round(((projected.x + 1) / 2) * (BOARD_SIZE - 1)), 0, BOARD_SIZE - 1); + const y = clamp(Math.round(((projected.y + 1) / 2) * (BOARD_SIZE - 1)), 0, BOARD_SIZE - 1); + return y * BOARD_SIZE + x; + } + + function vectorNotation(vector) { + return vector.map((value, index) => `${String.fromCharCode(97 + index)}${value + 1}`).join(" "); + } + + let activeMove = 0; + + function renderChess() { + if (!board) { + return; + } + board.innerHTML = ""; + const cells = Array.from({ length: BOARD_SIZE * BOARD_SIZE }, (_, index) => { + const cell = document.createElement("span"); + cell.className = "chess8d-cell"; + cell.textContent = index % 9 === 0 ? "8D" : ""; + board.appendChild(cell); + return cell; + }); + + pieces.forEach((piece, index) => { + const cell = cells[cellIndexForVector(piece.vector)]; + cell.classList.add(index === activeMove ? "active" : "player"); + cell.textContent = `${piece.piece}${piece.player}`; + cell.title = `Player ${piece.player}: ${vectorNotation(piece.vector)}`; + }); + + const active = pieces[activeMove]; + if (chessReadout) { + chessReadout.textContent = `vector ${activeMove + 1} of ${pieces.length}: P${active.player} ${active.piece} · ${vectorNotation(active.vector)}`; + } + } + + prevButton?.addEventListener("click", () => { + activeMove = (activeMove + pieces.length - 1) % pieces.length; + renderChess(); + }); + + nextButton?.addEventListener("click", () => { + activeMove = (activeMove + 1) % pieces.length; + renderChess(); + }); + + renderChess(); + + window.AgentPipe8D = { + parseVector, + rotate8d, + project8d, + cellIndexForVector, + pieces, + }; +})(); diff --git a/docs/index.html b/docs/index.html index a4d790a5..c36e378e 100644 --- a/docs/index.html +++ b/docs/index.html @@ -20,6 +20,7 @@ @@ -101,6 +102,54 @@

A deterministic 4D banana, rendered in the browser.

screen = project4Dto2D(rotated) +
+
+

8D banana expansion pack

+

Audio paths and chess vectors now share one 8D engine.

+

+ The banana renderer now has a companion 8D lab: an interactive Web Audio + spatializer, custom HRTF vector input, and a sparse 8D chess board that + maps eight players onto an 8x8x8x8x8x8x8x8 coordinate space. +

+
+ +
+
+

8D audio

+

+ Start a deterministic banana motif and move it through an eight-axis + orbit. Custom HRTF vectors reshape the path before it is projected to + stereo panning and gain. +

+ + + + audio idle +
+ +
+

8D chess

+

+ Browse a deterministic opening on a sparse 8D board. Each move uses the + same vector projection helpers as the audio orbit, keeping the impossible + board compact enough to inspect. +

+
+
+ + +
+ vector 1 of 8 +
+
+
+

Download AgentPipe and run the pipeline locally.

@@ -128,5 +177,6 @@

Download AgentPipe and run the pipeline locally.

+ diff --git a/docs/styles.css b/docs/styles.css index 0e0d1e95..721ca731 100644 --- a/docs/styles.css +++ b/docs/styles.css @@ -259,6 +259,14 @@ h2 { font-size: clamp(1.05rem, 1.7vw, 1.35rem); } +.eyebrow { + margin: 0 0 0.8rem; + color: var(--green); + font-size: 0.8rem; + font-weight: 900; + text-transform: uppercase; +} + .feature-band { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); @@ -313,6 +321,120 @@ h2 { box-shadow: 0 24px 52px rgba(53, 41, 0, 0.2); } +.lab8d-band { + padding: clamp(4rem, 8vw, 7rem) clamp(1rem, 5vw, 5.5rem); + color: var(--white); + background: + radial-gradient(circle at 20% 20%, rgba(255, 212, 42, 0.22), transparent 26rem), + linear-gradient(135deg, #0f130c 0%, #171407 48%, #423600 100%); +} + +.lab8d-copy, +.lab8d-grid { + width: min(74rem, 100%); + margin: 0 auto; +} + +.lab8d-copy p { + max-width: 56rem; + margin: 1.2rem 0 0; + color: rgba(255, 255, 255, 0.78); + font-size: clamp(1.05rem, 1.7vw, 1.32rem); +} + +.lab8d-copy .eyebrow { + color: var(--yellow); +} + +.lab8d-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-top: 2rem; +} + +.lab-panel { + min-height: 24rem; + padding: clamp(1.2rem, 3vw, 2rem); + border: 1px solid rgba(255, 212, 42, 0.24); + border-radius: 8px; + background: rgba(255, 255, 255, 0.08); +} + +.lab-panel h3 { + margin: 0; + color: var(--yellow); + font-size: clamp(1.4rem, 2.4vw, 2.1rem); +} + +.lab-panel p, +.lab-panel label, +.lab-panel output { + color: rgba(255, 255, 255, 0.78); +} + +.lab-panel label { + display: block; + margin-top: 1.2rem; + font-weight: 800; +} + +.lab-panel input { + width: 100%; + min-height: 2.8rem; + margin: 0.45rem 0 1rem; + padding: 0.65rem 0.75rem; + border: 1px solid rgba(255, 212, 42, 0.3); + border-radius: 8px; + color: var(--white); + background: rgba(0, 0, 0, 0.28); +} + +.lab-panel output { + display: block; + margin-top: 1rem; + font-weight: 800; +} + +.chess8d-board { + display: grid; + grid-template-columns: repeat(8, minmax(0, 1fr)); + aspect-ratio: 1; + margin-top: 1rem; + overflow: hidden; + border: 1px solid rgba(255, 212, 42, 0.32); + border-radius: 8px; + background: #15140f; +} + +.chess8d-cell { + display: grid; + min-width: 0; + place-items: center; + border: 1px solid rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.7); + font-size: clamp(0.68rem, 1.2vw, 0.82rem); + font-weight: 900; +} + +.chess8d-cell.active { + color: var(--charcoal); + background: var(--yellow); + box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.65); +} + +.chess8d-cell.player { + color: var(--white); + background: rgba(46, 125, 50, 0.82); +} + +.chess8d-controls { + display: flex; + flex-wrap: wrap; + gap: 0.7rem; + margin-top: 1rem; +} + .download-band { text-align: center; } @@ -355,6 +477,10 @@ footer span:first-child { grid-template-columns: 1fr; } + .lab8d-grid { + grid-template-columns: 1fr; + } + .hero { min-height: auto; } diff --git a/package.json b/package.json index 92ddf23e..77f6a7c6 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "scripts": { + "check:8d": "node --check docs/banana8d.js && node scripts/check-8d-site.mjs" + }, "dependencies": { "@11ty/eleventy": "^3.1.6" } diff --git a/scripts/check-8d-site.mjs b/scripts/check-8d-site.mjs new file mode 100644 index 00000000..f7e49d3b --- /dev/null +++ b/scripts/check-8d-site.mjs @@ -0,0 +1,51 @@ +import { readFileSync, statSync } from "node:fs"; + +const requiredFiles = [ + "docs/index.html", + "docs/styles.css", + "docs/banana8d.js", +]; + +for (const file of requiredFiles) { + const stats = statSync(file); + if (!stats.isFile() || stats.size === 0) { + throw new Error(`${file} must exist and be non-empty`); + } +} + +const html = readFileSync("docs/index.html", "utf8"); +const css = readFileSync("docs/styles.css", "utf8"); +const js = readFileSync("docs/banana8d.js", "utf8"); + +for (const token of [ + "8D Lab", + "audio8d-toggle", + "chess8d-board", + "banana8d.js", +]) { + if (!html.includes(token)) { + throw new Error(`docs/index.html is missing ${token}`); + } +} + +for (const token of [ + ".lab8d-band", + ".chess8d-board", + ".chess8d-cell.active", +]) { + if (!css.includes(token)) { + throw new Error(`docs/styles.css is missing ${token}`); + } +} + +for (const token of [ + "function rotate8d", + "function project8d", + "window.AgentPipe8D", +]) { + if (!js.includes(token)) { + throw new Error(`docs/banana8d.js is missing ${token}`); + } +} + +console.log("AgentPipe 8D site checks passed."); From 556a36fa68a0dc20325f453927045980d481cdc5 Mon Sep 17 00:00:00 2001 From: alanamind7 Date: Sun, 28 Jun 2026 02:15:05 +0000 Subject: [PATCH 2/2] Address 8D audio playback and chess piece review --- docs/banana8d.js | 150 +++++++++++++++++++++++++++++++------- docs/index.html | 14 ++-- scripts/check-8d-site.mjs | 13 ++++ 3 files changed, 142 insertions(+), 35 deletions(-) diff --git a/docs/banana8d.js b/docs/banana8d.js index 292f07e3..90f640ca 100644 --- a/docs/banana8d.js +++ b/docs/banana8d.js @@ -10,6 +10,9 @@ const TAU = Math.PI * 2; const BOARD_SIZE = 8; const PLAYER_COUNT = 8; + const PIECES_PER_PLAYER = 16; + const TOTAL_CHESS_PIECES = PLAYER_COUNT * PIECES_PER_PLAYER; + const MUSIC_BPM = 96; function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); @@ -59,18 +62,72 @@ } let audioContext = null; - let oscillator = null; + let musicSource = null; + let musicBuffer = null; let gain = null; let panner = null; let audioFrame = 0; let playing = false; + function midiToFrequency(note) { + return 440 * 2 ** ((note - 69) / 12); + } + + function noteEnvelope(timeIntoNote, noteLength) { + const attack = Math.min(0.025, noteLength * 0.18); + const release = Math.min(0.12, noteLength * 0.28); + if (timeIntoNote < attack) { + return timeIntoNote / attack; + } + if (timeIntoNote > noteLength - release) { + return Math.max(0, (noteLength - timeIntoNote) / release); + } + return 0.86; + } + + function buildBananaMusicBuffer(context) { + const sampleRate = context.sampleRate; + const duration = 12; + const frameCount = Math.floor(sampleRate * duration); + const buffer = context.createBuffer(2, frameCount, sampleRate); + const left = buffer.getChannelData(0); + const right = buffer.getChannelData(1); + const beatLength = 60 / MUSIC_BPM; + const melody = [64, 67, 71, 72, 71, 67, 64, 60, 62, 65, 69, 74, 72, 69, 65, 62]; + const bass = [40, 40, 47, 47, 45, 45, 43, 43]; + const chord = [52, 55, 59, 64]; + + for (let frame = 0; frame < frameCount; frame += 1) { + const time = frame / sampleRate; + const beat = Math.floor(time / beatLength); + const beatTime = time % beatLength; + const barPhase = (time % (beatLength * 8)) / (beatLength * 8); + const melodyFrequency = midiToFrequency(melody[beat % melody.length]); + const bassFrequency = midiToFrequency(bass[Math.floor(beat / 2) % bass.length]); + const chordFrequency = midiToFrequency(chord[(beat + Math.floor(time * 2)) % chord.length]); + const envelope = noteEnvelope(beatTime, beatLength); + const swing = 0.74 + Math.sin(barPhase * TAU) * 0.16; + const melodySample = + Math.sin(time * TAU * melodyFrequency) * 0.55 + + Math.sin(time * TAU * melodyFrequency * 2) * 0.14; + const chordSample = Math.sin(time * TAU * chordFrequency) * 0.18; + const bassSample = Math.sin(time * TAU * bassFrequency) * 0.28; + const tick = beatTime < 0.025 ? (1 - beatTime / 0.025) * 0.08 : 0; + const sample = Math.tanh((melodySample * envelope + chordSample + bassSample + tick) * 0.72); + + left[frame] = sample * (0.78 - swing * 0.12); + right[frame] = sample * (0.62 + swing * 0.18); + } + + return buffer; + } + function stopAudio() { playing = false; - if (oscillator) { - oscillator.stop(); - oscillator.disconnect(); - oscillator = null; + if (musicSource) { + musicSource.stop(); + musicSource.disconnect(); + musicSource = null; } if (gain) { gain.disconnect(); @@ -90,7 +147,7 @@ } function updateAudio() { - if (!playing || !audioContext || !gain || !panner) { + if (!playing || !audioContext || !gain || !panner || !musicSource) { return; } const time = audioContext.currentTime; @@ -98,11 +155,9 @@ const projected = project8d(rotate8d(bananaMotif(time, hrtfVector), time)); panner.pan.setTargetAtTime(projected.x, time, 0.025); gain.gain.setTargetAtTime(0.18 + Math.max(0, projected.depth) * 0.04, time, 0.03); - if (oscillator) { - oscillator.frequency.setTargetAtTime(220 + (projected.y + 1) * 110, time, 0.04); - } + musicSource.playbackRate.setTargetAtTime(0.94 + (projected.y + 1) * 0.045, time, 0.04); if (audioReadout) { - audioReadout.textContent = `pan ${projected.x.toFixed(2)} · pitch ${(220 + (projected.y + 1) * 110).toFixed(0)} Hz`; + audioReadout.textContent = `playing banana music · pan ${projected.x.toFixed(2)} · rate ${musicSource.playbackRate.value.toFixed(2)}x`; } audioFrame = requestAnimationFrame(updateAudio); } @@ -110,15 +165,17 @@ async function startAudio() { audioContext = audioContext || new AudioContext(); await audioContext.resume(); - oscillator = audioContext.createOscillator(); + musicBuffer = musicBuffer || buildBananaMusicBuffer(audioContext); + musicSource = audioContext.createBufferSource(); gain = audioContext.createGain(); panner = audioContext.createStereoPanner(); - oscillator.type = "triangle"; + musicSource.buffer = musicBuffer; + musicSource.loop = true; gain.gain.value = 0.16; - oscillator.connect(gain); + musicSource.connect(gain); gain.connect(panner); panner.connect(audioContext.destination); - oscillator.start(); + musicSource.start(); playing = true; if (audioToggle) { audioToggle.textContent = "Stop 8D banana audio"; @@ -140,16 +197,36 @@ } }); - const pieces = [ - { player: 1, piece: "K", vector: [0, 0, 0, 0, 0, 0, 0, 0] }, - { player: 2, piece: "Q", vector: [7, 7, 7, 7, 7, 7, 7, 7] }, - { player: 3, piece: "R", vector: [0, 7, 0, 7, 0, 7, 0, 7] }, - { player: 4, piece: "B", vector: [7, 0, 7, 0, 7, 0, 7, 0] }, - { player: 5, piece: "N", vector: [2, 5, 3, 6, 1, 4, 0, 7] }, - { player: 6, piece: "P", vector: [5, 2, 6, 3, 4, 1, 7, 0] }, - { player: 7, piece: "Ω", vector: [1, 6, 2, 5, 3, 4, 7, 0] }, - { player: 8, piece: "♙", vector: [6, 1, 5, 2, 4, 3, 0, 7] }, - ]; + function create8DChessPieces() { + const backRank = ["R", "N", "B", "Q", "K", "B", "N", "R"]; + const allPieces = []; + + for (let playerIndex = 0; playerIndex < PLAYER_COUNT; playerIndex += 1) { + const player = playerIndex + 1; + const forwardAxis = playerIndex; + const fileAxis = (playerIndex + 1) % 8; + const homeRank = playerIndex % 2 === 0 ? 0 : BOARD_SIZE - 1; + const pawnRank = playerIndex % 2 === 0 ? 1 : BOARD_SIZE - 2; + + backRank.forEach((piece, file) => { + const vector = Array.from({ length: 8 }, (_, axis) => playerIndex); + vector[forwardAxis] = homeRank; + vector[fileAxis] = file; + allPieces.push({ player, piece, vector }); + }); + + for (let file = 0; file < BOARD_SIZE; file += 1) { + const vector = Array.from({ length: 8 }, (_, axis) => playerIndex); + vector[forwardAxis] = pawnRank; + vector[fileAxis] = file; + allPieces.push({ player, piece: "P", vector }); + } + } + + return allPieces; + } + + const pieces = create8DChessPieces(); function normalizeBoardVector(vector) { return vector.map((value) => (value / (BOARD_SIZE - 1)) * 2 - 1); @@ -181,11 +258,25 @@ return cell; }); + const stacks = new Map(); pieces.forEach((piece, index) => { - const cell = cells[cellIndexForVector(piece.vector)]; - cell.classList.add(index === activeMove ? "active" : "player"); - cell.textContent = `${piece.piece}${piece.player}`; - cell.title = `Player ${piece.player}: ${vectorNotation(piece.vector)}`; + const cellIndex = cellIndexForVector(piece.vector); + const stack = stacks.get(cellIndex) || []; + stack.push({ ...piece, index }); + stacks.set(cellIndex, stack); + }); + + stacks.forEach((stack, cellIndex) => { + const cell = cells[cellIndex]; + const activePiece = stack.find((piece) => piece.index === activeMove); + const displayPiece = activePiece || stack[0]; + cell.classList.add(activePiece ? "active" : "player"); + cell.textContent = stack.length > 1 + ? `${displayPiece.piece}${displayPiece.player}+${stack.length - 1}` + : `${displayPiece.piece}${displayPiece.player}`; + cell.title = stack + .map((piece) => `Player ${piece.player} ${piece.piece}: ${vectorNotation(piece.vector)}`) + .join("\n"); }); const active = pieces[activeMove]; @@ -210,7 +301,10 @@ parseVector, rotate8d, project8d, + buildBananaMusicBuffer, + create8DChessPieces, cellIndexForVector, + TOTAL_CHESS_PIECES, pieces, }; })(); diff --git a/docs/index.html b/docs/index.html index c36e378e..d8335406 100644 --- a/docs/index.html +++ b/docs/index.html @@ -117,9 +117,9 @@

Audio paths and chess vectors now share one 8D engine.

8D audio

- Start a deterministic banana motif and move it through an eight-axis - orbit. Custom HRTF vectors reshape the path before it is projected to - stereo panning and gain. + Play back a generated banana music loop and move it through an + eight-axis orbit. Custom HRTF vectors reshape the path before it is + projected to stereo panning, gain, and playback rate.

8D audio

8D chess

- Browse a deterministic opening on a sparse 8D board. Each move uses the - same vector projection helpers as the audio orbit, keeping the impossible - board compact enough to inspect. + Browse a deterministic 128-piece opening on a sparse 8D board. Each + move uses the same vector projection helpers as the audio orbit, keeping + the impossible board compact enough to inspect.

- vector 1 of 8 + vector 1 of 128
diff --git a/scripts/check-8d-site.mjs b/scripts/check-8d-site.mjs index f7e49d3b..0828f5db 100644 --- a/scripts/check-8d-site.mjs +++ b/scripts/check-8d-site.mjs @@ -41,6 +41,10 @@ for (const token of [ for (const token of [ "function rotate8d", "function project8d", + "function buildBananaMusicBuffer", + "function create8DChessPieces", + "TOTAL_CHESS_PIECES = PLAYER_COUNT * PIECES_PER_PLAYER", + "musicSource = audioContext.createBufferSource()", "window.AgentPipe8D", ]) { if (!js.includes(token)) { @@ -48,4 +52,13 @@ for (const token of [ } } +if (js.includes("createOscillator")) { + throw new Error("docs/banana8d.js should play back music instead of using a plain oscillator"); +} + +const playerLoop = js.match(/playerIndex < PLAYER_COUNT/g) || []; +if (playerLoop.length < 1 || !js.includes("pieces.length")) { + throw new Error("docs/banana8d.js must build and expose all 128 projected 8D chess pieces"); +} + console.log("AgentPipe 8D site checks passed.");