2026-06-28 03:28:45 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Console — the FIVE real dock drawers (operator dock restructure). Each renderer
|
2026-06-28 21:39:54 +02:00
|
|
|
|
* takes (ctx, depth); what shows is gated by depth (condensed | expanded):
|
2026-06-28 03:28:45 +02:00
|
|
|
|
*
|
|
|
|
|
|
* learn — Learning : feedback-mode selector, solo/arm chooser, live params
|
|
|
|
|
|
* inputs — Inputs : input source
|
|
|
|
|
|
* route — Outputs : per-output control matrix for the ACTIVE mode/backend,
|
|
|
|
|
|
* a Mode-specific config section, and (Editor mode) the
|
|
|
|
|
|
* MEMLNaut serial panel. The old separate "Synth" and
|
|
|
|
|
|
* "Particle/Visual" drawers are REMOVED — their config now
|
|
|
|
|
|
* lives here under the active Mode (TOP dock selector).
|
2026-07-25 15:07:35 +02:00
|
|
|
|
* settings — Settings : icon style, input-map shape + feature flags
|
2026-06-28 03:28:45 +02:00
|
|
|
|
* help — Help : keymap + the loop explanation
|
|
|
|
|
|
*
|
|
|
|
|
|
* The TOP dock selector ("Mode") chooses the active OUTPUT backend/target; this
|
|
|
|
|
|
* drawer renders whatever that backend needs.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Engine wiring: the feedback-mode pill → controller.setMode; the arm flags →
|
|
|
|
|
|
* engine.feedback.setFocus; per-output rows write the shared MFParam store.
|
|
|
|
|
|
* Where engine support does not yet exist the UI + state are wired and a TODO
|
|
|
|
|
|
* references the relevant spec — no faked engine behaviour.
|
|
|
|
|
|
*/
|
2026-07-25 15:24:50 +02:00
|
|
|
|
import { useState, type ReactNode } from 'react';
|
2026-06-28 03:28:45 +02:00
|
|
|
|
import { Badge, Button, PillToggle, Slider, Switch } from '../primitives';
|
|
|
|
|
|
import type { ConsoleCtx, DrawerDepth, DrawerKey, FeedbackModeUI, SoloMode } from './types';
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
import type { InputMode } from '../inputs';
|
2026-06-28 03:28:45 +02:00
|
|
|
|
import { OutputControlRow } from '../dock/OutputControlRow';
|
|
|
|
|
|
import { OutputsBackendConfig, BackendStatusChip } from '../dock/OutputsBackendConfig';
|
2026-07-25 15:28:26 +02:00
|
|
|
|
import { shapeValues, type MFParam } from './model';
|
2026-06-28 03:28:45 +02:00
|
|
|
|
import { outputModeDescriptor } from './output-mode';
|
|
|
|
|
|
import { useSettings, unfocusedIconCss } from '../settings/settings-store';
|
|
|
|
|
|
import type { UnfocusedIconColour, InputMapMode } from '../settings/settings-store';
|
2026-07-25 15:16:37 +02:00
|
|
|
|
import type { ExampleResizePolicy, NetworkResizePolicy } from '../engine/io-reshape';
|
2026-07-25 15:24:50 +02:00
|
|
|
|
import { useEngine, useEngineVersion } from '../engine';
|
2026-06-28 03:28:45 +02:00
|
|
|
|
import { EditorPanel } from '../serial/EditorPanel';
|
feat: curve truth, DriverConfig, real telemetry, engine benchmark
Four items from one workflow, committed together because their build and CI
wiring genuinely interleaves — nisps/CMakeLists.txt, run-all-tests.sh and
ci.yml each carry hunks from two of them, and the stage renumbering (1/5 ->
1/6) touches every line. Splitting would produce commits that do not build,
which is worse than a commit that does four things and says so.
S26 part 2 — the curve declaration now matches reality. params[].curve stays
the mode-wide DEFAULT; a voice_spaces entry may now be {name, curve_overrides}
declaring only the slots where THAT voice space deviates. The 6 modes with one
voice space are byte-identical. The values were derived MECHANICALLY by a new
codegen/curve-audit.ts that models the four idioms a p[N]*p[N] regex misses
(alias form, memlcelium's implicit-counter sq() lambda, loop-generated indices,
smooth_params_), inlines helpers, and RAISES rather than guessing when it
cannot reduce an expression. A drift gate cross-checks 1179 (voice space x
param) slots against engine source on every run and was proved to fail loudly
on three drift classes. Application stays in the engine: nisps/engines,
nisps/pipeline and nisps/core are untouched, generated output is pure insertion
(755 insertions, 0 deletions), and the rebuilt nisps.wasm was byte-identical.
S4 / 7.2 — firmware reads the active mode's driver config at mode start, and
mic/line is real. My brief assumed the engine owns this; the code disagreed and
the code was right. sound_analysis_midi's EngineT is NoOpEngine — the mic lives
on a separately-composed AnalysisEngine member — so engine-level wiring would
have compiled, passed every gate, and left the one mic mode on line input.
Hence a mode-level seam defaulting to engine().driver_config(). Separately,
DriverConfig's defaults (line_level 0, output_volume 1.0) had drifted from
memllib's actual 3/0.8 because nothing had ever read them; wiring them as-is
would have made every silent mode louder and its line input maximally
insensitive — a behaviour change disguised as plumbing. Now pinned by a test.
Also: GetSysClockSpeed() panic()s on unsupported sample rates and runs on the
first line of setup(), so sample_rate needed a fallback ahead of clock setup.
CI's firmware env list gains soundanalysismidi — it is the only mic variant and
nothing else compiles that path.
Plan 5e — telemetry is real. A loss_history C-API entry across the full 5-layer
chain lets the browser read the per-iteration loss the core already records.
The audit named one fabrication site; there were two — wasm-iml.ts's
synchronous train() published lossHistory: [loss] as well. A third, ctx.loss,
was not merely dead but actively synthetic (fallbacks of prev * 0.82 and a
literal 0.5, rendered by nothing) and is deleted. The firmware buffer stays
untouched, per the L25 call. EngineApi.lossHistory() reads spine state rather
than the MLP handle, because trainAsync() fits on the worker's mirror net and
the handle would give a subtly-wrong second answer.
Plan 5f — engine throughput is measurable. One source compiled twice (CMake
natively, emcc for WASM) so the targets compare directly and no WASM export is
added. Sequencers are driven into a working state, and every row prints its own
working-state evidence so a number produced by an idle engine is visible rather
than plausible. Reports, never asserts: a wall-clock threshold on shared
hardware is meaningless or flaky, same call as the firmware size job.
ALIGNMENT: the telemetry defect is deleted (built, not deferred); the
performance defect is rewritten to what is actually left — these are HOST
numbers, and nothing measures the RP2350 at 150 MHz, which is the target the
mission's constraint is about. Q4 (memllib ownership) and Q5 (legacy feedback
modes) are closed.
Corrections to my own earlier claims, both found by agents contradicting the
brief: manifold/ONBOARDING.md was NOT "now accurate" — its primitives list
still named five deleted primitives and cited a seededGradient() that does not
exist. And the parity harness misses the sequencer engines because it runs 128
frames while their sequencers evaluate every 400-500 samples, NOT because
all-params-0.5 fails to trigger them (it does trigger: 0.5 maps to ratio 2,
firing three times per bar). The fix is a longer window, not different params.
Gates: run-all-tests.sh ALL GREEN — 4/4 ctest, parity PASS, lint clean, curve
drift 1179 slots ok, 39 e2e (was 33). Firmware: 5 envs built including the mic
variant.
2026-07-21 22:02:23 +02:00
|
|
|
|
import { TrainingHealth } from './TrainingHealth';
|
2026-06-28 03:28:45 +02:00
|
|
|
|
import {
|
|
|
|
|
|
LearningIcon,
|
|
|
|
|
|
InputsIcon,
|
|
|
|
|
|
OutputsIcon,
|
|
|
|
|
|
SettingsIcon,
|
|
|
|
|
|
HelpIcon,
|
|
|
|
|
|
} from './icons';
|
|
|
|
|
|
|
|
|
|
|
|
function Chip({ children, tone }: { children: ReactNode; tone?: string }) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<span
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
|
letterSpacing: '0.06em',
|
|
|
|
|
|
color: tone || 'var(--fg-mute)',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-pill)',
|
|
|
|
|
|
padding: '2px 8px',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
function SectionLabel({ children }: { children: ReactNode }) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
color: 'var(--fg-dim)',
|
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
|
letterSpacing: '0.1em',
|
|
|
|
|
|
marginTop: 'var(--sp-2)',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{children}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** A 2+ segment selector pill that drives a typed value. */
|
|
|
|
|
|
function Segmented<T extends string>({
|
|
|
|
|
|
value,
|
|
|
|
|
|
onChange,
|
|
|
|
|
|
options,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
value: T;
|
|
|
|
|
|
onChange: (v: T) => void;
|
|
|
|
|
|
options: { value: T; label: string }[];
|
|
|
|
|
|
}) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-pill)',
|
|
|
|
|
|
padding: 2,
|
|
|
|
|
|
gap: 2,
|
|
|
|
|
|
flexWrap: 'wrap',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{options.map((o) => {
|
|
|
|
|
|
const on = value === o.value;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={o.value}
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={() => onChange(o.value)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: on ? 'var(--accent)' : 'transparent',
|
|
|
|
|
|
color: on ? 'var(--bg)' : 'var(--fg-mute)',
|
|
|
|
|
|
border: 0,
|
|
|
|
|
|
borderRadius: 'var(--r-pill)',
|
|
|
|
|
|
padding: '5px 12px',
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
fontSize: 'var(--fs-xs)',
|
|
|
|
|
|
letterSpacing: '0.04em',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{o.label}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// 1. LEARNING-BEHAVIOUR (dock-spec §1; rl-feedback-design)
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const FEEDBACK_OPTS: { value: FeedbackModeUI; label: string }[] = [
|
|
|
|
|
|
{ value: 'geometric-dislike', label: 'Push away' },
|
|
|
|
|
|
{ value: 'explore-and-place', label: 'Explore & place' },
|
|
|
|
|
|
];
|
|
|
|
|
|
const FEEDBACK_DESC: Record<FeedbackModeUI, string> = {
|
|
|
|
|
|
'geometric-dislike':
|
|
|
|
|
|
'Down carves the current sound away from what you like — directed repulsion (Mode 1).',
|
|
|
|
|
|
'explore-and-place':
|
|
|
|
|
|
'Down re-rolls the whole net into a scratchpad you audition; + places a liked sound (Mode 2).',
|
|
|
|
|
|
};
|
refactor(manifold): delete the dead console UI stratum
Phase 1 group 5 (S15, S16, S18, S19, L22, L23, L20, L21, L1 delete-half).
- S15: the four-way focus/altitude system. setFocus was never called anywhere,
so only the 'composite' branch was reachable. Deleted SplitStage,
ReadoutStrip, InputMini, AltitudeNav, CompactAxis, the UI Focus type/prop,
the focus branches, stripPinned and the vacuous keyboard gates. MiniMeters
kept; engine.feedback.setFocus (a different, live thing) untouched.
- S16 + L1: the decorative stratum that rendered real-looking controls driving
nothing — A/B machinery, the fake seed, seededGradient + weightsRevision,
snapshots, master volume, bpm, and the learningRate/decay/tame/spreadLevel
sliders with their Drawers rows. Each was confirmed self-referential first.
NOTE a real behaviour change falls out of dropping `snapshots`: Undo outside
an active explore-and-place session used to pop a UI-only snapshot that
restored noiseCap/seed. It is now simply inactive unless a genuine
core-backed scratchpad undo exists. Geometric-dislike mode never had a real
undo primitive in the core, so only the fake path is gone.
- S18: BackendAdvanced.tsx and its Drawers block. It self-described as a
duplicate of the inline OutputsBackendConfig editor and BOTH rendered in the
same expanded drawer. OutputsBackendConfig already covers every backend.
- S19: pruned ConsoleCtx to the fields Dock/Drawers/OutputsBackendConfig
actually read; deleted the Axes type + axes/setAxis and the
preset/setPreset/offsetActive chain (permanently 'Sculpt'/false).
KEPT ctx.modes and ctx.setModeId despite having no reader today — the
Phase 5 instrument picker (§7.6, adopted) is built on exactly that plumbing.
- L22: the 5 dead primitives (Panel, StatusLine, ControlAxis, CurvePlot,
Sparkline) and their barrel exports, plus the now-dead .mf-axis-input CSS.
- L23: OutputControl/toOutputControl, ModeIconComponent and the BACKENDS
catalogue; Drawers now reads modeDesc.label/description from OUTPUT_MODES,
the surviving single catalogue.
- L20: the solo-mode selector's two unimplemented options no longer pretend to
be selectable.
- L21: FeedbackController vestiges — seed/undoDepth options, maxUndo, and six
ControllerEngine members nothing called (the finding named three; the other
three are used on the real EngineApi by debug/probe.ts, a different
interface, so removing them from ControllerEngine is safe).
Gates: run-all-tests.sh ALL GREEN (typecheck, 33 Playwright specs).
2026-07-21 12:49:25 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Solo behaviour. `mask-gradients` is the only variant the core actually
|
|
|
|
|
|
* implements — `soloMode` has no other observable effect today (the other two
|
|
|
|
|
|
* options need the C API's `train_masked` step). Rendered as a fixed,
|
|
|
|
|
|
* non-selectable label rather than a picker that offers dead choices
|
|
|
|
|
|
* (simplification audit L20).
|
|
|
|
|
|
*/
|
2026-06-28 03:28:45 +02:00
|
|
|
|
const SOLO_OPTS: { value: SoloMode; label: string }[] = [
|
|
|
|
|
|
{ value: 'mask-gradients', label: 'Mask gradients' },
|
|
|
|
|
|
{ value: 'zero-loss', label: 'Zero loss' },
|
|
|
|
|
|
{ value: 'dont-care', label: "Don't-care mask" },
|
|
|
|
|
|
];
|
|
|
|
|
|
const SOLO_DESC: Record<SoloMode, string> = {
|
|
|
|
|
|
'mask-gradients': 'Column-freeze (default) — only the armed output moves; the rest stay bit-identical.',
|
|
|
|
|
|
'zero-loss': 'Expressive, but armed and unarmed outputs share hidden weights, so others can drift.',
|
|
|
|
|
|
'dont-care': 'Each example stores a per-output mask so stale labels never pull unarmed outputs.',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-07-25 15:24:50 +02:00
|
|
|
|
function ModelArchitecture() {
|
|
|
|
|
|
const engine = useEngine();
|
|
|
|
|
|
useEngineVersion(engine);
|
|
|
|
|
|
const [detailsOpen, setDetailsOpen] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
if (!engine) {
|
|
|
|
|
|
return <p style={{ fontSize: 10, color: 'var(--fg-dim)', margin: 0 }}>engine not ready</p>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const arch = engine.architecture;
|
|
|
|
|
|
const hidden = arch.hidden.filter((size) => size > 0);
|
|
|
|
|
|
const layerCount = arch.numLayers || hidden.length + 1;
|
|
|
|
|
|
const sizes = hidden.map((size) => size.toString()).join(' → ');
|
|
|
|
|
|
const stageStyle = (tone: string) => ({
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column' as const,
|
|
|
|
|
|
gap: 2,
|
|
|
|
|
|
minWidth: 0,
|
|
|
|
|
|
padding: '6px 8px',
|
|
|
|
|
|
border: `1px solid ${tone}`,
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
});
|
|
|
|
|
|
const labelStyle = {
|
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
letterSpacing: '0.08em',
|
|
|
|
|
|
color: 'var(--fg-dim)',
|
|
|
|
|
|
};
|
|
|
|
|
|
const valueStyle = {
|
|
|
|
|
|
fontSize: 12,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
fontVariantNumeric: 'tabular-nums',
|
|
|
|
|
|
color: 'var(--fg)',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div data-testid="model-architecture" style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Chip tone="var(--accent)">
|
|
|
|
|
|
{layerCount} layer{layerCount === 1 ? '' : 's'}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
<Chip>{engine.weightCount.toLocaleString()} weights</Chip>
|
|
|
|
|
|
<Chip>{engine.exampleCount} examples</Chip>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr auto 1fr', gap: 4, alignItems: 'stretch' }}>
|
|
|
|
|
|
<div style={stageStyle('var(--accent-2)')}>
|
|
|
|
|
|
<span style={{ ...labelStyle, color: 'var(--accent-2)' }}>INPUT</span>
|
|
|
|
|
|
<span style={valueStyle}>{arch.inputSize} units</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
style={{ alignSelf: 'center', color: 'var(--fg-dim)', fontFamily: 'var(--font-mono)', fontSize: 11 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
→
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={stageStyle('var(--accent)')}>
|
|
|
|
|
|
<span style={{ ...labelStyle, color: 'var(--accent)' }}>OUTPUT</span>
|
|
|
|
|
|
<span style={valueStyle}>{arch.outputSize} units</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-expanded={detailsOpen}
|
|
|
|
|
|
onClick={() => setDetailsOpen((open) => !open)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
gap: 8,
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
padding: '5px 8px',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
color: 'var(--fg-mute)',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
textAlign: 'left',
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span style={{ color: 'var(--accent)' }}>{detailsOpen ? '▾' : '▸'}</span>
|
|
|
|
|
|
<span style={{ color: 'var(--fg)' }}>HIDDEN</span>
|
|
|
|
|
|
<span>{hidden.length} layer{hidden.length === 1 ? '' : 's'}</span>
|
|
|
|
|
|
{!detailsOpen && <span style={{ marginLeft: 'auto' }}>{sizes || 'none'}</span>}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{detailsOpen && (
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 3, paddingLeft: 12 }}>
|
|
|
|
|
|
{hidden.length > 0 ? (
|
|
|
|
|
|
hidden.map((size, i) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={i}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
justifyContent: 'space-between',
|
|
|
|
|
|
fontSize: 10,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
color: 'var(--fg-mute)',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span>HIDDEN {i + 1}</span>
|
|
|
|
|
|
<span style={{ color: 'var(--fg)' }}>{size} units</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span style={{ fontSize: 10, color: 'var(--fg-dim)' }}>no hidden layers</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-28 03:28:45 +02:00
|
|
|
|
function LearningDrawer(ctx: ConsoleCtx, depth: DrawerDepth) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
|
|
|
|
<Chip tone="var(--accent)">{FEEDBACK_OPTS.find((o) => o.value === ctx.feedbackMode)?.label}</Chip>
|
|
|
|
|
|
<Chip>arm: {ctx.armedCount ? `${ctx.armedCount} output${ctx.armedCount > 1 ? 's' : ''}` : 'all'}</Chip>
|
|
|
|
|
|
{ctx.exploring && <Chip tone="var(--accent-2)">exploring…</Chip>}
|
|
|
|
|
|
{ctx.learningPaused && <Chip tone="var(--warn)">learning paused</Chip>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<SectionLabel>Down action · feedback mode</SectionLabel>
|
|
|
|
|
|
<Segmented value={ctx.feedbackMode} onChange={ctx.setFeedbackMode} options={FEEDBACK_OPTS} />
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
{FEEDBACK_DESC[ctx.feedbackMode]}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-07-13 23:27:40 +02:00
|
|
|
|
<SectionLabel>Exploration</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant="secondary"
|
|
|
|
|
|
active={ctx.joltActive}
|
|
|
|
|
|
aria-label="Jolt — hold to morph the network's weights, release to freeze"
|
|
|
|
|
|
aria-pressed={ctx.joltActive}
|
|
|
|
|
|
onPointerDown={(e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
ctx.onJoltPress();
|
|
|
|
|
|
}}
|
|
|
|
|
|
onPointerUp={ctx.onJoltRelease}
|
|
|
|
|
|
onPointerLeave={ctx.onJoltRelease}
|
|
|
|
|
|
onPointerCancel={ctx.onJoltRelease}
|
|
|
|
|
|
style={{ touchAction: 'none', userSelect: 'none' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
Jolt {ctx.joltActive ? '(morphing…)' : '(hold)'}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>
|
|
|
|
|
|
hold to morph the whole net live · release to freeze
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<Slider
|
|
|
|
|
|
label="explore · output wander"
|
|
|
|
|
|
value={ctx.exploreIntensity}
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={1}
|
|
|
|
|
|
step={0.01}
|
|
|
|
|
|
onChange={ctx.setExploreIntensity}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{depth === 'expanded' && (
|
|
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Explore adds a slow random walk (Ornstein-Uhlenbeck) on the outputs so the sound roams;
|
|
|
|
|
|
likes and dislikes registered mid-wander steer the net toward what you want. 0 = off. Jolt
|
|
|
|
|
|
(firmware TogB1) and Explore (RVX1) drive the same core gestures as the MEMLNaut hardware.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-06-29 00:36:32 +02:00
|
|
|
|
<SectionLabel>Recorded examples</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
|
|
|
|
|
<Chip>
|
|
|
|
|
|
{ctx.datasetCount} example{ctx.datasetCount === 1 ? '' : 's'}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
<Button size="sm" variant="secondary" onClick={ctx.onClear}>
|
|
|
|
|
|
Clear
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>
|
|
|
|
|
|
forget every example & wipe the on-map marks
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-07-25 15:24:50 +02:00
|
|
|
|
<SectionLabel>Current model</SectionLabel>
|
|
|
|
|
|
<ModelArchitecture />
|
|
|
|
|
|
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Solo / arm scope</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Button size="sm" variant={ctx.armedCount ? 'secondary' : 'primary'} onClick={ctx.clearArmed}>
|
|
|
|
|
|
Arm all
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>
|
|
|
|
|
|
{ctx.armedCount
|
|
|
|
|
|
? `${ctx.armedCount} armed — arm with the S button on each output row`
|
|
|
|
|
|
: 'every live output learns'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<SectionLabel>Solo behaviour</SectionLabel>
|
refactor(manifold): delete the dead console UI stratum
Phase 1 group 5 (S15, S16, S18, S19, L22, L23, L20, L21, L1 delete-half).
- S15: the four-way focus/altitude system. setFocus was never called anywhere,
so only the 'composite' branch was reachable. Deleted SplitStage,
ReadoutStrip, InputMini, AltitudeNav, CompactAxis, the UI Focus type/prop,
the focus branches, stripPinned and the vacuous keyboard gates. MiniMeters
kept; engine.feedback.setFocus (a different, live thing) untouched.
- S16 + L1: the decorative stratum that rendered real-looking controls driving
nothing — A/B machinery, the fake seed, seededGradient + weightsRevision,
snapshots, master volume, bpm, and the learningRate/decay/tame/spreadLevel
sliders with their Drawers rows. Each was confirmed self-referential first.
NOTE a real behaviour change falls out of dropping `snapshots`: Undo outside
an active explore-and-place session used to pop a UI-only snapshot that
restored noiseCap/seed. It is now simply inactive unless a genuine
core-backed scratchpad undo exists. Geometric-dislike mode never had a real
undo primitive in the core, so only the fake path is gone.
- S18: BackendAdvanced.tsx and its Drawers block. It self-described as a
duplicate of the inline OutputsBackendConfig editor and BOTH rendered in the
same expanded drawer. OutputsBackendConfig already covers every backend.
- S19: pruned ConsoleCtx to the fields Dock/Drawers/OutputsBackendConfig
actually read; deleted the Axes type + axes/setAxis and the
preset/setPreset/offsetActive chain (permanently 'Sculpt'/false).
KEPT ctx.modes and ctx.setModeId despite having no reader today — the
Phase 5 instrument picker (§7.6, adopted) is built on exactly that plumbing.
- L22: the 5 dead primitives (Panel, StatusLine, ControlAxis, CurvePlot,
Sparkline) and their barrel exports, plus the now-dead .mf-axis-input CSS.
- L23: OutputControl/toOutputControl, ModeIconComponent and the BACKENDS
catalogue; Drawers now reads modeDesc.label/description from OUTPUT_MODES,
the surviving single catalogue.
- L20: the solo-mode selector's two unimplemented options no longer pretend to
be selectable.
- L21: FeedbackController vestiges — seed/undoDepth options, maxUndo, and six
ControllerEngine members nothing called (the finding named three; the other
three are used on the real EngineApi by debug/probe.ts, a different
interface, so removing them from ControllerEngine is safe).
Gates: run-all-tests.sh ALL GREEN (typecheck, 33 Playwright specs).
2026-07-21 12:49:25 +02:00
|
|
|
|
<Chip tone="var(--accent)">{SOLO_OPTS.find((o) => o.value === ctx.soloMode)?.label}</Chip>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
{SOLO_DESC[ctx.soloMode]} Solo only freezes the rest as far as a shared network allows.
|
refactor(manifold): delete the dead console UI stratum
Phase 1 group 5 (S15, S16, S18, S19, L22, L23, L20, L21, L1 delete-half).
- S15: the four-way focus/altitude system. setFocus was never called anywhere,
so only the 'composite' branch was reachable. Deleted SplitStage,
ReadoutStrip, InputMini, AltitudeNav, CompactAxis, the UI Focus type/prop,
the focus branches, stripPinned and the vacuous keyboard gates. MiniMeters
kept; engine.feedback.setFocus (a different, live thing) untouched.
- S16 + L1: the decorative stratum that rendered real-looking controls driving
nothing — A/B machinery, the fake seed, seededGradient + weightsRevision,
snapshots, master volume, bpm, and the learningRate/decay/tame/spreadLevel
sliders with their Drawers rows. Each was confirmed self-referential first.
NOTE a real behaviour change falls out of dropping `snapshots`: Undo outside
an active explore-and-place session used to pop a UI-only snapshot that
restored noiseCap/seed. It is now simply inactive unless a genuine
core-backed scratchpad undo exists. Geometric-dislike mode never had a real
undo primitive in the core, so only the fake path is gone.
- S18: BackendAdvanced.tsx and its Drawers block. It self-described as a
duplicate of the inline OutputsBackendConfig editor and BOTH rendered in the
same expanded drawer. OutputsBackendConfig already covers every backend.
- S19: pruned ConsoleCtx to the fields Dock/Drawers/OutputsBackendConfig
actually read; deleted the Axes type + axes/setAxis and the
preset/setPreset/offsetActive chain (permanently 'Sculpt'/false).
KEPT ctx.modes and ctx.setModeId despite having no reader today — the
Phase 5 instrument picker (§7.6, adopted) is built on exactly that plumbing.
- L22: the 5 dead primitives (Panel, StatusLine, ControlAxis, CurvePlot,
Sparkline) and their barrel exports, plus the now-dead .mf-axis-input CSS.
- L23: OutputControl/toOutputControl, ModeIconComponent and the BACKENDS
catalogue; Drawers now reads modeDesc.label/description from OUTPUT_MODES,
the surviving single catalogue.
- L20: the solo-mode selector's two unimplemented options no longer pretend to
be selectable.
- L21: FeedbackController vestiges — seed/undoDepth options, maxUndo, and six
ControllerEngine members nothing called (the finding named three; the other
three are used on the real EngineApi by debug/probe.ts, a different
interface, so removing them from ControllerEngine is safe).
Gates: run-all-tests.sh ALL GREEN (typecheck, 33 Playwright specs).
2026-07-21 12:49:25 +02:00
|
|
|
|
The other two behaviours need real core support (`train_masked`) and aren't selectable yet.
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
|
|
<SectionLabel>Live training params</SectionLabel>
|
|
|
|
|
|
<Slider label="noise cap" value={ctx.noiseCap} min={0} max={0.5} step={0.01} onChange={ctx.setNoiseCap} />
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Feedback lab</SectionLabel>
|
|
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
State machine: idle → exploring → commit / cancel. While Explore & place is exploring,
|
|
|
|
|
|
training is paused and the joystick auditions a random scratchpad net; + commits a placed
|
|
|
|
|
|
anchor and restores the real net.
|
|
|
|
|
|
</p>
|
2026-07-25 15:07:35 +02:00
|
|
|
|
{ctx.xavierSpreadEnabled && (
|
|
|
|
|
|
<Switch checked={ctx.spread} onChange={ctx.setSpread} label="Xavier (centred) weight regime" />
|
|
|
|
|
|
)}
|
feat: curve truth, DriverConfig, real telemetry, engine benchmark
Four items from one workflow, committed together because their build and CI
wiring genuinely interleaves — nisps/CMakeLists.txt, run-all-tests.sh and
ci.yml each carry hunks from two of them, and the stage renumbering (1/5 ->
1/6) touches every line. Splitting would produce commits that do not build,
which is worse than a commit that does four things and says so.
S26 part 2 — the curve declaration now matches reality. params[].curve stays
the mode-wide DEFAULT; a voice_spaces entry may now be {name, curve_overrides}
declaring only the slots where THAT voice space deviates. The 6 modes with one
voice space are byte-identical. The values were derived MECHANICALLY by a new
codegen/curve-audit.ts that models the four idioms a p[N]*p[N] regex misses
(alias form, memlcelium's implicit-counter sq() lambda, loop-generated indices,
smooth_params_), inlines helpers, and RAISES rather than guessing when it
cannot reduce an expression. A drift gate cross-checks 1179 (voice space x
param) slots against engine source on every run and was proved to fail loudly
on three drift classes. Application stays in the engine: nisps/engines,
nisps/pipeline and nisps/core are untouched, generated output is pure insertion
(755 insertions, 0 deletions), and the rebuilt nisps.wasm was byte-identical.
S4 / 7.2 — firmware reads the active mode's driver config at mode start, and
mic/line is real. My brief assumed the engine owns this; the code disagreed and
the code was right. sound_analysis_midi's EngineT is NoOpEngine — the mic lives
on a separately-composed AnalysisEngine member — so engine-level wiring would
have compiled, passed every gate, and left the one mic mode on line input.
Hence a mode-level seam defaulting to engine().driver_config(). Separately,
DriverConfig's defaults (line_level 0, output_volume 1.0) had drifted from
memllib's actual 3/0.8 because nothing had ever read them; wiring them as-is
would have made every silent mode louder and its line input maximally
insensitive — a behaviour change disguised as plumbing. Now pinned by a test.
Also: GetSysClockSpeed() panic()s on unsupported sample rates and runs on the
first line of setup(), so sample_rate needed a fallback ahead of clock setup.
CI's firmware env list gains soundanalysismidi — it is the only mic variant and
nothing else compiles that path.
Plan 5e — telemetry is real. A loss_history C-API entry across the full 5-layer
chain lets the browser read the per-iteration loss the core already records.
The audit named one fabrication site; there were two — wasm-iml.ts's
synchronous train() published lossHistory: [loss] as well. A third, ctx.loss,
was not merely dead but actively synthetic (fallbacks of prev * 0.82 and a
literal 0.5, rendered by nothing) and is deleted. The firmware buffer stays
untouched, per the L25 call. EngineApi.lossHistory() reads spine state rather
than the MLP handle, because trainAsync() fits on the worker's mirror net and
the handle would give a subtly-wrong second answer.
Plan 5f — engine throughput is measurable. One source compiled twice (CMake
natively, emcc for WASM) so the targets compare directly and no WASM export is
added. Sequencers are driven into a working state, and every row prints its own
working-state evidence so a number produced by an idle engine is visible rather
than plausible. Reports, never asserts: a wall-clock threshold on shared
hardware is meaningless or flaky, same call as the firmware size job.
ALIGNMENT: the telemetry defect is deleted (built, not deferred); the
performance defect is rewritten to what is actually left — these are HOST
numbers, and nothing measures the RP2350 at 150 MHz, which is the target the
mission's constraint is about. Q4 (memllib ownership) and Q5 (legacy feedback
modes) are closed.
Corrections to my own earlier claims, both found by agents contradicting the
brief: manifold/ONBOARDING.md was NOT "now accurate" — its primitives list
still named five deleted primitives and cited a seededGradient() that does not
exist. And the parity harness misses the sequencer engines because it runs 128
frames while their sequencers evaluate every 400-500 samples, NOT because
all-params-0.5 fails to trigger them (it does trigger: 0.5 maps to ratio 2,
firing three times per bar). The fix is a longer window, not different params.
Gates: run-all-tests.sh ALL GREEN — 4/4 ctest, parity PASS, lint clean, curve
drift 1179 slots ok, 39 e2e (was 33). Firmware: 5 envs built including the mic
variant.
2026-07-21 22:02:23 +02:00
|
|
|
|
|
|
|
|
|
|
<SectionLabel>Training health</SectionLabel>
|
|
|
|
|
|
<TrainingHealth />
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
2026-06-28 04:24:48 +02:00
|
|
|
|
// 2. INPUTS (workstream F; inputs-spec — modular input layer)
|
2026-06-28 03:28:45 +02:00
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
2026-06-28 04:24:48 +02:00
|
|
|
|
const STATUS_TONE: Record<string, string> = {
|
|
|
|
|
|
ready: 'var(--accent)',
|
|
|
|
|
|
connecting: 'var(--warn)',
|
|
|
|
|
|
unavailable: 'var(--fg-dim)',
|
|
|
|
|
|
error: 'var(--danger)',
|
|
|
|
|
|
idle: 'var(--fg-dim)',
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
const INPUT_MODE_OPTS: { value: InputMode; label: string }[] = [
|
|
|
|
|
|
{ value: 'internal', label: 'Internal' },
|
|
|
|
|
|
{ value: 'gamepad', label: 'Game Controller' },
|
|
|
|
|
|
{ value: 'midi', label: 'MIDI' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/** Standard-mapping gamepad button → verdict legend (mirrors ConsoleApp). */
|
|
|
|
|
|
const GAMEPAD_LEGEND: { btn: string; action: string }[] = [
|
|
|
|
|
|
{ btn: 'RB', action: 'Up · positive feedback' },
|
|
|
|
|
|
{ btn: 'LB', action: 'Down · negative feedback' },
|
|
|
|
|
|
{ btn: 'X', action: 'Randomise' },
|
|
|
|
|
|
{ btn: 'Y', action: 'Nudge' },
|
|
|
|
|
|
{ btn: 'B', action: 'Undo' },
|
|
|
|
|
|
{ btn: 'A (hold)', action: 'Reposition — hold, move stick, release to place' },
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Read-only INPUT meter — shows a learned MIDI control's live value. Deliberately
|
|
|
|
|
|
* styled apart from the output Sliders (which are orange, interactive thumbs):
|
|
|
|
|
|
* these are inset bars on the secondary accent with an "in" tag, so the user can
|
|
|
|
|
|
* see at a glance that these feed the net rather than being driven by it.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function MidiInputMeter({ label, value, onClear }: { label: string; value: number; onClear: () => void }) {
|
|
|
|
|
|
const pct = Math.max(0, Math.min(1, value));
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
|
|
|
|
<span
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
color: 'var(--accent-2)',
|
|
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
|
|
letterSpacing: '0.06em',
|
|
|
|
|
|
minWidth: 70,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{label}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
height: 8,
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderLeft: '2px solid var(--accent-2)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
inset: 0,
|
|
|
|
|
|
width: `${pct * 100}%`,
|
|
|
|
|
|
background: 'var(--accent-2)',
|
|
|
|
|
|
opacity: 0.55,
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span
|
|
|
|
|
|
style={{
|
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
color: 'var(--fg-mute)',
|
|
|
|
|
|
minWidth: 28,
|
|
|
|
|
|
textAlign: 'right',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{value.toFixed(2)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
onClick={onClear}
|
|
|
|
|
|
aria-label={`Remove ${label}`}
|
|
|
|
|
|
style={{ background: 'transparent', border: 0, color: 'var(--danger)', cursor: 'pointer', fontSize: 'var(--fs-xs)' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
✕
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-28 03:28:45 +02:00
|
|
|
|
function InputsDrawer(ctx: ConsoleCtx, depth: DrawerDepth) {
|
2026-06-28 04:24:48 +02:00
|
|
|
|
const inp = ctx.inputs;
|
feat(manifold): runtime-shaped net reshape with confirm modal (P2.3)
Wire the runtime-shaped WASM MLP (one-core-engine P2) through the manifold:
- WasmIML.reshape(dims, spread): calls nisps_ml_reshape, re-describes the
instance, reallocates every dim-dependent heap buffer, refreshes weightCount,
clears the TS Dataset mirror (C-side resets), drops the stale training worker,
and pushes the new shape through the sink.
- EngineApi.reshape exposes it and re-ticks the spine so outputs/audio reflect
the new net. Spine already tolerates the arity change (buffers resize, version
bumps); documented.
- Training worker protocol carries the current hidden dims; the worker
ensureNet()s its mirror net to match after a reshape.
- ConsoleApp offers the reshape behind ReshapeModal on an active-layout CHANGE
(never on load; default 32-input over-provisioned head + zero-padding
preserved when declined). British copy, reset-on-reshape.
- Drawers: delete the stale even/odd blending note; honest dedicated-dimensions
line + net-arity chip.
- Probe: __nisps.reshape(nIn) / .describe(); e2e reshape.spec (default 32/126,
reshape to 4, describe reports 4, bounded outputs, weight count 3148→2868,
spine still propagates). All 25 e2e pass (20 existing + 5 new).
- ONBOARDING: refresh the reshape status + stale hardwired-arity gotcha.
2026-07-14 03:54:12 +02:00
|
|
|
|
// The net is runtime-shaped (P2): each active axis drives its OWN input slot
|
|
|
|
|
|
// 1:1. A mismatch means the net is over-provisioned (axes < slots, extra slots
|
|
|
|
|
|
// zero-padded) or over capacity (axes > slots, extras dropped) — the reshape
|
|
|
|
|
|
// offer resolves either.
|
|
|
|
|
|
const inputMismatch = inp.axisCount > 0 && inp.axisCount !== inp.engineInputSize;
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
const active = inp.sources.find((s) => s.enabled);
|
|
|
|
|
|
const modeLabel = INPUT_MODE_OPTS.find((o) => o.value === inp.inputMode)?.label ?? 'Internal';
|
2026-06-28 04:24:48 +02:00
|
|
|
|
|
2026-06-28 03:28:45 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<>
|
2026-06-28 04:24:48 +02:00
|
|
|
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<Badge tone="info">{modeLabel}</Badge>
|
|
|
|
|
|
{inp.inputMode !== 'internal' && <Chip tone="var(--accent)">{inp.axisCount} axes</Chip>}
|
feat(manifold): runtime-shaped net reshape with confirm modal (P2.3)
Wire the runtime-shaped WASM MLP (one-core-engine P2) through the manifold:
- WasmIML.reshape(dims, spread): calls nisps_ml_reshape, re-describes the
instance, reallocates every dim-dependent heap buffer, refreshes weightCount,
clears the TS Dataset mirror (C-side resets), drops the stale training worker,
and pushes the new shape through the sink.
- EngineApi.reshape exposes it and re-ticks the spine so outputs/audio reflect
the new net. Spine already tolerates the arity change (buffers resize, version
bumps); documented.
- Training worker protocol carries the current hidden dims; the worker
ensureNet()s its mirror net to match after a reshape.
- ConsoleApp offers the reshape behind ReshapeModal on an active-layout CHANGE
(never on load; default 32-input over-provisioned head + zero-padding
preserved when declined). British copy, reset-on-reshape.
- Drawers: delete the stale even/odd blending note; honest dedicated-dimensions
line + net-arity chip.
- Probe: __nisps.reshape(nIn) / .describe(); e2e reshape.spec (default 32/126,
reshape to 4, describe reports 4, bounded outputs, weight count 3148→2868,
spine still propagates). All 25 e2e pass (20 existing + 5 new).
- ONBOARDING: refresh the reshape status + stale hardwired-arity gotcha.
2026-07-14 03:54:12 +02:00
|
|
|
|
{inputMismatch && (
|
|
|
|
|
|
<Chip tone="var(--warn)">
|
|
|
|
|
|
{inp.axisCount > inp.engineInputSize
|
|
|
|
|
|
? `${inp.axisCount} axes › ${inp.engineInputSize} slots`
|
|
|
|
|
|
: `net: ${inp.engineInputSize}-D`}
|
|
|
|
|
|
</Chip>
|
|
|
|
|
|
)}
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
{active && (
|
|
|
|
|
|
<Chip tone={STATUS_TONE[active.status.state] ?? 'var(--fg-dim)'}>{active.status.state}</Chip>
|
|
|
|
|
|
)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<SectionLabel>Input source</SectionLabel>
|
|
|
|
|
|
<Segmented value={inp.inputMode} onChange={inp.setInputMode} options={INPUT_MODE_OPTS} />
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{active && depth === 'expanded' && (
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<span style={{ fontSize: 9, color: STATUS_TONE[active.status.state] ?? 'var(--fg-dim)' }}>
|
|
|
|
|
|
{active.status.message}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
{/* ---- Internal (XY pad / manifold) ---- */}
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{inp.inputMode === 'internal' && depth === 'expanded' && (
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Drag the on-screen manifold / XY pad. Two axes feed the net directly — this is the default.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* ---- Game Controller ---- */}
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{inp.inputMode === 'gamepad' && depth === 'expanded' && (
|
2026-06-28 04:24:48 +02:00
|
|
|
|
<>
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<SectionLabel>Sticks</SectionLabel>
|
|
|
|
|
|
<Segmented
|
|
|
|
|
|
value={inp.gamepadStickMode}
|
|
|
|
|
|
onChange={inp.setGamepadStickMode}
|
|
|
|
|
|
options={[
|
|
|
|
|
|
{ value: 'single', label: 'One stick (2 ax)' },
|
|
|
|
|
|
{ value: 'double', label: 'Both sticks (4 ax)' },
|
|
|
|
|
|
]}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<SectionLabel>Buttons</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
|
|
|
|
{GAMEPAD_LEGEND.map((g) => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={g.btn}
|
|
|
|
|
|
style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 'var(--fs-xs)' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Chip tone="var(--accent)">{g.btn}</Chip>
|
|
|
|
|
|
<span style={{ color: 'var(--fg-mute)' }}>{g.action}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Connect a controller and press any button to wake it. Sticks drive the input map;
|
|
|
|
|
|
buttons fire the verdicts above.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
{/* ---- MIDI ---- */}
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{inp.inputMode === 'midi' && depth === 'expanded' && (
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Device</SectionLabel>
|
|
|
|
|
|
{inp.midiInputs.length === 0 ? (
|
|
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0 }}>
|
|
|
|
|
|
No MIDI inputs detected. Connect a device — it appears here automatically.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="sm"
|
|
|
|
|
|
variant={inp.midiDeviceId === null ? 'primary' : 'secondary'}
|
|
|
|
|
|
onClick={() => inp.selectMidiDevice(null)}
|
|
|
|
|
|
>
|
|
|
|
|
|
All ports
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
{inp.midiInputs.map((p) => (
|
2026-06-28 04:24:48 +02:00
|
|
|
|
<Button
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
key={p.id}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
size="sm"
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
variant={inp.midiDeviceId === p.id ? 'primary' : 'secondary'}
|
|
|
|
|
|
onClick={() => inp.selectMidiDevice(p.id)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
>
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
{p.name}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
</Button>
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
2026-06-28 04:24:48 +02:00
|
|
|
|
)}
|
|
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<SectionLabel>MIDI Learn</SectionLabel>
|
|
|
|
|
|
{inp.midiLearnArmed ? (
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '8px 10px',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
border: '1px solid var(--accent-2)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
gap: 8,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--accent-2)', lineHeight: 1.5 }}>
|
|
|
|
|
|
Move all of the controls you want to use, then click Done. Each knob or fader you
|
|
|
|
|
|
touch becomes an input.
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<Button size="sm" variant="primary" onClick={() => inp.armMidiLearn(false)}>
|
|
|
|
|
|
Done ({inp.midiBindings.length} learned)
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
2026-06-28 04:24:48 +02:00
|
|
|
|
) : (
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center', flexWrap: 'wrap' }}>
|
|
|
|
|
|
<Button size="sm" variant="secondary" onClick={() => inp.armMidiLearn(true)}>
|
|
|
|
|
|
{inp.midiBindings.length ? 'Learn more…' : 'Start MIDI Learn'}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
{inp.midiBindings.length > 0 && (
|
|
|
|
|
|
<Button size="sm" variant="secondary" onClick={inp.clearMidiBindings}>
|
|
|
|
|
|
Clear all
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
{inp.midiBindings.length > 0 && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Learned controls</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
|
|
|
|
|
{inp.midiBindings.map((b, i) => (
|
|
|
|
|
|
<MidiInputMeter
|
|
|
|
|
|
key={`${b.kind}-${b.number}-${b.channel}`}
|
|
|
|
|
|
label={b.label}
|
|
|
|
|
|
value={b.value}
|
|
|
|
|
|
onClear={() => inp.clearMidiBinding(i)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
2026-06-28 04:24:48 +02:00
|
|
|
|
</>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
)}
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
|
feat(manifold): runtime-shaped net reshape with confirm modal (P2.3)
Wire the runtime-shaped WASM MLP (one-core-engine P2) through the manifold:
- WasmIML.reshape(dims, spread): calls nisps_ml_reshape, re-describes the
instance, reallocates every dim-dependent heap buffer, refreshes weightCount,
clears the TS Dataset mirror (C-side resets), drops the stale training worker,
and pushes the new shape through the sink.
- EngineApi.reshape exposes it and re-ticks the spine so outputs/audio reflect
the new net. Spine already tolerates the arity change (buffers resize, version
bumps); documented.
- Training worker protocol carries the current hidden dims; the worker
ensureNet()s its mirror net to match after a reshape.
- ConsoleApp offers the reshape behind ReshapeModal on an active-layout CHANGE
(never on load; default 32-input over-provisioned head + zero-padding
preserved when declined). British copy, reset-on-reshape.
- Drawers: delete the stale even/odd blending note; honest dedicated-dimensions
line + net-arity chip.
- Probe: __nisps.reshape(nIn) / .describe(); e2e reshape.spec (default 32/126,
reshape to 4, describe reports 4, bounded outputs, weight count 3148→2868,
spine still propagates). All 25 e2e pass (20 existing + 5 new).
- ONBOARDING: refresh the reshape status + stale hardwired-arity gotcha.
2026-07-14 03:54:12 +02:00
|
|
|
|
{/* ---- Dedicated-dimensions note (only with >2 active axes) ---- */}
|
|
|
|
|
|
{inp.axisCount > 2 && depth === 'expanded' && (
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
feat(manifold): runtime-shaped net reshape with confirm modal (P2.3)
Wire the runtime-shaped WASM MLP (one-core-engine P2) through the manifold:
- WasmIML.reshape(dims, spread): calls nisps_ml_reshape, re-describes the
instance, reallocates every dim-dependent heap buffer, refreshes weightCount,
clears the TS Dataset mirror (C-side resets), drops the stale training worker,
and pushes the new shape through the sink.
- EngineApi.reshape exposes it and re-ticks the spine so outputs/audio reflect
the new net. Spine already tolerates the arity change (buffers resize, version
bumps); documented.
- Training worker protocol carries the current hidden dims; the worker
ensureNet()s its mirror net to match after a reshape.
- ConsoleApp offers the reshape behind ReshapeModal on an active-layout CHANGE
(never on load; default 32-input over-provisioned head + zero-padding
preserved when declined). British copy, reset-on-reshape.
- Drawers: delete the stale even/odd blending note; honest dedicated-dimensions
line + net-arity chip.
- Probe: __nisps.reshape(nIn) / .describe(); e2e reshape.spec (default 32/126,
reshape to 4, describe reports 4, bounded outputs, weight count 3148→2868,
spine still propagates). All 25 e2e pass (20 existing + 5 new).
- ONBOARDING: refresh the reshape status + stale hardwired-arity gotcha.
2026-07-14 03:54:12 +02:00
|
|
|
|
The net has {inp.engineInputSize} dedicated inputs — each active axis drives its own
|
|
|
|
|
|
dimension 1:1 (no blending).
|
|
|
|
|
|
{inputMismatch
|
|
|
|
|
|
? ` Its ${inp.engineInputSize} slots don't match the ${inp.axisCount} active axes; ` +
|
|
|
|
|
|
`changing the layout offers a reshape to ${inp.axisCount} inputs (warm-started).`
|
|
|
|
|
|
: ''}
|
feat(manifold): MIDI + game controller inputs; widen ML net to N-D
Wire the modular input layer into the Console and reshape the browser
engine so input axes are genuine independent dimensions.
Inputs (manifold/src/inputs/):
- gamepad-source: emit press+release edges with standard-mapping labels
(enables hold-and-move); single/double-stick already present.
- midi-input-source: single-device selection + batch "MIDI Learn"
(every CC swept while armed becomes an axis); notes stay discrete.
- input-layer: compose() forwards each axis 1:1 (no mean-blend);
add onReducedInput so the manifold tracks gamepad/MIDI position.
- types: InputAction.phase, InputMode.
Console (manifold/src/console/):
- ConsoleApp: bind gamepad buttons to verdicts (RB up / LB down /
X randomise / Y nudge / B undo / A-hold reposition); mirror composed
position onto the manifold.
- Drawers: rebuilt Inputs drawer (source picker, gamepad legend, MIDI
device picker + batch-learn flow, learned-control meters).
Engine (nisps/wasm, manifold/src/engine):
- DefaultMLP widened MLP<2,..> -> MLP<32,..> (32 = MAX_AXES); each
active axis gets a dedicated slot, unused slots held at 0 (inert).
Rebuilt nisps.wasm (playground + manifold).
- spine/engine-api: setInputs writes the full N-D vector (was dropping
arr[2+]); primary pair keeps the 2-D pipeline; process() re-ticks the
whole vector via spine.reprocess().
Tests:
- parity_check/parity_wasm: ParityMLP -> 32 inputs, widen example bufs.
- CMakeLists: build parity binary with -ffp-contract=off so native
matches FMA-free WASM (training amplified the gap past 1e-5).
Inputs dock is still an exclusive picker; mixing toggles, reshape modal,
and the >2-D slider view (inputs-spec.md) are groundwork-laid but not
yet wired. See docs/redesign/midi-gamepad-inputs-worklog.md.
2026-06-28 21:05:30 +02:00
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// 3. OUTPUTS / ROUTING (dock-spec §3, §4)
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const VISUAL_NAMES = [
|
|
|
|
|
|
'Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius',
|
|
|
|
|
|
'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse',
|
|
|
|
|
|
'RepCnt', 'RepRate',
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Per-Mode config section shown ABOVE the per-output rows. The synth Mode shows
|
|
|
|
|
|
* transport + tempo; the particle Mode names the outputs; MIDI/OSC/Editor show
|
|
|
|
|
|
* their own affordances. Replaces the removed Synth + Visual drawers.
|
|
|
|
|
|
*/
|
|
|
|
|
|
function ModeConfig(ctx: ConsoleCtx, depth: DrawerDepth) {
|
|
|
|
|
|
switch (ctx.outputMode) {
|
|
|
|
|
|
case 'synth':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Transport</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
|
|
|
|
|
<Button size="sm" variant={ctx.audioStarted ? 'secondary' : 'primary'} onClick={ctx.onToggleAudio}>
|
|
|
|
|
|
{ctx.audioStarted ? 'pause' : 'play'}
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>audio starts on the play gesture</span>
|
|
|
|
|
|
</div>
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
refactor(manifold): delete the dead console UI stratum
Phase 1 group 5 (S15, S16, S18, S19, L22, L23, L20, L21, L1 delete-half).
- S15: the four-way focus/altitude system. setFocus was never called anywhere,
so only the 'composite' branch was reachable. Deleted SplitStage,
ReadoutStrip, InputMini, AltitudeNav, CompactAxis, the UI Focus type/prop,
the focus branches, stripPinned and the vacuous keyboard gates. MiniMeters
kept; engine.feedback.setFocus (a different, live thing) untouched.
- S16 + L1: the decorative stratum that rendered real-looking controls driving
nothing — A/B machinery, the fake seed, seededGradient + weightsRevision,
snapshots, master volume, bpm, and the learningRate/decay/tame/spreadLevel
sliders with their Drawers rows. Each was confirmed self-referential first.
NOTE a real behaviour change falls out of dropping `snapshots`: Undo outside
an active explore-and-place session used to pop a UI-only snapshot that
restored noiseCap/seed. It is now simply inactive unless a genuine
core-backed scratchpad undo exists. Geometric-dislike mode never had a real
undo primitive in the core, so only the fake path is gone.
- S18: BackendAdvanced.tsx and its Drawers block. It self-described as a
duplicate of the inline OutputsBackendConfig editor and BOTH rendered in the
same expanded drawer. OutputsBackendConfig already covers every backend.
- S19: pruned ConsoleCtx to the fields Dock/Drawers/OutputsBackendConfig
actually read; deleted the Axes type + axes/setAxis and the
preset/setPreset/offsetActive chain (permanently 'Sculpt'/false).
KEPT ctx.modes and ctx.setModeId despite having no reader today — the
Phase 5 instrument picker (§7.6, adopted) is built on exactly that plumbing.
- L22: the 5 dead primitives (Panel, StatusLine, ControlAxis, CurvePlot,
Sparkline) and their barrel exports, plus the now-dead .mf-axis-input CSS.
- L23: OutputControl/toOutputControl, ModeIconComponent and the BACKENDS
catalogue; Drawers now reads modeDesc.label/description from OUTPUT_MODES,
the surviving single catalogue.
- L20: the solo-mode selector's two unimplemented options no longer pretend to
be selectable.
- L21: FeedbackController vestiges — seed/undoDepth options, maxUndo, and six
ControllerEngine members nothing called (the finding named three; the other
three are used on the real EngineApi by debug/probe.ts, a different
interface, so removing them from ControllerEngine is safe).
Gates: run-all-tests.sh ALL GREEN (typecheck, 33 Playwright specs).
2026-07-21 12:49:25 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
The active engine follows the selected mode ({ctx.mode.label}).
|
|
|
|
|
|
{/* TODO(dock-spec §5): arpeggiator + tiered synth presets + the
|
|
|
|
|
|
18-section group-override matrix are workstream E. Master
|
|
|
|
|
|
volume + bpm sliders were deleted (simplification audit
|
|
|
|
|
|
S16) — they drove no engine parameter. */}
|
|
|
|
|
|
</p>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
case 'editor':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>MEMLNaut · USB serial</SectionLabel>
|
|
|
|
|
|
<EditorPanel />
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
case 'particles':
|
2026-06-28 21:39:54 +02:00
|
|
|
|
return depth === 'expanded' ? (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Flow-field visualiser driven by the first {Math.min(20, ctx.params.length)} outputs (no audio).
|
|
|
|
|
|
{/* TODO(backends-spec §4): port FlowFieldVisualizer + visual preset chips. */}
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null;
|
|
|
|
|
|
case 'midi':
|
|
|
|
|
|
// The full MIDI config (port picker, CC count, per-output CC/channel/name)
|
|
|
|
|
|
// + preset bar render via OutputsBackendConfig in RoutingDrawer below.
|
2026-06-28 21:39:54 +02:00
|
|
|
|
return depth === 'expanded' ? (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Each output sends a real Web MIDI CC. Pick a port and set CC# / channel per output.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null;
|
|
|
|
|
|
case 'osc':
|
2026-06-28 21:39:54 +02:00
|
|
|
|
return depth === 'expanded' ? (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Each output sends to an OSC path with a physical range, over the WebSocket bridge.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
) : null;
|
|
|
|
|
|
default:
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) {
|
2026-07-25 13:55:19 +02:00
|
|
|
|
const activeParams = ctx.params.slice(0, ctx.displayOutputCount);
|
|
|
|
|
|
const values = shapeValues(activeParams, null); // bar uses shaped held/live value snapshot
|
|
|
|
|
|
const counts = activeParams.reduce<Record<string, number>>((a, p) => {
|
2026-06-28 03:28:45 +02:00
|
|
|
|
a[p.status] = (a[p.status] || 0) + 1;
|
|
|
|
|
|
return a;
|
|
|
|
|
|
}, {});
|
2026-07-25 13:55:19 +02:00
|
|
|
|
const mutedN = activeParams.filter((p) => p.muted).length;
|
2026-06-28 03:28:45 +02:00
|
|
|
|
const modeDesc = outputModeDescriptor(ctx.outputMode);
|
2026-07-25 15:28:26 +02:00
|
|
|
|
// Particle labels belong to semantic cards, not their current array slots.
|
|
|
|
|
|
// A deletion compacts the active prefix, so deriving the label from `idx`
|
|
|
|
|
|
// would make the deleted name reappear on its successor.
|
|
|
|
|
|
const particleNamesById = new Map(
|
|
|
|
|
|
ctx.mode.params.map((param, index) => [param.id, VISUAL_NAMES[index] ?? param.name]),
|
|
|
|
|
|
);
|
|
|
|
|
|
const nameFor = (param: MFParam) =>
|
|
|
|
|
|
ctx.outputMode === 'particles'
|
|
|
|
|
|
? particleNamesById.get(param.id) ?? param.name
|
|
|
|
|
|
: param.name;
|
2026-06-28 03:28:45 +02:00
|
|
|
|
|
2026-06-28 21:39:54 +02:00
|
|
|
|
const expanded = depth === 'expanded';
|
2026-07-25 13:55:19 +02:00
|
|
|
|
const rows = expanded ? activeParams : activeParams.slice(0, 6);
|
2026-07-25 15:45:40 +02:00
|
|
|
|
const addButton = (placement: 'prepend' | 'append') => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
aria-label={placement === 'append' ? '+ output' : '+ output (prepend)'}
|
|
|
|
|
|
title={placement === 'append' ? 'Add parameter after the last card' : 'Add parameter before the first card'}
|
|
|
|
|
|
onClick={() => ctx.addOutput(placement)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
alignSelf: 'center',
|
|
|
|
|
|
width: 30,
|
|
|
|
|
|
height: 26,
|
|
|
|
|
|
margin: '4px 0 2px',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
color: 'var(--accent)',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
fontFamily: 'var(--font-mono)',
|
|
|
|
|
|
fontSize: 18,
|
|
|
|
|
|
lineHeight: 1,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
+
|
|
|
|
|
|
</button>
|
|
|
|
|
|
);
|
2026-06-28 03:28:45 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
|
|
|
|
<Chip tone="var(--accent)">{modeDesc.label}</Chip>
|
2026-07-25 14:45:04 +02:00
|
|
|
|
<Chip>{activeParams.length} output{activeParams.length === 1 ? '' : 's'}</Chip>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<Chip tone="var(--accent)">live {counts.live || 0}</Chip>
|
|
|
|
|
|
<Chip tone="var(--accent-2)">fixed {counts.fixed || 0}</Chip>
|
|
|
|
|
|
<Chip>off {counts.off || 0}</Chip>
|
|
|
|
|
|
<Chip tone="var(--danger)">muted {mutedN}</Chip>
|
|
|
|
|
|
<BackendStatusChip ctx={ctx} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{ModeConfig(ctx, depth)}
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{/* Specialised per-backend config + named-preset bar (MIDI/OSC); hidden when condensed. */}
|
|
|
|
|
|
{expanded && <OutputsBackendConfig ctx={ctx} backend={modeDesc.backend} />}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<SectionLabel>Outputs · M mute · S arm · off/fixed/live</SectionLabel>
|
|
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
|
gap: 4,
|
2026-07-25 15:24:50 +02:00
|
|
|
|
flex: expanded ? 1 : undefined,
|
|
|
|
|
|
minHeight: expanded ? 0 : undefined,
|
|
|
|
|
|
maxHeight: expanded ? undefined : 220,
|
2026-07-25 15:36:50 +02:00
|
|
|
|
padding: expanded ? '8px 10px' : '6px 10px',
|
2026-06-28 03:28:45 +02:00
|
|
|
|
overflow: 'auto',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-07-25 15:45:40 +02:00
|
|
|
|
{addButton('prepend')}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
{rows.map((p) => {
|
|
|
|
|
|
const i = ctx.params.indexOf(p);
|
2026-07-25 15:28:26 +02:00
|
|
|
|
const labelled = { ...p, name: nameFor(p) };
|
2026-06-28 03:28:45 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<OutputControlRow
|
2026-07-25 15:16:37 +02:00
|
|
|
|
key={p.id}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
param={labelled}
|
|
|
|
|
|
value={values[i] ?? 0}
|
|
|
|
|
|
onChange={(patch) => ctx.setParam(i, patch)}
|
2026-07-25 15:16:37 +02:00
|
|
|
|
onDelete={activeParams.length > 1 ? () => ctx.deleteOutput(i) : undefined}
|
2026-06-28 21:39:54 +02:00
|
|
|
|
showCurve={expanded}
|
2026-07-25 15:34:06 +02:00
|
|
|
|
showMidi={ctx.outputMode === 'midi'}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2026-07-25 15:45:40 +02:00
|
|
|
|
{addButton('append')}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</div>
|
2026-07-25 13:55:19 +02:00
|
|
|
|
{!expanded && activeParams.length > 6 && (
|
|
|
|
|
|
<span style={{ fontSize: 9, color: 'var(--fg-dim)' }}>+{activeParams.length - 6} more — expand to edit</span>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// 4. SETTINGS (operator dock restructure — settings-store)
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const ICON_COLOUR_OPTS: { value: UnfocusedIconColour; label: string }[] = [
|
|
|
|
|
|
{ value: 'off-white', label: 'Off-white' },
|
|
|
|
|
|
{ value: 'white', label: 'White' },
|
|
|
|
|
|
{ value: 'orange', label: 'Orange' },
|
|
|
|
|
|
];
|
|
|
|
|
|
const INPUT_MAP_OPTS: { value: InputMapMode; label: string }[] = [
|
|
|
|
|
|
{ value: 'follow-mode', label: 'Follow mode' },
|
|
|
|
|
|
{ value: 'rectangular', label: 'Rectangular' },
|
|
|
|
|
|
{ value: 'circular', label: 'Circular' },
|
|
|
|
|
|
];
|
2026-07-25 15:16:37 +02:00
|
|
|
|
const NETWORK_RESIZE_OPTS: { value: NetworkResizePolicy; label: string }[] = [
|
|
|
|
|
|
{ value: 'capacity', label: 'Keep capacity' },
|
|
|
|
|
|
{ value: 'exact', label: 'Exact I/O' },
|
|
|
|
|
|
];
|
|
|
|
|
|
const EXAMPLE_RESIZE_OPTS: { value: ExampleResizePolicy; label: string }[] = [
|
|
|
|
|
|
{ value: 'adapt', label: 'Adapt' },
|
|
|
|
|
|
{ value: 'clear', label: 'Clear' },
|
|
|
|
|
|
];
|
2026-06-28 03:28:45 +02:00
|
|
|
|
|
|
|
|
|
|
function SettingsDrawer({ depth }: { ctx: ConsoleCtx; depth: DrawerDepth }) {
|
|
|
|
|
|
const { settings, set } = useSettings();
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Icons</SectionLabel>
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
checked={settings.monochromeIcons}
|
|
|
|
|
|
onChange={(v) => set('monochromeIcons', v)}
|
|
|
|
|
|
label="Monochrome icons"
|
|
|
|
|
|
/>
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<>
|
|
|
|
|
|
<div style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>Unfocused icon colour</div>
|
|
|
|
|
|
<PillToggle
|
|
|
|
|
|
value={settings.unfocusedIconColour}
|
|
|
|
|
|
onChange={(v) => set('unfocusedIconColour', v as UnfocusedIconColour)}
|
|
|
|
|
|
options={ICON_COLOUR_OPTS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Focused / active icons are always accent orange. This sets the resting colour of unfocused
|
|
|
|
|
|
icons (preview below).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: 10, alignItems: 'center' }}>
|
|
|
|
|
|
<span style={{ color: unfocusedIconCss(settings.unfocusedIconColour), display: 'inline-flex' }}>
|
|
|
|
|
|
<SettingsIcon size={20} />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span style={{ color: 'var(--accent)', display: 'inline-flex' }}>
|
|
|
|
|
|
<SettingsIcon size={20} />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<SectionLabel>Input map</SectionLabel>
|
|
|
|
|
|
<PillToggle
|
|
|
|
|
|
value={settings.inputMap}
|
|
|
|
|
|
onChange={(v) => set('inputMap', v as InputMapMode)}
|
|
|
|
|
|
options={INPUT_MAP_OPTS}
|
|
|
|
|
|
/>
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
The 2D input surface: a rectangular XY map or a circular joystick-style disc. "Follow mode"
|
|
|
|
|
|
uses the active mode's declared input (joystick → circular, else rectangular).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-07-25 15:16:37 +02:00
|
|
|
|
<SectionLabel>I/O editing</SectionLabel>
|
|
|
|
|
|
<div style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>Network size</div>
|
|
|
|
|
|
<PillToggle
|
|
|
|
|
|
value={settings.networkResizePolicy}
|
|
|
|
|
|
onChange={(v) => set('networkResizePolicy', v as NetworkResizePolicy)}
|
|
|
|
|
|
options={NETWORK_RESIZE_OPTS}
|
|
|
|
|
|
ariaLabel="Network resize policy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<div style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)' }}>Existing examples</div>
|
|
|
|
|
|
<PillToggle
|
|
|
|
|
|
value={settings.exampleResizePolicy}
|
|
|
|
|
|
onChange={(v) => set('exampleResizePolicy', v as ExampleResizePolicy)}
|
|
|
|
|
|
options={EXAMPLE_RESIZE_OPTS}
|
|
|
|
|
|
ariaLabel="Example resize policy"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{depth === 'expanded' && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Keep capacity edits mappings in place and reconstructs only when the active cards outgrow
|
|
|
|
|
|
the network. Exact I/O keeps network arity equal to the cards. Surviving dimensions retain
|
|
|
|
|
|
their identity and weights; exploration scratch state resets after an I/O edit.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<Slider
|
|
|
|
|
|
label="New input example value"
|
|
|
|
|
|
value={settings.addedInputExampleValue}
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={1}
|
|
|
|
|
|
step={0.05}
|
|
|
|
|
|
onChange={(v) => set('addedInputExampleValue', v)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Slider
|
|
|
|
|
|
label="New output example value"
|
|
|
|
|
|
value={settings.addedOutputExampleValue}
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={1}
|
|
|
|
|
|
step={0.05}
|
|
|
|
|
|
onChange={(v) => set('addedOutputExampleValue', v)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<SectionLabel>Chrome</SectionLabel>
|
|
|
|
|
|
<Slider
|
|
|
|
|
|
label="Corner radius"
|
|
|
|
|
|
value={settings.cornerRadius}
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={14}
|
|
|
|
|
|
step={1}
|
|
|
|
|
|
unit="px"
|
|
|
|
|
|
onChange={(v) => set('cornerRadius', Math.round(v))}
|
|
|
|
|
|
/>
|
2026-06-28 21:39:54 +02:00
|
|
|
|
{depth === 'expanded' && (
|
2026-06-28 03:28:45 +02:00
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Roundness of buttons, control rows, dock icons and panels. Pills and the circular verdict
|
|
|
|
|
|
buttons are intentionally exempt. Default 2px.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-07-25 15:07:35 +02:00
|
|
|
|
|
|
|
|
|
|
<SectionLabel>Experimental features</SectionLabel>
|
|
|
|
|
|
<Switch
|
|
|
|
|
|
checked={settings.xavierSpreadEnabled}
|
|
|
|
|
|
onChange={(v) => set('xavierSpreadEnabled', v)}
|
|
|
|
|
|
label="Xavier / spread randomisation"
|
|
|
|
|
|
/>
|
|
|
|
|
|
{depth === 'expanded' && (
|
|
|
|
|
|
<p style={{ fontSize: 9, color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
|
|
|
|
|
|
Off by default: new networks and re-rolls use full-range uniform weights for broad,
|
|
|
|
|
|
strongly varied mappings. Enable this to restore the legacy centred regime and its
|
|
|
|
|
|
Learning-drawer switch.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
// 5. HELP
|
|
|
|
|
|
// ===========================================================================
|
|
|
|
|
|
|
|
|
|
|
|
const KEYS: [string, string][] = [
|
2026-06-29 00:45:45 +02:00
|
|
|
|
['1', 'down − / explore'],
|
|
|
|
|
|
['2', 'commit +'],
|
2026-06-28 03:28:45 +02:00
|
|
|
|
['space / ↑', 'commit +'],
|
2026-06-29 00:45:45 +02:00
|
|
|
|
['↓', 'down − / explore'],
|
|
|
|
|
|
['3–5', 'open drawers'],
|
|
|
|
|
|
['\\', 'expand drawer'],
|
2026-06-28 03:28:45 +02:00
|
|
|
|
['z', 'undo'],
|
|
|
|
|
|
['[ ] =', 'split (composite)'],
|
2026-06-29 00:45:45 +02:00
|
|
|
|
['dbl-click mark', 'follow mouse (Esc exits)'],
|
2026-06-28 03:28:45 +02:00
|
|
|
|
];
|
|
|
|
|
|
function HelpDrawer() {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SectionLabel>Keyboard</SectionLabel>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
|
|
|
|
{KEYS.map(([k, v]) => (
|
|
|
|
|
|
<div key={k} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'var(--fs-xs)' }}>
|
|
|
|
|
|
<kbd
|
|
|
|
|
|
style={{
|
|
|
|
|
|
background: 'var(--bg-2)',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
padding: '1px 6px',
|
|
|
|
|
|
color: 'var(--accent)',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
{k}
|
|
|
|
|
|
</kbd>
|
|
|
|
|
|
<span style={{ color: 'var(--fg-mute)' }}>{v}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<SectionLabel>The loop</SectionLabel>
|
|
|
|
|
|
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.7 }}>
|
|
|
|
|
|
Drag the manifold to explore. Hear something good → + to keep it. Wrong → − to push away or
|
|
|
|
|
|
re-roll (set the behaviour in the Learning drawer). Went too far → undo. The dock reveals
|
|
|
|
|
|
exactly as much machinery as you reach for.
|
|
|
|
|
|
</p>
|
2026-06-28 04:42:27 +02:00
|
|
|
|
<SectionLabel>Learn more</SectionLabel>
|
|
|
|
|
|
<a
|
|
|
|
|
|
href="animations/"
|
|
|
|
|
|
target="_blank"
|
|
|
|
|
|
rel="noopener"
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: 'inline-flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
gap: 6,
|
|
|
|
|
|
fontSize: 'var(--fs-xs)',
|
|
|
|
|
|
color: 'var(--accent-2)',
|
|
|
|
|
|
border: '1px solid var(--line)',
|
|
|
|
|
|
borderRadius: 'var(--r-1)',
|
|
|
|
|
|
padding: '5px 10px',
|
|
|
|
|
|
width: 'fit-content',
|
|
|
|
|
|
textDecoration: 'none',
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
▶ Watch the explainers + interactive demos
|
|
|
|
|
|
</a>
|
2026-06-28 03:28:45 +02:00
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface DrawerSection {
|
|
|
|
|
|
/** Monochrome inline-SVG icon (currentColor-driven by the dock button). */
|
|
|
|
|
|
icon: ReactNode;
|
|
|
|
|
|
/** Prior colour-emoji glyph, used when monochrome icons are OFF. */
|
|
|
|
|
|
glyph: string;
|
|
|
|
|
|
label: string;
|
|
|
|
|
|
render: (ctx: ConsoleCtx, depth: DrawerDepth) => ReactNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const DRAWERS: Record<DrawerKey, DrawerSection> = {
|
|
|
|
|
|
learn: { icon: <LearningIcon />, glyph: '🧠', label: 'Learning', render: LearningDrawer },
|
|
|
|
|
|
inputs: { icon: <InputsIcon />, glyph: '🎚', label: 'Inputs', render: InputsDrawer },
|
|
|
|
|
|
route: { icon: <OutputsIcon />, glyph: '🔀', label: 'Outputs', render: RoutingDrawer },
|
|
|
|
|
|
settings: { icon: <SettingsIcon />, glyph: '⚙', label: 'Settings', render: (c, d) => <SettingsDrawer ctx={c} depth={d} /> },
|
|
|
|
|
|
help: { icon: <HelpIcon />, glyph: '?', label: 'Help', render: HelpDrawer },
|
|
|
|
|
|
};
|