/* Console 2.0 — the reactive spine + layout. One input → ML → output flow that every consumer (readout strip, manifold, health glow) reads. Pseudo-inference stands in for the WASM MLP; the structure mirrors the rewrite plan. */ const CA_DS = window.ManifoldDesignSystem_490915; /* ---- shared model (modes + inference) lives in model.jsx ---- */ const MODES2 = window.MF_MODES; const infer = window.MF_infer; const seededGradient = window.MF_seededGradient; let SNAP_ID = 0; function ConsoleApp({ focus = 'in' }) { const [modeId, setModeId] = React.useState('paf_synth'); const mode = MODES2.find((m) => m.id === modeId); const [params, setParams] = React.useState(() => mode.params.map((p) => ({ ...p }))); const [pos, setPos] = React.useState([0.5, 0.5]); const [seed, setSeed] = React.useState(0.4); const [axes, setAxes] = React.useState({ boldness: 0.55, memory: 0.4, precision: 0.5 }); const [preset, setPreset] = React.useState('Sculpt'); const [noiseCap, setNoiseCap] = React.useState(0.12); const [examples, setExamples] = React.useState(0); const [addingExample, setAddingExample] = React.useState(false); const [loss, setLoss] = React.useState([]); const [busy, setBusy] = React.useState(false); const [snapshots, setSnapshots] = React.useState([]); const [ab, setAB] = React.useState('B'); const [holdingA, setHoldingA] = React.useState(false); const aRef = React.useRef(null); const [spread, setSpread] = React.useState(false); const [tame, setTame] = React.useState(0.85); const [health, setHealth] = React.useState(0.8); const [rev, setRev] = React.useState(1); const [active, setActive] = React.useState('feel'); // auto-open Feel peek const [depth, setDepth] = React.useState('peek'); const [follow, setFollow] = React.useState(false); const [split, setSplit] = React.useState(() => { const v = parseFloat(localStorage.getItem('mf-composite-split')); return Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 0.5; }); React.useEffect(() => { localStorage.setItem('mf-composite-split', String(split)); }, [split]); const [stripPinned, setStripPinned] = React.useState(true); const [firstSession, setFirstSession] = React.useState(true); const [pins, setPins] = React.useState([]); // reset transient state on mode switch React.useEffect(() => { setParams(mode.params.map((p) => ({ ...p }))); setPos([0.5, 0.5]); setExamples(0); setLoss([]); setSnapshots([]); setSeed(0.4); setFollow(false); setPins([]); setActive('feel'); setDepth('peek'); }, [modeId]); const effSeed = holdingA && aRef.current ? aRef.current.seed : seed; const values = React.useMemo(() => infer(pos, effSeed, params, axes), [pos, effSeed, params, axes]); const gradient = React.useMemo(() => seededGradient(rev), [rev]); const pushSnap = (tag) => setSnapshots((s) => [...s, { id: ++SNAP_ID, tag, noise: noiseCap, seed }].slice(-50)); const commit = () => { setFirstSession(false); pushSnap('commit +'); setBusy(true); setNoiseCap((n) => Math.max(0.02, n * 0.7)); setHealth((h) => Math.min(1, h + 0.08)); setRev((r) => r + 1); setTimeout(() => { setLoss((prev) => { const base = prev.length ? prev[prev.length - 1] : 0.5; return [...prev, Math.max(0.004, base * (0.8 + Math.random() * 0.08))].slice(-120); }); setBusy(false); }, 260); }; const perturb = () => { setFirstSession(false); pushSnap('perturb −'); setSeed((s) => s + (Math.random() - 0.5) * (noiseCap * 4 + 0.3)); setNoiseCap((n) => Math.min(0.5, n + 0.06)); setHealth((h) => Math.max(0.1, h - 0.06)); setRev((r) => r + 1); }; const reroll = () => { setFirstSession(false); pushSnap('re-roll'); setSeed(Math.random() * 6); setNoiseCap(0.4); setHealth(0.5); setRev((r) => r + 1); }; const undo = () => setSnapshots((s) => { if (!s.length) return s; const last = s[s.length - 1]; setSeed(last.seed); setNoiseCap(last.noise); setRev((r) => r + 1); return s.slice(0, -1); }); const train = () => { setBusy(true); setTimeout(() => { setLoss((p) => { const b = p.length ? p[p.length - 1] : 0.5; return [...p, Math.max(0.004, b * 0.82)].slice(-120); }); setBusy(false); }, 260); }; const addExample = () => { if (!addingExample) { setAddingExample(true); return; } setAddingExample(false); setExamples((e) => e + 1); pushSnap('example'); train(); }; const setParam = (i, patch) => setParams((ps) => ps.map((p, j) => (j === i ? { ...p, ...patch } : p))); const cycleStatus = (i) => setParams((ps) => ps.map((p, j) => (j === i ? { ...p, status: ({ off: 'fixed', fixed: 'live', live: 'off' })[p.status] } : p))); const toggleAB = () => { if (ab === 'B') { aRef.current = { seed }; setAB('A'); } else { if (aRef.current) setSeed(aRef.current.seed); setAB('B'); } }; // keyboard accelerators React.useEffect(() => { const onKey = (e) => { if (e.target.tagName === 'INPUT') return; const map = { '1': 'shape', '2': 'feel', '3': 'route', '4': 'health', '5': 'help' }; if (map[e.key]) { setActive((a) => (a === map[e.key] ? null : map[e.key])); setDepth('peek'); } else if (e.key === '\\') setDepth((d) => (d === 'full' ? 'peek' : 'full')); else if (focus === 'composite' && e.key === '[') { e.preventDefault(); setSplit((s) => Math.max(0, s - 0.04)); } else if (focus === 'composite' && e.key === ']') { e.preventDefault(); setSplit((s) => Math.min(1, s + 0.04)); } else if (focus === 'composite' && (e.key === '=' || e.key === '0')) { e.preventDefault(); setSplit(0.5); } else if (e.key === ' ' || e.key === 'ArrowUp') { e.preventDefault(); commit(); } else if (e.key === 'ArrowDown') { e.preventDefault(); perturb(); } else if (e.key.toLowerCase() === 'z') undo(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }); const ctx = { modes: MODES2, modeId, setModeId, mode, axes, setAxis: (k, v) => setAxes((s) => ({ ...s, [k]: v })), preset, setPreset, offsetActive: preset !== 'Sculpt', datasetCount: examples, loss, busy, addingExample, onAddExample: addExample, onTrain: train, onClear: () => { setExamples(0); setLoss([]); }, snapshots, onJump: (id) => { const s = snapshots.find((x) => x.id === id); if (s) { setSeed(s.seed); setNoiseCap(s.noise); setRev((r) => r + 1); } }, params, cycleStatus, outputBackend: 'audio', setOutputBackend: () => {}, health, gradient: gradient.norms, gradientStatus: gradient.status, weightsRevision: rev, spread, setSpread, tame, setTame, noiseCap, setNoiseCap, }; const healthColor = health > 0.66 ? 'rgba(107,194,107,' : health > 0.33 ? 'rgba(245,196,94,' : 'rgba(255,68,102,'; const Input = mode.input; return (
{/* ambient health glow at the screen edge */}
{/* stage = manifold area (left of dock) */}
{/* readout strip (input-first only — promoted to the stage in output-first) */} {focus === 'in' && (stripPinned || mode.cls !== 'Synth') && (
setStripPinned((p) => !p)} />
)} {/* the stage: manifold (input-first) or output field (output-first) */}
{focus === 'composite' ? ( setPos([x, y])} noiseCap={noiseCap} pins={pins} follow={follow} onLongPress={(p) => setPins((ps) => [...ps, { x: p[0], y: p[1], color: 'rgba(255,106,0,0.16)' }])} params={params} values={values} onChange={setParam} /> ) : focus === 'split' ? ( setPos([x, y])} noiseCap={noiseCap} pins={pins} follow={follow} onLongPress={(p) => setPins((ps) => [...ps, { x: p[0], y: p[1], color: 'rgba(255,106,0,0.16)' }])} params={params} values={values} onChange={setParam} /> ) : focus === 'out' ? ( setPos([x, y])} noiseCap={noiseCap} corner="bottom-left" /> ) : ( setPos([x, y])} noiseCap={noiseCap} pins={pins} frozen={false} follow={follow} onLongPress={(p) => setPins((ps) => [...ps, { x: p[0], y: p[1], color: 'rgba(255,106,0,0.16)' }])} /> )} {/* corner overlays */}
MEMLNaut {mode.label} {mode.badge && {mode.badge}}
{mode.cls.toLowerCase()} · {mode.input} · {params.length} targets
input ({pos[0].toFixed(2)}, {pos[1].toFixed(2)}) · noise {noiseCap.toFixed(3)} {holdingA && · preview A}
0} ab={ab} onToggleAB={toggleAB} onHoldA={setHoldingA} firstSession={firstSession} />
); } window.ConsoleApp = ConsoleApp;