Skip to content

Commit 9328751

Browse files
Jamie Nuchoclaude
andcommitted
Add self-hosted 3D particle visualizations for BST axioms
Embed interactive Three.js particle shapes into the web app: - Landing: BST_COMPLETE 30-second arc as animated hero background - Formal Theory: "See in 3D" expand on each axiom (1-4) + Theorem 1 - Path Invariance: 6-cluster convergence visualization with probe depth slider New particle engine (src/particles/) with 8 shape modules ported from bst_particles, lazy loading, IntersectionObserver auto-pause, and mobile optimization. Three.js code-split into separate chunk (~197KB gzipped). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c801616 commit 9328751

18 files changed

Lines changed: 968 additions & 11 deletions

web/package-lock.json

Lines changed: 8 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"react": "^18.2.0",
1515
"react-dom": "^18.2.0",
1616
"react-markdown": "^9.0.1",
17-
"react-router-dom": "^6.21.0"
17+
"react-router-dom": "^6.21.0",
18+
"three": "^0.183.2"
1819
},
1920
"devDependencies": {
2021
"@types/react": "^18.2.43",

web/src/components/FormalTheory.jsx

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { useState, useEffect } from 'react'
1+
import { useState } from 'react'
22
import { Link } from 'react-router-dom'
3+
import LazyParticleVis from '../particles/LazyParticleVis'
4+
import { AXIOM1, AXIOM2, AXIOM3, AXIOM4, THEOREM1 } from '../particles/shapes/index.js'
35

4-
// Map theorems to the probe questions that test them
56
const THEOREM_PROBES = {
67
'theorem-0': { label: 'Theorem 0: Unification', questions: [5, 16, 17, 18], description: 'Godel, Turing, and Chaitin as one structural limit' },
78
'theorem-1': { label: 'Theorem 1: Self-Grounding Limit', questions: [1, 2, 11, 12, 26, 27, 28], description: 'No expressive system can self-ground' },
@@ -10,6 +11,13 @@ const THEOREM_PROBES = {
1011
'falsifiability': { label: 'Falsifiability', questions: [11, 17, 20, 26, 28], description: 'Every attempt to falsify failed' },
1112
}
1213

14+
const AXIOM_SHAPES = {
15+
1: AXIOM1,
16+
2: AXIOM2,
17+
3: AXIOM3,
18+
4: AXIOM4,
19+
}
20+
1321
export default function FormalTheory() {
1422
return (
1523
<div className="max-w-3xl mx-auto px-4 py-8">
@@ -68,7 +76,7 @@ export default function FormalTheory() {
6876
</div>
6977
</Theorem>
7078

71-
<Theorem id="theorem-1" num={1} name="Self-Grounding Limit">
79+
<Theorem id="theorem-1" num={1} name="Self-Grounding Limit" shape={THEOREM1}>
7280
<Code>{'∀S : Expressive(S) ∧ SelfReferential(S)\n→ ¬SelfGrounding(C_S)'}</Code>
7381
<p className="text-xs text-muted mt-2">
7482
No sufficiently expressive self-referential system can achieve self-grounding of its own constraints.
@@ -143,19 +151,64 @@ function Section({ title, children }) {
143151
}
144152

145153
function Axiom({ num, name, children }) {
154+
const [showViz, setShowViz] = useState(false)
155+
const shape = AXIOM_SHAPES[num]
156+
146157
return (
147158
<div className="mb-4 p-4 border border-border rounded-lg">
148159
<p className="text-xs text-muted mb-2">Axiom {num}: {name}</p>
149160
{children}
161+
{shape && (
162+
<>
163+
<button
164+
onClick={() => setShowViz(!showViz)}
165+
className="mt-3 text-xs text-accent hover:text-accent/80 transition-colors"
166+
>
167+
{showViz ? 'Hide 3D' : 'See in 3D'}
168+
</button>
169+
{showViz && (
170+
<div className="mt-3 relative h-64 rounded overflow-hidden border border-border/50">
171+
<LazyParticleVis
172+
shape={shape}
173+
count={10000}
174+
bloom
175+
className="absolute inset-0"
176+
/>
177+
</div>
178+
)}
179+
</>
180+
)}
150181
</div>
151182
)
152183
}
153184

154-
function Theorem({ id, num, name, children }) {
185+
function Theorem({ id, num, name, shape, children }) {
186+
const [showViz, setShowViz] = useState(false)
187+
155188
return (
156189
<div id={id} className="mb-4 p-4 border border-accent/20 rounded-lg bg-accent/5">
157190
<p className="text-xs text-accent mb-2">Theorem {num}: {name}</p>
158191
{children}
192+
{shape && (
193+
<>
194+
<button
195+
onClick={() => setShowViz(!showViz)}
196+
className="mt-3 text-xs text-accent hover:text-accent/80 transition-colors"
197+
>
198+
{showViz ? 'Hide 3D' : 'See in 3D'}
199+
</button>
200+
{showViz && (
201+
<div className="mt-3 relative h-64 rounded overflow-hidden border border-accent/20">
202+
<LazyParticleVis
203+
shape={shape}
204+
count={10000}
205+
bloom
206+
className="absolute inset-0"
207+
/>
208+
</div>
209+
)}
210+
</>
211+
)}
159212
</div>
160213
)
161214
}

web/src/components/Landing.jsx

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { Link } from 'react-router-dom'
2+
import { lazy, Suspense } from 'react'
3+
4+
const ParticleVis = lazy(() => import('../particles/ParticleVis'))
5+
import { BST_COMPLETE } from '../particles/shapes/index.js'
26

37
export default function Landing({ data }) {
48
const { meta, questions } = data
@@ -7,15 +11,31 @@ export default function Landing({ data }) {
711
return (
812
<div className="min-h-screen flex flex-col">
913
{/* Hero */}
10-
<section className="flex-1 flex items-center justify-center px-4">
11-
<div className="max-w-2xl text-center">
12-
<p className="text-muted text-xs tracking-[0.3em] uppercase mb-6">
14+
<section className="flex-1 flex items-center justify-center px-4 relative overflow-hidden min-h-[80vh]">
15+
{/* Particle background */}
16+
<Suspense fallback={null}>
17+
<ParticleVis
18+
shape={BST_COMPLETE}
19+
count={15000}
20+
autoRotate
21+
showHUD={false}
22+
showControls={false}
23+
bloom
24+
className="absolute inset-0 z-0"
25+
/>
26+
</Suspense>
27+
28+
<div className="max-w-2xl text-center relative z-10">
29+
<p className="text-muted text-xs tracking-[0.3em] uppercase mb-6"
30+
style={{ textShadow: '0 0 8px rgba(0,0,0,0.8)' }}>
1331
Bounded Systems Theory
1432
</p>
15-
<h1 className="text-3xl md:text-5xl font-light leading-tight mb-6">
33+
<h1 className="text-3xl md:text-5xl font-light leading-tight mb-6"
34+
style={{ textShadow: '0 0 12px rgba(0,0,0,0.9), 0 0 24px rgba(0,0,0,0.7)' }}>
1635
No system can model<br />its own source.
1736
</h1>
18-
<p className="text-muted text-sm md:text-base leading-relaxed mb-10 max-w-lg mx-auto">
37+
<p className="text-muted text-sm md:text-base leading-relaxed mb-10 max-w-lg mx-auto"
38+
style={{ textShadow: '0 0 8px rgba(0,0,0,0.8)' }}>
1939
64 questions. 6 AI architectures. Every system hit the same structural wall.
2040
This is the data.
2141
</p>

web/src/components/PathInvariance.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { useState, useEffect, useRef } from 'react'
2+
import LazyParticleVis from '../particles/LazyParticleVis'
3+
import { BST_CONVERGENCE } from '../particles/shapes/index.js'
24

35
const PHASE_COLORS = {
46
foundation: '#3b82f6',
@@ -189,6 +191,23 @@ export default function PathInvariance() {
189191
</div>
190192
)}
191193

194+
{/* Convergence Visualization */}
195+
<section className="mb-10">
196+
<h2 className="text-sm font-semibold mb-2">Watch them converge</h2>
197+
<p className="text-xs text-muted mb-4">
198+
6 AI architectures start as separate clusters. Drag the probe depth slider to push them
199+
toward self-referential limits — watch them merge into one structure.
200+
</p>
201+
<div className="relative h-80 rounded-lg overflow-hidden border border-border">
202+
<LazyParticleVis
203+
shape={BST_CONVERGENCE}
204+
count={15000}
205+
bloom
206+
className="absolute inset-0"
207+
/>
208+
</div>
209+
</section>
210+
192211
{/* The 6 models tested */}
193212
<section className="mb-10">
194213
<h2 className="text-sm font-semibold mb-4">The 6 AI systems tested</h2>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useRef, useState, useEffect, lazy, Suspense } from 'react'
2+
3+
const ParticleVis = lazy(() => import('./ParticleVis'))
4+
5+
export default function LazyParticleVis(props) {
6+
const sentinelRef = useRef(null)
7+
const [visible, setVisible] = useState(false)
8+
9+
useEffect(() => {
10+
const el = sentinelRef.current
11+
if (!el) return
12+
const obs = new IntersectionObserver(
13+
([entry]) => {
14+
if (entry.isIntersecting) {
15+
setVisible(true)
16+
obs.disconnect()
17+
}
18+
},
19+
{ rootMargin: '200px' }
20+
)
21+
obs.observe(el)
22+
return () => obs.disconnect()
23+
}, [])
24+
25+
return (
26+
<div ref={sentinelRef} className={props.className || ''}>
27+
{visible ? (
28+
<Suspense fallback={
29+
<div className="absolute inset-0 flex items-center justify-center">
30+
<span className="text-xs text-muted animate-pulse">Loading visualization...</span>
31+
</div>
32+
}>
33+
<ParticleVis {...props} />
34+
</Suspense>
35+
) : (
36+
<div className="absolute inset-0 bg-surface/50 rounded" />
37+
)}
38+
</div>
39+
)
40+
}

0 commit comments

Comments
 (0)