From 885d78ae2d703addcf0819ea9001ab44e7ba9356 Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:25:44 -0400 Subject: [PATCH 1/7] Add shared chrome for deeper GaugeGap labs --- .../src/features/gaugegap/DeepLabChrome.jsx | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 brainsnn-r3f-app/src/features/gaugegap/DeepLabChrome.jsx diff --git a/brainsnn-r3f-app/src/features/gaugegap/DeepLabChrome.jsx b/brainsnn-r3f-app/src/features/gaugegap/DeepLabChrome.jsx new file mode 100644 index 0000000..ed3b238 --- /dev/null +++ b/brainsnn-r3f-app/src/features/gaugegap/DeepLabChrome.jsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { Copy, Pause, Play, RefreshCw, Share2, Sparkles } from 'lucide-react'; + +export function clamp(value, min, max) { + return Math.min(max, Math.max(min, value)); +} + +export function round(value, places = 2) { + const factor = 10 ** places; + return Math.round(value * factor) / factor; +} + +export function seededRandom(seedValue) { + let seed = seedValue >>> 0; + return () => { + seed += 0x6d2b79f5; + let value = seed; + value = Math.imul(value ^ (value >>> 15), value | 1); + value ^= value + Math.imul(value ^ (value >>> 7), value | 61); + return ((value ^ (value >>> 14)) >>> 0) / 4294967296; + }; +} + +export async function copyChallenge(url, setNotice) { + try { + await navigator.clipboard.writeText(url); + setNotice('Challenge link copied. The exact lab settings travel with it.'); + } catch { + setNotice('Clipboard access was blocked. Use the native Share button.'); + } +} + +export async function shareChallenge({ title, text, url, setNotice }) { + try { + if (navigator.share) await navigator.share({ title, text, url }); + else await copyChallenge(url, setNotice); + } catch (error) { + if (error?.name !== 'AbortError') setNotice('Sharing was blocked. Copy the challenge link instead.'); + } +} + +export function DeepLabHeading({ kicker, title, description, scoreLabel, score, scoreNote }) { + return ( +
+
+

{kicker}

+

{title}

+

{description}

+
+
+ {scoreLabel} + {score} + {scoreNote} +
+
+ ); +} + +export function DeepLabActions({ paused, onPause, onReset, onShare, onCopy, notice }) { + return ( +
+ + + + + {notice} +
+ ); +} From 2c4c17bcab7b71049c0a65e95da3fd2e9c305881 Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:26:43 -0400 Subject: [PATCH 2/7] Add gravity forge and chaos twins labs --- .../features/gaugegap/PhysicsPlaygrounds.jsx | 507 ++++++++++++++++++ 1 file changed, 507 insertions(+) create mode 100644 brainsnn-r3f-app/src/features/gaugegap/PhysicsPlaygrounds.jsx diff --git a/brainsnn-r3f-app/src/features/gaugegap/PhysicsPlaygrounds.jsx b/brainsnn-r3f-app/src/features/gaugegap/PhysicsPlaygrounds.jsx new file mode 100644 index 0000000..6dfa461 --- /dev/null +++ b/brainsnn-r3f-app/src/features/gaugegap/PhysicsPlaygrounds.jsx @@ -0,0 +1,507 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { CircleDot, MousePointer2, Orbit, Target } from 'lucide-react'; +import { + clamp, + copyChallenge, + DeepLabActions, + DeepLabHeading, + round, + shareChallenge, +} from './DeepLabChrome.jsx'; + +const ORBIT_PRESETS = { + solar: { + label: 'Tiny solar system', + gravity: 0.75, + bodies: [ + { x: 0.5, y: 0.5, vx: 0, vy: 0, mass: 640, radius: 13, hue: 48 }, + { x: 0.5, y: 0.29, vx: 1.5, vy: 0, mass: 5, radius: 4.5, hue: 188 }, + { x: 0.5, y: 0.76, vx: -1.28, vy: 0, mass: 9, radius: 5.5, hue: 310 }, + { x: 0.23, y: 0.5, vx: 0, vy: -1.34, mass: 3.5, radius: 4, hue: 130 }, + ], + }, + binary: { + label: 'Binary dance', + gravity: 0.63, + bodies: [ + { x: 0.39, y: 0.5, vx: 0, vy: -0.56, mass: 330, radius: 11, hue: 42 }, + { x: 0.61, y: 0.5, vx: 0, vy: 0.56, mass: 330, radius: 11, hue: 205 }, + { x: 0.5, y: 0.18, vx: 1.02, vy: 0, mass: 4, radius: 4, hue: 300 }, + ], + }, + slingshot: { + label: 'Slingshot test', + gravity: 0.92, + bodies: [ + { x: 0.53, y: 0.52, vx: 0, vy: 0, mass: 780, radius: 14, hue: 36 }, + { x: 0.13, y: 0.67, vx: 1.42, vy: -0.32, mass: 7, radius: 5, hue: 195 }, + { x: 0.76, y: 0.32, vx: -0.42, vy: 0.65, mass: 22, radius: 6.5, hue: 325 }, + ], + }, +}; + +function parseGravityState() { + if (typeof window === 'undefined') return null; + const query = new URLSearchParams(window.location.search); + if (query.get('lab') !== 'gravity') return null; + const [preset, gravityValue] = (query.get('state') || '').split(','); + const gravity = Number(gravityValue); + if (!ORBIT_PRESETS[preset] || !Number.isFinite(gravity)) return null; + return { preset, gravity: clamp(gravity, 0.2, 1.4) }; +} + +export function GravityForgeLab() { + const shared = useMemo(() => parseGravityState(), []); + const initialPreset = shared?.preset || 'solar'; + const [preset, setPreset] = useState(initialPreset); + const [gravity, setGravity] = useState(shared?.gravity || ORBIT_PRESETS[initialPreset].gravity); + const [paused, setPaused] = useState(false); + const [bodyCount, setBodyCount] = useState(ORBIT_PRESETS[initialPreset].bodies.length); + const [survival, setSurvival] = useState(0); + const [notice, setNotice] = useState(shared ? 'Shared orbit loaded. Add a planet and make it yours.' : 'Drag from empty space to launch a new planet.'); + const canvasRef = useRef(null); + const bodiesRef = useRef([]); + const trailsRef = useRef([]); + const gravityRef = useRef(gravity); + const pausedRef = useRef(paused); + const launchRef = useRef(null); + const resetRef = useRef(0); + + useEffect(() => { gravityRef.current = gravity; }, [gravity]); + useEffect(() => { pausedRef.current = paused; }, [paused]); + + function loadPreset(id) { + setPreset(id); + setGravity(ORBIT_PRESETS[id].gravity); + setPaused(false); + setNotice(`${ORBIT_PRESETS[id].label} loaded. Drag to launch another body.`); + } + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return undefined; + const context = canvas.getContext('2d'); + if (!context) return undefined; + let width = 0; + let height = 0; + let animationId = 0; + let frameCount = 0; + let lastReset = -1; + let startTime = performance.now(); + + function resize() { + const rect = canvas.getBoundingClientRect(); + const ratio = Math.min(window.devicePixelRatio || 1, 2); + width = Math.max(320, rect.width); + height = Math.max(360, rect.height); + canvas.width = Math.floor(width * ratio); + canvas.height = Math.floor(height * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + } + + function resetBodies() { + const base = ORBIT_PRESETS[preset]; + const scale = Math.min(width, height) * 0.19; + bodiesRef.current = base.bodies.map((body) => ({ + ...body, + x: body.x * width, + y: body.y * height, + vx: body.vx * scale, + vy: body.vy * scale, + })); + trailsRef.current = bodiesRef.current.map(() => []); + startTime = performance.now(); + setBodyCount(bodiesRef.current.length); + setSurvival(0); + context.fillStyle = '#02040c'; + context.fillRect(0, 0, width, height); + } + + const observer = new ResizeObserver(() => { + resize(); + resetBodies(); + }); + observer.observe(canvas); + resize(); + resetBodies(); + + function pointerPosition(event) { + const rect = canvas.getBoundingClientRect(); + return { x: event.clientX - rect.left, y: event.clientY - rect.top }; + } + + function onPointerDown(event) { + launchRef.current = pointerPosition(event); + canvas.setPointerCapture?.(event.pointerId); + } + + function onPointerUp(event) { + if (!launchRef.current) return; + const start = launchRef.current; + const end = pointerPosition(event); + launchRef.current = null; + const dx = end.x - start.x; + const dy = end.y - start.y; + const speed = clamp(Math.hypot(dx, dy), 8, 180); + const hue = 155 + (bodiesRef.current.length * 47) % 195; + bodiesRef.current.push({ + x: start.x, + y: start.y, + vx: dx * 0.58, + vy: dy * 0.58, + mass: 4 + speed * 0.035, + radius: 4 + speed * 0.012, + hue, + }); + trailsRef.current.push([]); + setBodyCount(bodiesRef.current.length); + setNotice('Planet launched. A shorter drag makes a slower, tighter orbit.'); + } + + canvas.addEventListener('pointerdown', onPointerDown); + canvas.addEventListener('pointerup', onPointerUp); + + function render(now) { + if (lastReset !== resetRef.current) { + lastReset = resetRef.current; + resetBodies(); + } + + context.fillStyle = 'rgba(2, 4, 12, 0.22)'; + context.fillRect(0, 0, width, height); + const bodies = bodiesRef.current; + + if (!pausedRef.current) { + const dt = 0.0035; + const forceScale = gravityRef.current * 112; + for (let i = 0; i < bodies.length; i += 1) { + for (let j = i + 1; j < bodies.length; j += 1) { + const a = bodies[i]; + const b = bodies[j]; + const dx = b.x - a.x; + const dy = b.y - a.y; + const distanceSquared = Math.max(90, dx * dx + dy * dy); + const distance = Math.sqrt(distanceSquared); + const force = forceScale / distanceSquared; + const nx = dx / distance; + const ny = dy / distance; + a.vx += nx * force * b.mass * dt; + a.vy += ny * force * b.mass * dt; + b.vx -= nx * force * a.mass * dt; + b.vy -= ny * force * a.mass * dt; + } + } + bodies.forEach((body, index) => { + body.x += body.vx * dt; + body.y += body.vy * dt; + const trail = trailsRef.current[index] || []; + if (frameCount % 2 === 0) trail.push({ x: body.x, y: body.y }); + if (trail.length > 120) trail.shift(); + trailsRef.current[index] = trail; + }); + } + + bodies.forEach((body, index) => { + const trail = trailsRef.current[index] || []; + if (trail.length > 1) { + context.beginPath(); + context.moveTo(trail[0].x, trail[0].y); + for (let point = 1; point < trail.length; point += 1) context.lineTo(trail[point].x, trail[point].y); + context.strokeStyle = `hsla(${body.hue}, 88%, 68%, 0.35)`; + context.lineWidth = 1; + context.stroke(); + } + const gradient = context.createRadialGradient(body.x, body.y, 0, body.x, body.y, body.radius * 3.2); + gradient.addColorStop(0, `hsla(${body.hue}, 95%, 78%, 1)`); + gradient.addColorStop(0.28, `hsla(${body.hue}, 90%, 58%, 0.9)`); + gradient.addColorStop(1, `hsla(${body.hue}, 88%, 55%, 0)`); + context.fillStyle = gradient; + context.beginPath(); + context.arc(body.x, body.y, body.radius * 3.2, 0, Math.PI * 2); + context.fill(); + }); + + const alive = bodies.filter((body) => body.x > -80 && body.x < width + 80 && body.y > -80 && body.y < height + 80).length; + if (frameCount % 30 === 0) { + const seconds = Math.floor((now - startTime) / 1000); + setSurvival(Math.round(clamp((alive / Math.max(1, bodies.length)) * 70 + Math.min(seconds, 30), 0, 100))); + } + frameCount += 1; + animationId = window.requestAnimationFrame(render); + } + + animationId = window.requestAnimationFrame(render); + return () => { + observer.disconnect(); + canvas.removeEventListener('pointerdown', onPointerDown); + canvas.removeEventListener('pointerup', onPointerUp); + window.cancelAnimationFrame(animationId); + }; + }, [preset]); + + function challengeUrl() { + const url = new URL(window.location.href); + url.searchParams.set('lab', 'gravity'); + url.searchParams.set('state', `${preset},${round(gravity, 2)}`); + url.hash = 'playground'; + return url.toString(); + } + + return ( +
+ +
+
+ +
Drag to launch · longer drag = faster planet
+
+ +
+ setPaused((value) => !value)} + onReset={() => { resetRef.current += 1; setNotice('The system reset to its preset state.'); }} + onShare={() => shareChallenge({ title: `My GaugeGap orbit scored ${survival}`, text: `Can you build a more stable ${bodyCount}-body system?`, url: challengeUrl(), setNotice })} + onCopy={() => copyChallenge(challengeUrl(), setNotice)} + notice={notice} + /> +
+ ); +} + +function pendulumAcceleration(theta1, theta2, omega1, omega2, gravity) { + const mass1 = 1; + const mass2 = 1; + const length1 = 1; + const length2 = 1; + const delta = theta1 - theta2; + const denominator1 = length1 * (2 * mass1 + mass2 - mass2 * Math.cos(2 * delta)); + const denominator2 = length2 * (2 * mass1 + mass2 - mass2 * Math.cos(2 * delta)); + const alpha1 = (-gravity * (2 * mass1 + mass2) * Math.sin(theta1) - mass2 * gravity * Math.sin(theta1 - 2 * theta2) - 2 * Math.sin(delta) * mass2 * (omega2 * omega2 * length2 + omega1 * omega1 * length1 * Math.cos(delta))) / denominator1; + const alpha2 = (2 * Math.sin(delta) * (omega1 * omega1 * length1 * (mass1 + mass2) + gravity * (mass1 + mass2) * Math.cos(theta1) + omega2 * omega2 * length2 * mass2 * Math.cos(delta))) / denominator2; + return { alpha1, alpha2 }; +} + +function parsePendulumState() { + if (typeof window === 'undefined') return null; + const query = new URLSearchParams(window.location.search); + if (query.get('lab') !== 'pendulum') return null; + const [angleValue, differenceValue, gravityValue] = (query.get('state') || '').split(',').map(Number); + if (![angleValue, differenceValue, gravityValue].every(Number.isFinite)) return null; + return { + angle: clamp(angleValue, 45, 175), + difference: clamp(differenceValue, 0.01, 1.2), + gravity: clamp(gravityValue, 0.2, 1.8), + }; +} + +export function ChaosTwinsLab() { + const shared = useMemo(() => parsePendulumState(), []); + const [angle, setAngle] = useState(shared?.angle || 122); + const [difference, setDifference] = useState(shared?.difference || 0.08); + const [gravity, setGravity] = useState(shared?.gravity || 1); + const [paused, setPaused] = useState(false); + const [divergence, setDivergence] = useState(0); + const [timeToSplit, setTimeToSplit] = useState(null); + const [notice, setNotice] = useState(shared ? 'Shared chaos race loaded.' : 'Two pendulums begin almost identical. Predict when their paths split.'); + const canvasRef = useRef(null); + const paramsRef = useRef({ angle, difference, gravity }); + const pausedRef = useRef(paused); + const resetRef = useRef(0); + + useEffect(() => { paramsRef.current = { angle, difference, gravity }; }, [angle, difference, gravity]); + useEffect(() => { pausedRef.current = paused; }, [paused]); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return undefined; + const context = canvas.getContext('2d'); + if (!context) return undefined; + let width = 0; + let height = 0; + let animationId = 0; + let frameCount = 0; + let lastReset = -1; + let startedAt = performance.now(); + let splitRecorded = false; + let twins = []; + let trails = [[], []]; + + function reset() { + const base = paramsRef.current.angle * Math.PI / 180; + const offset = paramsRef.current.difference * Math.PI / 180; + twins = [ + { theta1: base, theta2: base * 0.72, omega1: 0, omega2: 0 }, + { theta1: base + offset, theta2: base * 0.72, omega1: 0, omega2: 0 }, + ]; + trails = [[], []]; + startedAt = performance.now(); + splitRecorded = false; + setDivergence(0); + setTimeToSplit(null); + } + + function resize() { + const rect = canvas.getBoundingClientRect(); + const ratio = Math.min(window.devicePixelRatio || 1, 2); + width = Math.max(320, rect.width); + height = Math.max(360, rect.height); + canvas.width = Math.floor(width * ratio); + canvas.height = Math.floor(height * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + reset(); + } + + const observer = new ResizeObserver(resize); + observer.observe(canvas); + resize(); + + function integrate(state, dt) { + const acceleration = pendulumAcceleration(state.theta1, state.theta2, state.omega1, state.omega2, 9.81 * paramsRef.current.gravity); + state.omega1 += acceleration.alpha1 * dt; + state.omega2 += acceleration.alpha2 * dt; + state.theta1 += state.omega1 * dt; + state.theta2 += state.omega2 * dt; + } + + function points(state) { + const length = Math.min(width, height) * 0.19; + const originX = width * 0.5; + const originY = height * 0.25; + const x1 = originX + Math.sin(state.theta1) * length; + const y1 = originY + Math.cos(state.theta1) * length; + const x2 = x1 + Math.sin(state.theta2) * length; + const y2 = y1 + Math.cos(state.theta2) * length; + return { originX, originY, x1, y1, x2, y2 }; + } + + function render(now) { + if (lastReset !== resetRef.current) { + lastReset = resetRef.current; + reset(); + } + context.fillStyle = 'rgba(2, 4, 12, 0.18)'; + context.fillRect(0, 0, width, height); + if (!pausedRef.current) { + for (let pass = 0; pass < 5; pass += 1) twins.forEach((state) => integrate(state, 0.0034)); + } + const colors = ['#7df9ff', '#ff5edb']; + const endpoints = twins.map(points); + endpoints.forEach((point, index) => { + const trail = trails[index]; + if (!pausedRef.current && frameCount % 2 === 0) trail.push({ x: point.x2, y: point.y2 }); + if (trail.length > 180) trail.shift(); + if (trail.length > 1) { + context.strokeStyle = `${colors[index]}55`; + context.lineWidth = 1.2; + context.beginPath(); + context.moveTo(trail[0].x, trail[0].y); + trail.slice(1).forEach((item) => context.lineTo(item.x, item.y)); + context.stroke(); + } + context.strokeStyle = colors[index]; + context.lineWidth = 2; + context.beginPath(); + context.moveTo(point.originX, point.originY); + context.lineTo(point.x1, point.y1); + context.lineTo(point.x2, point.y2); + context.stroke(); + context.fillStyle = colors[index]; + context.beginPath(); + context.arc(point.x1, point.y1, 6, 0, Math.PI * 2); + context.arc(point.x2, point.y2, 8, 0, Math.PI * 2); + context.fill(); + }); + const distance = Math.hypot(endpoints[0].x2 - endpoints[1].x2, endpoints[0].y2 - endpoints[1].y2); + const normalized = Math.round(clamp(distance / Math.min(width, height) * 180, 0, 100)); + if (frameCount % 10 === 0) { + setDivergence(normalized); + if (!splitRecorded && normalized > 35) { + splitRecorded = true; + setTimeToSplit(round((now - startedAt) / 1000, 1)); + } + } + context.fillStyle = '#e7fbff'; + context.beginPath(); + context.arc(width * 0.5, height * 0.25, 5, 0, Math.PI * 2); + context.fill(); + frameCount += 1; + animationId = window.requestAnimationFrame(render); + } + + animationId = window.requestAnimationFrame(render); + return () => { + observer.disconnect(); + window.cancelAnimationFrame(animationId); + }; + }, []); + + function applyPreset(nextAngle, nextDifference, nextGravity, message) { + setAngle(nextAngle); + setDifference(nextDifference); + setGravity(nextGravity); + resetRef.current += 1; + setNotice(message); + } + + function challengeUrl() { + const url = new URL(window.location.href); + url.searchParams.set('lab', 'pendulum'); + url.searchParams.set('state', [angle, difference, round(gravity, 2)].join(',')); + url.hash = 'playground'; + return url.toString(); + } + + return ( +
+ +
+
+ +
Cyan and pink begin only {difference}° apart
+
+ +
+ setPaused((value) => !value)} + onReset={() => { resetRef.current += 1; setNotice('Both futures restarted from the same tiny separation.'); }} + onShare={() => shareChallenge({ title: `My GaugeGap chaos twins split in ${timeToSplit || 'over 10'} seconds`, text: 'Can you keep two almost-identical pendulums together longer?', url: challengeUrl(), setNotice })} + onCopy={() => copyChallenge(challengeUrl(), setNotice)} + notice={notice} + /> +
+ ); +} From 5cfefac57cd6b578a4c537ca748622f3eca95b9d Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:27:38 -0400 Subject: [PATCH 3/7] Add flock intelligence and outbreak containment labs --- .../features/gaugegap/AgentPlaygrounds.jsx | 511 ++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100644 brainsnn-r3f-app/src/features/gaugegap/AgentPlaygrounds.jsx diff --git a/brainsnn-r3f-app/src/features/gaugegap/AgentPlaygrounds.jsx b/brainsnn-r3f-app/src/features/gaugegap/AgentPlaygrounds.jsx new file mode 100644 index 0000000..3065565 --- /dev/null +++ b/brainsnn-r3f-app/src/features/gaugegap/AgentPlaygrounds.jsx @@ -0,0 +1,511 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Crosshair, Shield, Target, Wind } from 'lucide-react'; +import { + clamp, + copyChallenge, + DeepLabActions, + DeepLabHeading, + round, + seededRandom, + shareChallenge, +} from './DeepLabChrome.jsx'; + +function makeFlock(count, width, height, seed = 42) { + const random = seededRandom(seed); + return Array.from({ length: count }, (_, index) => { + const angle = random() * Math.PI * 2; + const speed = 0.7 + random(); + return { + x: random() * width, + y: random() * height, + vx: Math.cos(angle) * speed, + vy: Math.sin(angle) * speed, + hue: 176 + (index % 70), + }; + }); +} + +function parseFlockState() { + if (typeof window === 'undefined') return null; + const query = new URLSearchParams(window.location.search); + if (query.get('lab') !== 'flock') return null; + const [countValue, alignmentValue, cohesionValue, separationValue, modeValue] = (query.get('state') || '').split(','); + const values = [countValue, alignmentValue, cohesionValue, separationValue].map(Number); + if (!values.every(Number.isFinite)) return null; + return { + count: clamp(values[0], 40, 220), + alignment: clamp(values[1], 0, 1.4), + cohesion: clamp(values[2], 0, 1.4), + separation: clamp(values[3], 0, 1.5), + mode: modeValue === 'beacon' ? 'beacon' : 'predator', + }; +} + +export function FlockMindLab() { + const shared = useMemo(() => parseFlockState(), []); + const [count, setCount] = useState(shared?.count || 120); + const [alignment, setAlignment] = useState(shared?.alignment || 0.72); + const [cohesion, setCohesion] = useState(shared?.cohesion || 0.54); + const [separation, setSeparation] = useState(shared?.separation || 0.86); + const [mode, setMode] = useState(shared?.mode || 'predator'); + const [paused, setPaused] = useState(false); + const [coherence, setCoherence] = useState(0); + const [notice, setNotice] = useState(shared ? 'Shared flock loaded. Move over the field to test it.' : 'Move across the field. The flock treats you as a predator.'); + const canvasRef = useRef(null); + const birdsRef = useRef([]); + const controlsRef = useRef({ count, alignment, cohesion, separation, mode }); + const pointerRef = useRef({ x: -9999, y: -9999, active: false }); + const pausedRef = useRef(paused); + const resetRef = useRef(0); + + useEffect(() => { controlsRef.current = { count, alignment, cohesion, separation, mode }; }, [count, alignment, cohesion, separation, mode]); + useEffect(() => { pausedRef.current = paused; }, [paused]); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return undefined; + const context = canvas.getContext('2d'); + if (!context) return undefined; + let width = 0; + let height = 0; + let animationId = 0; + let frameCount = 0; + let lastReset = -1; + + function resize() { + const rect = canvas.getBoundingClientRect(); + const ratio = Math.min(window.devicePixelRatio || 1, 2); + width = Math.max(320, rect.width); + height = Math.max(360, rect.height); + canvas.width = Math.floor(width * ratio); + canvas.height = Math.floor(height * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + birdsRef.current = makeFlock(controlsRef.current.count, width, height, 91); + } + + function pointer(event) { + const rect = canvas.getBoundingClientRect(); + pointerRef.current = { x: event.clientX - rect.left, y: event.clientY - rect.top, active: true }; + } + + function pointerLeave() { + pointerRef.current.active = false; + } + + const observer = new ResizeObserver(resize); + observer.observe(canvas); + canvas.addEventListener('pointermove', pointer); + canvas.addEventListener('pointerenter', pointer); + canvas.addEventListener('pointerleave', pointerLeave); + resize(); + + function render() { + if (lastReset !== resetRef.current || birdsRef.current.length !== controlsRef.current.count) { + lastReset = resetRef.current; + birdsRef.current = makeFlock(controlsRef.current.count, width, height, Date.now() % 100000); + } + + context.fillStyle = 'rgba(2, 5, 12, 0.3)'; + context.fillRect(0, 0, width, height); + const birds = birdsRef.current; + const controls = controlsRef.current; + + if (!pausedRef.current) { + for (let index = 0; index < birds.length; index += 1) { + const bird = birds[index]; + let neighbors = 0; + let avgVx = 0; + let avgVy = 0; + let centerX = 0; + let centerY = 0; + let avoidX = 0; + let avoidY = 0; + + for (let otherIndex = 0; otherIndex < birds.length; otherIndex += 1) { + if (otherIndex === index) continue; + const other = birds[otherIndex]; + let dx = other.x - bird.x; + let dy = other.y - bird.y; + if (Math.abs(dx) > width / 2) dx -= Math.sign(dx) * width; + if (Math.abs(dy) > height / 2) dy -= Math.sign(dy) * height; + const distanceSquared = dx * dx + dy * dy; + if (distanceSquared < 4200) { + neighbors += 1; + avgVx += other.vx; + avgVy += other.vy; + centerX += dx; + centerY += dy; + if (distanceSquared < 420) { + const scale = 1 / Math.max(1, distanceSquared); + avoidX -= dx * scale; + avoidY -= dy * scale; + } + } + } + + if (neighbors) { + avgVx /= neighbors; + avgVy /= neighbors; + centerX /= neighbors; + centerY /= neighbors; + bird.vx += (avgVx - bird.vx) * 0.012 * controls.alignment; + bird.vy += (avgVy - bird.vy) * 0.012 * controls.alignment; + bird.vx += centerX * 0.00018 * controls.cohesion; + bird.vy += centerY * 0.00018 * controls.cohesion; + bird.vx += avoidX * 2.8 * controls.separation; + bird.vy += avoidY * 2.8 * controls.separation; + } + + if (pointerRef.current.active) { + const dx = pointerRef.current.x - bird.x; + const dy = pointerRef.current.y - bird.y; + const distanceSquared = Math.max(120, dx * dx + dy * dy); + const direction = controls.mode === 'beacon' ? 1 : -1; + bird.vx += direction * dx * 18 / distanceSquared; + bird.vy += direction * dy * 18 / distanceSquared; + } + + const speed = Math.hypot(bird.vx, bird.vy); + const maxSpeed = 2.45; + if (speed > maxSpeed) { + bird.vx = bird.vx / speed * maxSpeed; + bird.vy = bird.vy / speed * maxSpeed; + } + bird.x = (bird.x + bird.vx + width) % width; + bird.y = (bird.y + bird.vy + height) % height; + } + } + + let directionX = 0; + let directionY = 0; + birds.forEach((bird) => { + const angle = Math.atan2(bird.vy, bird.vx); + directionX += Math.cos(angle); + directionY += Math.sin(angle); + context.save(); + context.translate(bird.x, bird.y); + context.rotate(angle); + context.fillStyle = `hsla(${bird.hue}, 90%, 68%, 0.82)`; + context.beginPath(); + context.moveTo(7, 0); + context.lineTo(-4, 3.2); + context.lineTo(-2.2, 0); + context.lineTo(-4, -3.2); + context.closePath(); + context.fill(); + context.restore(); + }); + + if (frameCount % 20 === 0) setCoherence(Math.round(Math.hypot(directionX, directionY) / Math.max(1, birds.length) * 100)); + frameCount += 1; + animationId = window.requestAnimationFrame(render); + } + + animationId = window.requestAnimationFrame(render); + return () => { + observer.disconnect(); + window.cancelAnimationFrame(animationId); + canvas.removeEventListener('pointermove', pointer); + canvas.removeEventListener('pointerenter', pointer); + canvas.removeEventListener('pointerleave', pointerLeave); + }; + }, []); + + function challengeUrl() { + const url = new URL(window.location.href); + url.searchParams.set('lab', 'flock'); + url.searchParams.set('state', [count, round(alignment, 2), round(cohesion, 2), round(separation, 2), mode].join(',')); + url.hash = 'playground'; + return url.toString(); + } + + return ( +
+ 82 ? 'Shared direction achieved' : 'Local rules still competing'} + /> +
+
+ +
Move over the canvas to influence the flock
+
+ +
+ setPaused((value) => !value)} + onReset={() => { resetRef.current += 1; setNotice('New flock generated from the same rules.'); }} + onShare={() => shareChallenge({ title: `My GaugeGap flock reached ${coherence}% coherence`, text: 'Can you make a leaderless flock coordinate better?', url: challengeUrl(), setNotice })} + onCopy={() => copyChallenge(challengeUrl(), setNotice)} + notice={notice} + /> +
+ ); +} + +function buildNetwork(count, width, height, seed) { + const random = seededRandom(seed); + const nodes = Array.from({ length: count }, (_, index) => ({ + x: 38 + random() * Math.max(20, width - 76), + y: 38 + random() * Math.max(20, height - 76), + state: 'susceptible', + timer: 0, + id: index, + })); + const edges = []; + const keys = new Set(); + for (let index = 0; index < count; index += 1) { + const distances = nodes + .map((node, other) => ({ other, distance: Math.hypot(node.x - nodes[index].x, node.y - nodes[index].y) })) + .filter((item) => item.other !== index) + .sort((a, b) => a.distance - b.distance) + .slice(0, 3); + distances.forEach(({ other }) => { + const key = index < other ? `${index}-${other}` : `${other}-${index}`; + if (!keys.has(key)) { + keys.add(key); + edges.push({ key, a: index, b: other }); + } + }); + } + return { nodes, edges }; +} + +function parseOutbreakState() { + if (typeof window === 'undefined') return null; + const query = new URLSearchParams(window.location.search); + if (query.get('lab') !== 'outbreak') return null; + const [transmissionValue, recoveryValue, contactsValue] = (query.get('state') || '').split(',').map(Number); + if (![transmissionValue, recoveryValue, contactsValue].every(Number.isFinite)) return null; + return { + transmission: clamp(transmissionValue, 0.08, 0.9), + recovery: clamp(recoveryValue, 2, 12), + contacts: clamp(contactsValue, 1, 4), + }; +} + +export function OutbreakZeroLab() { + const shared = useMemo(() => parseOutbreakState(), []); + const [transmission, setTransmission] = useState(shared?.transmission || 0.38); + const [recovery, setRecovery] = useState(shared?.recovery || 6); + const [contacts, setContacts] = useState(shared?.contacts || 1); + const [tool, setTool] = useState('infect'); + const [paused, setPaused] = useState(false); + const [stats, setStats] = useState({ infected: 0, recovered: 0, immunized: 0, peak: 0 }); + const [budget, setBudget] = useState(12); + const [notice, setNotice] = useState(shared ? 'Shared outbreak settings loaded. Choose patient zero.' : 'Choose patient zero, then use your limited vaccine budget.'); + const canvasRef = useRef(null); + const networkRef = useRef({ nodes: [], edges: [] }); + const controlsRef = useRef({ transmission, recovery, contacts }); + const pausedRef = useRef(paused); + const budgetRef = useRef(budget); + const toolRef = useRef(tool); + const resetRef = useRef(0); + + useEffect(() => { controlsRef.current = { transmission, recovery, contacts }; }, [transmission, recovery, contacts]); + useEffect(() => { pausedRef.current = paused; }, [paused]); + useEffect(() => { budgetRef.current = budget; }, [budget]); + useEffect(() => { toolRef.current = tool; }, [tool]); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return undefined; + const context = canvas.getContext('2d'); + if (!context) return undefined; + let width = 0; + let height = 0; + let animationId = 0; + let frameCount = 0; + let accumulator = 0; + let lastTime = performance.now(); + let lastReset = -1; + + function resize() { + const rect = canvas.getBoundingClientRect(); + const ratio = Math.min(window.devicePixelRatio || 1, 2); + width = Math.max(320, rect.width); + height = Math.max(360, rect.height); + canvas.width = Math.floor(width * ratio); + canvas.height = Math.floor(height * ratio); + context.setTransform(ratio, 0, 0, ratio, 0, 0); + networkRef.current = buildNetwork(62, width, height, 734); + } + + function resetNetwork() { + networkRef.current = buildNetwork(62, width, height, Date.now() % 100000); + setBudget(12); + setStats({ infected: 0, recovered: 0, immunized: 0, peak: 0 }); + } + + const observer = new ResizeObserver(resize); + observer.observe(canvas); + resize(); + + function onClick(event) { + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + const nearest = networkRef.current.nodes.reduce((best, candidate) => { + const distance = Math.hypot(candidate.x - x, candidate.y - y); + return distance < best.distance ? { node: candidate, distance } : best; + }, { node: null, distance: Infinity }); + if (!nearest.node || nearest.distance > 20) return; + const node = nearest.node; + if (toolRef.current === 'infect' && node.state === 'susceptible') { + node.state = 'infected'; + node.timer = 0; + setNotice('Patient zero selected. Switch to immunize and interrupt the graph.'); + } + if (toolRef.current === 'immunize' && node.state === 'susceptible' && budgetRef.current > 0) { + node.state = 'immunized'; + setBudget((value) => value - 1); + setNotice('Node immunized. Strategic bridge positions matter more than random coverage.'); + } + } + + canvas.addEventListener('click', onClick); + + function step(delta) { + const { nodes, edges } = networkRef.current; + const active = controlsRef.current; + const newlyInfected = new Set(); + edges.forEach((edge) => { + const a = nodes[edge.a]; + const b = nodes[edge.b]; + const attempts = Math.max(1, Math.round(active.contacts)); + for (let attempt = 0; attempt < attempts; attempt += 1) { + const probability = active.transmission * delta * 0.48; + if (a.state === 'infected' && b.state === 'susceptible' && Math.random() < probability) newlyInfected.add(b); + if (b.state === 'infected' && a.state === 'susceptible' && Math.random() < probability) newlyInfected.add(a); + } + }); + newlyInfected.forEach((node) => { node.state = 'infected'; node.timer = 0; }); + nodes.forEach((node) => { + if (node.state === 'infected') { + node.timer += delta; + if (node.timer > active.recovery) node.state = 'recovered'; + } + }); + } + + function render(now) { + if (lastReset !== resetRef.current) { + lastReset = resetRef.current; + resetNetwork(); + } + const delta = Math.min(0.05, (now - lastTime) / 1000); + lastTime = now; + accumulator += delta; + if (!pausedRef.current && accumulator > 0.035) { + step(accumulator); + accumulator = 0; + } + + context.fillStyle = '#02050d'; + context.fillRect(0, 0, width, height); + const { nodes, edges } = networkRef.current; + edges.forEach((edge) => { + const a = nodes[edge.a]; + const b = nodes[edge.b]; + context.strokeStyle = a.state === 'infected' || b.state === 'infected' ? 'rgba(255, 88, 115, 0.28)' : 'rgba(125, 249, 255, 0.09)'; + context.lineWidth = 1; + context.beginPath(); + context.moveTo(a.x, a.y); + context.lineTo(b.x, b.y); + context.stroke(); + }); + + const palette = { susceptible: '#5a7184', infected: '#ff5873', recovered: '#7df9ff', immunized: '#d9ff65' }; + nodes.forEach((node) => { + context.shadowBlur = node.state === 'infected' ? 18 : 10; + context.shadowColor = palette[node.state]; + context.fillStyle = palette[node.state]; + context.beginPath(); + context.arc(node.x, node.y, node.state === 'infected' ? 6 : 4.5, 0, Math.PI * 2); + context.fill(); + context.shadowBlur = 0; + }); + + if (frameCount % 14 === 0) { + const infected = nodes.filter((node) => node.state === 'infected').length; + const recoveredCount = nodes.filter((node) => node.state === 'recovered').length; + const immunized = nodes.filter((node) => node.state === 'immunized').length; + setStats((current) => ({ infected, recovered: recoveredCount, immunized, peak: Math.max(current.peak, infected) })); + } + frameCount += 1; + animationId = window.requestAnimationFrame(render); + } + + animationId = window.requestAnimationFrame(render); + return () => { + observer.disconnect(); + canvas.removeEventListener('click', onClick); + window.cancelAnimationFrame(animationId); + }; + }, []); + + const containment = Math.round(clamp(100 - (stats.peak / 62) * 100 + budget * 1.2, 0, 100)); + + function challengeUrl() { + const url = new URL(window.location.href); + url.searchParams.set('lab', 'outbreak'); + url.searchParams.set('state', [round(transmission, 2), recovery, contacts].join(',')); + url.hash = 'playground'; + return url.toString(); + } + + return ( +
+ +
+
+ +
Click nodes to {tool === 'infect' ? 'choose patient zero' : 'immunize'} · vaccines left: {budget}
+
+ +
+ setPaused((value) => !value)} + onReset={() => { resetRef.current += 1; setNotice('Fresh contact network generated. Choose a new patient zero.'); }} + onShare={() => shareChallenge({ title: `I contained GaugeGap outbreak at ${containment}%`, text: `Can you keep the peak below ${stats.peak} with twelve vaccines?`, url: challengeUrl(), setNotice })} + onCopy={() => copyChallenge(challengeUrl(), setNotice)} + notice={notice} + /> +
+ ); +} From 8dd03cec97ad170ccbe433f10d6d1ccfb6b9d596 Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:28:00 -0400 Subject: [PATCH 4/7] Expand GaugeGap arcade selector to eight experiments --- .../features/gaugegap/ExperimentArcade.jsx | 62 ++++++++++++++++--- 1 file changed, 54 insertions(+), 8 deletions(-) diff --git a/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx b/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx index 713bee1..1ffefd4 100644 --- a/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx +++ b/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx @@ -1,7 +1,9 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Activity, Dna, Gauge, Shuffle, Sparkles, Waves } from 'lucide-react'; +import { Activity, CircleDot, Dna, Gauge, Orbit, Shield, Shuffle, Sparkles, Waves, Wind } from 'lucide-react'; import { AttractorPlayground } from './AttractorPlayground.jsx'; import { FireflySyncLab, ReactionDiffusionLab, WaveInterferenceLab } from './CollectivePlaygrounds.jsx'; +import { FlockMindLab, OutbreakZeroLab } from './AgentPlaygrounds.jsx'; +import { ChaosTwinsLab, GravityForgeLab } from './PhysicsPlaygrounds.jsx'; import '../../styles/arcade.css'; const EXPERIMENTS = [ @@ -45,6 +47,46 @@ const EXPERIMENTS = [ accent: 'pink', mechanic: 'Create', }, + { + id: 'gravity', + number: '005', + title: 'Gravity Forge', + short: 'Build a solar system', + description: 'Launch planets into a live n-body field and see whether your orbital architecture survives.', + icon: Orbit, + accent: 'amber', + mechanic: 'Construct', + }, + { + id: 'flock', + number: '006', + title: 'Flock Mind', + short: 'Steer emergence', + description: 'Act as predator or beacon while leaderless agents coordinate through only local rules.', + icon: Wind, + accent: 'blue', + mechanic: 'Influence', + }, + { + id: 'outbreak', + number: '007', + title: 'Outbreak Zero', + short: 'Contain the network', + description: 'Choose patient zero, spend twelve vaccines and break the transmission graph before it turns red.', + icon: Shield, + accent: 'red', + mechanic: 'Intervene', + }, + { + id: 'pendulum', + number: '008', + title: 'Chaos Twins', + short: 'Race two futures', + description: 'Start two double pendulums almost identically and measure how quickly their futures separate.', + icon: CircleDot, + accent: 'orange', + mechanic: 'Predict', + }, ]; function initialExperiment() { @@ -89,8 +131,8 @@ export function ExperimentArcade() {

GaugeGap science arcade

-

Four experiments. Four different ways to play.

-

Tune chaos, synchronize a crowd, erase a wave or draw living chemistry. Every lab starts in seconds and hides the real model one layer underneath.

+

Eight experiments. Eight different ways to think.

+

Tune, synchronize, explore, draw, construct, influence, intervene and predict. Every lab begins as play, then exposes the scientific model underneath.

@@ -128,15 +170,19 @@ export function ExperimentArcade() { {active === 'fireflies' ? : null} {active === 'waves' ? : null} {active === 'reaction' ? : null} + {active === 'gravity' ? : null} + {active === 'flock' ? : null} + {active === 'outbreak' ? : null} + {active === 'pendulum' ? : null}
- What we borrowed from the best interactive-science sites + A complete science arcade needs Immediate motion - Recognizable presets - One clear challenge - Different interaction styles - A shareable result + A clear mission + Different interaction verbs + A visible model + A shareable challenge
); From 0ee1a9e49fbae8d82c0df3254bf5ca1712d0918b Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:28:27 -0400 Subject: [PATCH 5/7] Style the deeper GaugeGap experiment library --- brainsnn-r3f-app/src/styles/deep-arcade.css | 228 ++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 brainsnn-r3f-app/src/styles/deep-arcade.css diff --git a/brainsnn-r3f-app/src/styles/deep-arcade.css b/brainsnn-r3f-app/src/styles/deep-arcade.css new file mode 100644 index 0000000..a4810b9 --- /dev/null +++ b/brainsnn-r3f-app/src/styles/deep-arcade.css @@ -0,0 +1,228 @@ +.gg-arcade-card-amber { --arcade-accent: #ffc857; } +.gg-arcade-card-blue { --arcade-accent: #60a5fa; } +.gg-arcade-card-red { --arcade-accent: #ff5873; } +.gg-arcade-card-orange { --arcade-accent: #fb923c; } + +.gg-deep-lab { + background: + radial-gradient(circle at 74% 0%, rgba(96, 165, 250, 0.1), transparent 34%), + linear-gradient(180deg, rgba(9, 11, 25, 0.98), rgba(3, 5, 13, 0.99)); +} + +.gg-gravity-canvas, +.gg-flock-canvas, +.gg-outbreak-canvas, +.gg-pendulum-canvas { + cursor: crosshair; +} + +.gg-deep-mission { + display: grid; + grid-template-columns: auto 1fr; + gap: 5px 10px; + padding: 14px; + border: 1px solid rgba(125, 249, 255, 0.16); + border-radius: 14px; + color: #dffcff; + background: linear-gradient(135deg, rgba(125, 249, 255, 0.07), rgba(139, 92, 246, 0.04)); +} + +.gg-deep-mission svg { + grid-row: 1 / span 2; + align-self: center; + color: var(--bsn-cyan); +} + +.gg-deep-mission span, +.gg-model-card span { + color: var(--bsn-text-muted); + font-family: var(--bsn-font-mono); + font-size: 0.66rem; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.gg-deep-mission strong { + color: var(--bsn-text); + font-size: 0.82rem; + line-height: 1.4; +} + +.gg-deep-presets, +.gg-deep-toggle { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.gg-deep-presets button, +.gg-deep-toggle button { + min-height: 39px; + padding: 7px 9px; + border: 1px solid rgba(255, 255, 255, 0.09); + border-radius: 10px; + color: var(--bsn-text-secondary); + background: rgba(255, 255, 255, 0.025); + font-size: 0.72rem; + font-weight: 800; + cursor: pointer; +} + +.gg-deep-presets button:hover, +.gg-deep-presets button.active, +.gg-deep-toggle button:hover, +.gg-deep-toggle button.active { + border-color: rgba(125, 249, 255, 0.44); + color: #e7ffff; + background: rgba(125, 249, 255, 0.08); +} + +.gg-deep-presets button:last-child:nth-child(odd) { + grid-column: 1 / -1; +} + +.gg-model-card { + padding: 14px; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 14px; + background: rgba(255, 255, 255, 0.025); +} + +.gg-model-card strong { + display: block; + margin-top: 6px; + color: #f3f5ff; + font-family: var(--bsn-font-display); + font-size: 1rem; +} + +.gg-model-card p { + margin: 7px 0 0; + color: var(--bsn-text-secondary); + font-size: 0.78rem; + line-height: 1.5; +} + +.gg-deep-actions { + display: flex; + align-items: center; + gap: 9px; + flex-wrap: wrap; + margin-top: 17px; + padding-top: 16px; + border-top: 1px solid rgba(255, 255, 255, 0.07); +} + +.gg-deep-actions button { + min-height: 40px; + display: inline-flex; + align-items: center; + gap: 7px; + padding: 0 12px; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 10px; + color: var(--bsn-text-secondary); + background: rgba(255, 255, 255, 0.025); + font-size: 0.75rem; + font-weight: 800; + cursor: pointer; +} + +.gg-deep-actions button:hover { + border-color: rgba(125, 249, 255, 0.42); + color: #e7ffff; + background: rgba(125, 249, 255, 0.07); +} + +.gg-deep-actions > span { + flex: 1 1 260px; + color: var(--bsn-text-muted); + font-size: 0.78rem; + line-height: 1.4; + text-align: right; +} + +.gg-outbreak-stats { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 7px; +} + +.gg-outbreak-stats span { + min-width: 0; + padding: 10px 8px; + border: 1px solid rgba(255, 255, 255, 0.07); + border-radius: 10px; + color: var(--bsn-text-muted); + background: rgba(255, 255, 255, 0.02); + font-family: var(--bsn-font-mono); + font-size: 0.62rem; + text-align: center; + text-transform: uppercase; +} + +.gg-outbreak-stats strong { + display: block; + margin-top: 4px; + color: #e7fbff; + font-size: 1rem; +} + +.gg-gravity-wrap::before, +.gg-flock-canvas + .gg-sim-canvas-label::before, +.gg-outbreak-canvas + .gg-sim-canvas-label::before, +.gg-pendulum-canvas + .gg-sim-canvas-label::before { + content: ""; + width: 7px; + height: 7px; + display: inline-block; + margin-right: 5px; + border-radius: 50%; + background: #7df9ff; + box-shadow: 0 0 12px rgba(125, 249, 255, 0.8); +} + +@media (max-width: 1050px) { + .gg-arcade-selector { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +@media (max-width: 720px) { + .gg-deep-actions { + align-items: stretch; + } + + .gg-deep-actions button { + flex: 1 1 calc(50% - 8px); + justify-content: center; + } + + .gg-deep-actions > span { + flex-basis: 100%; + text-align: left; + } + + .gg-deep-presets, + .gg-deep-toggle { + grid-template-columns: 1fr; + } + + .gg-deep-presets button:last-child:nth-child(odd) { + grid-column: auto; + } +} + +@media (max-width: 560px) { + .gg-arcade-selector { + grid-template-columns: 1fr; + } + + .gg-arcade-card { + min-height: 190px; + } + + .gg-outbreak-stats { + grid-template-columns: 1fr; + } +} From 85547443cd0ac7855fc8d3fc8a7d1ff97562c643 Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:28:58 -0400 Subject: [PATCH 6/7] Load styles for deeper GaugeGap labs --- brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx b/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx index 1ffefd4..58b984a 100644 --- a/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx +++ b/brainsnn-r3f-app/src/features/gaugegap/ExperimentArcade.jsx @@ -5,6 +5,7 @@ import { FireflySyncLab, ReactionDiffusionLab, WaveInterferenceLab } from './Col import { FlockMindLab, OutbreakZeroLab } from './AgentPlaygrounds.jsx'; import { ChaosTwinsLab, GravityForgeLab } from './PhysicsPlaygrounds.jsx'; import '../../styles/arcade.css'; +import '../../styles/deep-arcade.css'; const EXPERIMENTS = [ { From 4dfd2e41be614ce45bea268250a471bb79cbc3f0 Mon Sep 17 00:00:00 2001 From: PenguineWalkOS Date: Thu, 9 Jul 2026 23:29:52 -0400 Subject: [PATCH 7/7] Expand GaugeGap homepage to eight live experiments --- brainsnn-r3f-app/src/app/GaugeGapLanding.jsx | 52 +++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/brainsnn-r3f-app/src/app/GaugeGapLanding.jsx b/brainsnn-r3f-app/src/app/GaugeGapLanding.jsx index e9508bb..5ae2eb5 100644 --- a/brainsnn-r3f-app/src/app/GaugeGapLanding.jsx +++ b/brainsnn-r3f-app/src/app/GaugeGapLanding.jsx @@ -4,16 +4,20 @@ import { BrainCircuit, CheckCircle2, ChevronDown, + CircleDot, Dna, FlaskConical, Gauge, Layers3, Microscope, + Orbit, Play, Share2, + Shield, Sparkles, WandSparkles, Waves, + Wind, Zap, } from 'lucide-react'; import { Button } from '../components/ui/Button.jsx'; @@ -56,6 +60,38 @@ const LABS = [ icon: Dna, action: 'Grow a species', }, + { + id: 'gravity', + eyebrow: 'Live experiment 005', + title: 'Gravity Forge', + description: 'Launch planets into a live n-body field and build an orbital system that does not destroy itself.', + icon: Orbit, + action: 'Build a system', + }, + { + id: 'flock', + eyebrow: 'Live experiment 006', + title: 'Flock Mind', + description: 'Steer a leaderless crowd as predator or beacon while local rules create group intelligence.', + icon: Wind, + action: 'Steer emergence', + }, + { + id: 'outbreak', + eyebrow: 'Live experiment 007', + title: 'Outbreak Zero', + description: 'Pick patient zero, spend twelve vaccines and interrupt the contact graph before it turns red.', + icon: Shield, + action: 'Contain it', + }, + { + id: 'pendulum', + eyebrow: 'Live experiment 008', + title: 'Chaos Twins', + description: 'Race two nearly identical double pendulums and measure when their futures stop agreeing.', + icon: CircleDot, + action: 'Race the futures', + }, { id: 'soliton', eyebrow: 'Research engine', @@ -80,7 +116,7 @@ const LOOP = [ { number: '03', title: 'Publish', text: 'Export the visual, the challenge link and the explanation from one run.' }, ]; -const ARCADE_IDS = new Set(['attractor', 'fireflies', 'waves', 'reaction']); +const ARCADE_IDS = new Set(['attractor', 'fireflies', 'waves', 'reaction', 'gravity', 'flock', 'outbreak', 'pendulum']); export function GaugeGapLanding({ onStart, onNavigate, onOpenReconstruct }) { useEffect(() => { @@ -140,16 +176,16 @@ export function GaugeGapLanding({ onStart, onNavigate, onOpenReconstruct }) {

Science you can touch, remix and publish

Don’t just learn the universe. Play with it.

- Four live experiments turn difficult science into visual games and shareable challenges. - Shape chaos, synchronize a crowd, erase a wave or draw a new species. + Eight live experiments turn difficult science into visual games and shareable challenges. + Shape chaos, build orbits, steer a flock, contain an outbreak and race competing futures.

-
4 liveDistinct experiments
-
1-clickChallenge links
+
8 liveDistinct experiments
+
8 verbsDifferent ways to play
ExactShareable run states
OpenEquations and limits
@@ -182,8 +218,8 @@ export function GaugeGapLanding({ onStart, onNavigate, onOpenReconstruct }) {

Find the most beautiful edge of chaos.

- Then try three completely different systems - 4 labs + Then jump into seven completely different systems + 8 labs
@@ -280,7 +316,7 @@ export function GaugeGapLanding({ onStart, onNavigate, onOpenReconstruct }) {
-

Four worlds are already running

+

Eight worlds are already running

Pick a system. Break its rules. Share what appears.

The next person should arrive through your challenge—not through another explanation page.