fix(manifold): broaden randomisation by default
This commit is contained in:
parent
0bd65917c4
commit
6e71ad7d35
13 changed files with 128 additions and 18 deletions
2
MAP.md
2
MAP.md
|
|
@ -84,7 +84,7 @@ anchor + locked decisions) and the `docs/specs/*-spec.md` set.
|
||||||
`backends/base-backend.ts` is its output-side counterpart (status + throttle + lastSent) used by the midi/osc/vcv transports.
|
`backends/base-backend.ts` is its output-side counterpart (status + throttle + lastSent) used by the midi/osc/vcv transports.
|
||||||
- `manifold/src/feedback/` — `controller.ts` (Explore-and-place scratchpad + geometric-dislike + solo; a thin
|
- `manifold/src/feedback/` — `controller.ts` (Explore-and-place scratchpad + geometric-dislike + solo; a thin
|
||||||
driver over the shared C++ core).
|
driver over the shared C++ core).
|
||||||
- `manifold/src/settings/` — `settings-store.ts` (monochrome icons, input-map shape, corner radius).
|
- `manifold/src/settings/` — `settings-store.ts` (monochrome icons, input-map shape, corner radius, and the opt-in legacy Xavier/spread feature flag; Manifold randomisation is full-range uniform by default).
|
||||||
- `manifold/src/serial/` — `memlnaut-serial.ts` Web Serial scaffold + `EditorPanel.tsx` (MEMLNaut Editor mode).
|
- `manifold/src/serial/` — `memlnaut-serial.ts` Web Serial scaffold + `EditorPanel.tsx` (MEMLNaut Editor mode).
|
||||||
- `manifold/src/engine/exploration.ts` — Jolt press + OU explore gestures (Learning drawer): a thin
|
- `manifold/src/engine/exploration.ts` — Jolt press + OU explore gestures (Learning drawer): a thin
|
||||||
timer-driver over the shared C++ core via the `nisps_ml_jolt_*`/`nisps_ml_ou_*` bindings (the interim
|
timer-driver over the shared C++ core via the `nisps_ml_jolt_*`/`nisps_ml_ou_*` bindings (the interim
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ The browser MLP is runtime-shaped since P2 (`MLPCore<DynamicStorage>`): `nisps_m
|
||||||
|-------|-------|---------|--------|
|
|-------|-------|---------|--------|
|
||||||
| `debug` | 1 | _(off)_ | Exposes the `window.__nisps` debug probe. |
|
| `debug` | 1 | _(off)_ | Exposes the `window.__nisps` debug probe. |
|
||||||
|
|
||||||
(The playground-era `tame`/`spread`/`preset` URL params died with the playground; `spread` survives as an engine concept — see below.)
|
(The playground-era `tame`/`spread`/`preset` URL params died with the playground; `spread` survives as an opt-in engine concept — see below.)
|
||||||
|
|
||||||
### `spread` — sigmoid saturation control
|
### `spread` — sigmoid saturation control
|
||||||
|
|
||||||
|
|
@ -140,6 +140,13 @@ The MLP uses ReLU hidden layers with a sigmoid output. With uniform [-1,1] weigh
|
||||||
- `spread=1` (centered): Xavier-scaled weights, RL noise cap 0.05, 10% weight decay per move. Outputs spread across [0,1] — better for fine-grained shaping.
|
- `spread=1` (centered): Xavier-scaled weights, RL noise cap 0.05, 10% weight decay per move. Outputs spread across [0,1] — better for fine-grained shaping.
|
||||||
- Intermediate values interpolate.
|
- Intermediate values interpolate.
|
||||||
|
|
||||||
|
Manifold deliberately defaults to `spread=0` at every browser boundary: initial construction,
|
||||||
|
mode-switch reshape, direct re-roll, explore-and-place scratchpad re-roll, and forwarded VCV
|
||||||
|
randomise. The old schema spread and the expanded Learning-drawer centred switch are available only
|
||||||
|
after enabling Settings → Experimental features → **Xavier / spread randomisation**. This is a
|
||||||
|
Manifold compatibility flag; the shared C++ core and generated mode schemas still expose spread for
|
||||||
|
firmware, VCV, benchmarks, and explicit API callers.
|
||||||
|
|
||||||
## Verification chokepoints (user-confirmed)
|
## Verification chokepoints (user-confirmed)
|
||||||
|
|
||||||
- **A. Hardware**: each firmware mode flashes and produces correct audio on RP2350.
|
- **A. Hardware**: each firmware mode flashes and produces correct audio on RP2350.
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ a setting → `--r-*` tokens.
|
||||||
match. Debug: `window.__nisps.reshape(nIn)` / `.describe()`. See the `manifold-mixed-inputs` memory
|
match. Debug: `window.__nisps.reshape(nIn)` / `.describe()`. See the `manifold-mixed-inputs` memory
|
||||||
for the locked design (adaptive slider viz when >2 dims is still pending).
|
for the locked design (adaptive slider viz when >2 dims is still pending).
|
||||||
- **Per-mode net dims (P5.3):** switching INSTRUMENT mode reshapes the net to that mode's schema
|
- **Per-mode net dims (P5.3):** switching INSTRUMENT mode reshapes the net to that mode's schema
|
||||||
`ml` config (`MFMode.ml` — input/hidden/output + spread) via a `ConsoleApp` effect keyed on
|
`ml` config (`MFMode.ml` — input/hidden/output + legacy spread) via a `ConsoleApp` effect keyed on
|
||||||
`[engine, modeId]`. No confirm modal (switching instrument is deliberate); the axis-count
|
`[engine, modeId]`. No confirm modal (switching instrument is deliberate); the axis-count
|
||||||
`ReshapeModal` above is for input-LAYOUT changes only. The effect depends on `engine`, so on boot
|
`ReshapeModal` above is for input-LAYOUT changes only. The effect depends on `engine`, so on boot
|
||||||
it fires once WASM is ready and lands the boot mode's dims (**paf_synth → 4→[10,10,14]→33**, weights
|
it fires once WASM is ready and lands the boot mode's dims (**paf_synth → 4→[10,10,14]→33**, weights
|
||||||
|
|
@ -261,6 +261,10 @@ a setting → `--r-*` tokens.
|
||||||
Debug seam for tests: under `?debug=1` ConsoleApp installs `window.__mf`
|
Debug seam for tests: under `?debug=1` ConsoleApp installs `window.__mf`
|
||||||
(`setMode`/`getModeId`/`paramCount`/`modeIds`) — the UI-level analogue of `__nisps`, since no
|
(`setMode`/`getModeId`/`paramCount`/`modeIds`) — the UI-level analogue of `__nisps`, since no
|
||||||
in-UI instrument picker exists yet (`ctx.modes`/`setModeId` are plumbed but unrendered).
|
in-UI instrument picker exists yet (`ctx.modes`/`setModeId` are plumbed but unrendered).
|
||||||
|
Manifold passes `spread=0` for boot, mode-switch reshapes, direct re-rolls, explore-and-place
|
||||||
|
scratchpad rolls, and VCV-forwarded randomise gestures by default. Settings → Experimental
|
||||||
|
features → **Xavier / spread randomisation** restores the schema spread and reveals the centred
|
||||||
|
regime switch in the expanded Learning drawer.
|
||||||
|
|
||||||
### Feedback — `src/feedback/`
|
### Feedback — `src/feedback/`
|
||||||
- `controller.ts` — `FeedbackController`, framework-neutral, owned by ConsoleApp. **As of one-core-
|
- `controller.ts` — `FeedbackController`, framework-neutral, owned by ConsoleApp. **As of one-core-
|
||||||
|
|
@ -291,7 +295,8 @@ a setting → `--r-*` tokens.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- `src/serial/memlnaut-serial.ts` — **STUB** Web Serial scaffold for the MEMLNaut Editor mode (protocol TODO). `EditorPanel.tsx` is its UI.
|
- `src/serial/memlnaut-serial.ts` — **STUB** Web Serial scaffold for the MEMLNaut Editor mode (protocol TODO). `EditorPanel.tsx` is its UI.
|
||||||
- `src/settings/settings-store.ts` — localStorage settings (`mf-settings`): icon style, input-map shape, corner radius.
|
- `src/settings/settings-store.ts` — localStorage settings (`mf-settings`): icon style, input-map
|
||||||
|
shape, corner radius, and the opt-in legacy Xavier/spread feature flag.
|
||||||
- `src/midi-devices/` — codegen'd external-synth device templates.
|
- `src/midi-devices/` — codegen'd external-synth device templates.
|
||||||
- `src/debug/probe.ts` — `window.__nisps` synchronous probe (engine/audio/bus). Some playground
|
- `src/debug/probe.ts` — `window.__nisps` synchronous probe (engine/audio/bus). Some playground
|
||||||
feature-store methods are present-but-inert (not ported yet) to keep the surface stable.
|
feature-store methods are present-but-inert (not ported yet) to keep the surface stable.
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,11 @@ export function ConsoleApp() {
|
||||||
const [addingExample, setAddingExample] = useState(false);
|
const [addingExample, setAddingExample] = useState(false);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [spread, setSpread] = useState(false);
|
const [spread, setSpread] = useState(false);
|
||||||
|
// Manifold's normal regime is a full-range uniform draw. The per-mode legacy
|
||||||
|
// spread remains available only when explicitly enabled in Settings.
|
||||||
|
const randomisationSpread = settings.xavierSpreadEnabled
|
||||||
|
? (spread ? 1 : mode.ml.defaultSpread)
|
||||||
|
: 0;
|
||||||
const [active, setActive] = useState<DrawerKey | null>(null);
|
const [active, setActive] = useState<DrawerKey | null>(null);
|
||||||
const [depth, setDepth] = useState<DrawerDepth>('condensed');
|
const [depth, setDepth] = useState<DrawerDepth>('condensed');
|
||||||
// Sandwich (parameter-landscape) centre-stage toggle — dock-bottom layers icon.
|
// Sandwich (parameter-landscape) centre-stage toggle — dock-bottom layers icon.
|
||||||
|
|
@ -172,7 +177,7 @@ export function ConsoleApp() {
|
||||||
const controllerRef = useRef<FeedbackController | null>(null);
|
const controllerRef = useRef<FeedbackController | null>(null);
|
||||||
if (engine && !controllerRef.current) {
|
if (engine && !controllerRef.current) {
|
||||||
controllerRef.current = new FeedbackController(engine, {
|
controllerRef.current = new FeedbackController(engine, {
|
||||||
spread: 0.6,
|
spread: randomisationSpread,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,6 +234,12 @@ export function ConsoleApp() {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [engine, feedbackMode]);
|
}, [engine, feedbackMode]);
|
||||||
|
|
||||||
|
// Settings changes affect future randomise/explore gestures without
|
||||||
|
// destructively re-drawing the currently audible network.
|
||||||
|
useEffect(() => {
|
||||||
|
controllerRef.current?.setSpread(randomisationSpread);
|
||||||
|
}, [engine, randomisationSpread]);
|
||||||
|
|
||||||
// Push the selected solo-mode + the arm mask into the controller whenever they
|
// Push the selected solo-mode + the arm mask into the controller whenever they
|
||||||
// change (dock-spec §1.2). The controller RESPECTS the arm mask at the example
|
// change (dock-spec §1.2). The controller RESPECTS the arm mask at the example
|
||||||
// level in BOTH modes and forwards it to engine.feedback.setFocus.
|
// level in BOTH modes and forwards it to engine.feedback.setFocus.
|
||||||
|
|
@ -257,8 +268,8 @@ export function ConsoleApp() {
|
||||||
// modal stays for input-LAYOUT changes only (see the reshape-offer effect).
|
// modal stays for input-LAYOUT changes only (see the reshape-offer effect).
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!engine) return;
|
if (!engine) return;
|
||||||
const { inputSize, outputSize, hidden, defaultSpread } = mode.ml;
|
const { inputSize, outputSize, hidden } = mode.ml;
|
||||||
engine.reshape({ inputSize, outputSize, hidden }, defaultSpread);
|
engine.reshape({ inputSize, outputSize, hidden }, randomisationSpread);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [engine, modeId]);
|
}, [engine, modeId]);
|
||||||
|
|
||||||
|
|
@ -402,7 +413,7 @@ export function ConsoleApp() {
|
||||||
const forwardVcvFeedback = (op: 'up' | 'down' | 'rand' | 'clear') => {
|
const forwardVcvFeedback = (op: 'up' | 'down' | 'rand' | 'clear') => {
|
||||||
backendManager?.forwardFeedback({
|
backendManager?.forwardFeedback({
|
||||||
op,
|
op,
|
||||||
spread: spread ? 1 : 0.6,
|
spread: randomisationSpread,
|
||||||
input: [pos[0], pos[1]],
|
input: [pos[0], pos[1]],
|
||||||
output: Array.from(engine?.getOutputs() ?? new Float32Array(0)),
|
output: Array.from(engine?.getOutputs() ?? new Float32Array(0)),
|
||||||
});
|
});
|
||||||
|
|
@ -508,7 +519,7 @@ export function ConsoleApp() {
|
||||||
c.reroll();
|
c.reroll();
|
||||||
} else {
|
} else {
|
||||||
// Outside a scratchpad session a re-roll randomises the real net directly.
|
// Outside a scratchpad session a re-roll randomises the real net directly.
|
||||||
engine?.randomise(spread ? 1 : 0.6);
|
engine?.randomise(randomisationSpread);
|
||||||
}
|
}
|
||||||
// VCV bridged mode: re-roll the module's net too.
|
// VCV bridged mode: re-roll the module's net too.
|
||||||
forwardVcvFeedback('rand');
|
forwardVcvFeedback('rand');
|
||||||
|
|
@ -794,6 +805,7 @@ export function ConsoleApp() {
|
||||||
inputs,
|
inputs,
|
||||||
spread,
|
spread,
|
||||||
setSpread,
|
setSpread,
|
||||||
|
xavierSpreadEnabled: settings.xavierSpreadEnabled,
|
||||||
noiseCap,
|
noiseCap,
|
||||||
setNoiseCap,
|
setNoiseCap,
|
||||||
// learning-behaviour
|
// learning-behaviour
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
* MEMLNaut serial panel. The old separate "Synth" and
|
* MEMLNaut serial panel. The old separate "Synth" and
|
||||||
* "Particle/Visual" drawers are REMOVED — their config now
|
* "Particle/Visual" drawers are REMOVED — their config now
|
||||||
* lives here under the active Mode (TOP dock selector).
|
* lives here under the active Mode (TOP dock selector).
|
||||||
* settings — Settings : icon style + input-map shape (settings-store)
|
* settings — Settings : icon style, input-map shape + feature flags
|
||||||
* help — Help : keymap + the loop explanation
|
* help — Help : keymap + the loop explanation
|
||||||
*
|
*
|
||||||
* The TOP dock selector ("Mode") chooses the active OUTPUT backend/target; this
|
* The TOP dock selector ("Mode") chooses the active OUTPUT backend/target; this
|
||||||
|
|
@ -259,7 +259,9 @@ function LearningDrawer(ctx: ConsoleCtx, depth: DrawerDepth) {
|
||||||
training is paused and the joystick auditions a random scratchpad net; + commits a placed
|
training is paused and the joystick auditions a random scratchpad net; + commits a placed
|
||||||
anchor and restores the real net.
|
anchor and restores the real net.
|
||||||
</p>
|
</p>
|
||||||
<Switch checked={ctx.spread} onChange={ctx.setSpread} label="Xavier (centered) weight regime" />
|
{ctx.xavierSpreadEnabled && (
|
||||||
|
<Switch checked={ctx.spread} onChange={ctx.setSpread} label="Xavier (centred) weight regime" />
|
||||||
|
)}
|
||||||
|
|
||||||
<SectionLabel>Training health</SectionLabel>
|
<SectionLabel>Training health</SectionLabel>
|
||||||
<TrainingHealth />
|
<TrainingHealth />
|
||||||
|
|
@ -742,6 +744,20 @@ function SettingsDrawer({ depth }: { ctx: ConsoleCtx; depth: DrawerDepth }) {
|
||||||
buttons are intentionally exempt. Default 2px.
|
buttons are intentionally exempt. Default 2px.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,8 @@ export function SettingsIcon({ size = 18, style }: IconProps) {
|
||||||
size,
|
size,
|
||||||
style,
|
style,
|
||||||
<>
|
<>
|
||||||
|
<path d="M12.2 2h-.4a2 2 0 0 0-2 2v.2a2 2 0 0 1-1 1.7l-.4.3a2 2 0 0 1-2 0l-.2-.1a2 2 0 0 0-2.7.7l-.2.4A2 2 0 0 0 4 9.9l.2.1a2 2 0 0 1 1 1.7v.6a2 2 0 0 1-1 1.7l-.2.1a2 2 0 0 0-.7 2.7l.2.4a2 2 0 0 0 2.7.7l.2-.1a2 2 0 0 1 2 0l.4.3a2 2 0 0 1 1 1.7v.2a2 2 0 0 0 2 2h.4a2 2 0 0 0 2-2v-.2a2 2 0 0 1 1-1.7l.4-.3a2 2 0 0 1 2 0l.2.1a2 2 0 0 0 2.7-.7l.2-.4a2 2 0 0 0-.7-2.7l-.2-.1a2 2 0 0 1-1-1.7v-.6a2 2 0 0 1 1-1.7l.2-.1a2 2 0 0 0 .7-2.7l-.2-.4a2 2 0 0 0-2.7-.7l-.2.1a2 2 0 0 1-2 0l-.4-.3a2 2 0 0 1-1-1.7V4a2 2 0 0 0-2-2Z" />
|
||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
<path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.2 5.2l2.1 2.1M16.7 16.7l2.1 2.1M18.8 5.2l-2.1 2.1M7.3 16.7l-2.1 2.1" />
|
|
||||||
</>,
|
</>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,8 @@ export interface ConsoleCtx {
|
||||||
|
|
||||||
spread: boolean;
|
spread: boolean;
|
||||||
setSpread: (v: boolean) => void;
|
setSpread: (v: boolean) => void;
|
||||||
|
/** Settings feature flag: expose and apply the legacy Xavier/spread regime. */
|
||||||
|
xavierSpreadEnabled: boolean;
|
||||||
noiseCap: number;
|
noiseCap: number;
|
||||||
setNoiseCap: (v: number) => void;
|
setNoiseCap: (v: number) => void;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export interface EngineApiOptions {
|
||||||
learningRate?: number;
|
learningRate?: number;
|
||||||
/** Default max training iterations for train/trainAsync. */
|
/** Default max training iterations for train/trainAsync. */
|
||||||
maxIterations?: number;
|
maxIterations?: number;
|
||||||
/** Default RL move speed / spread for thumbsDown. */
|
/** Default RL move speed / randomisation spread. Spread defaults to 0. */
|
||||||
noiseCap?: number;
|
noiseCap?: number;
|
||||||
spread?: number;
|
spread?: number;
|
||||||
/**
|
/**
|
||||||
|
|
@ -141,7 +141,7 @@ export class EngineApi {
|
||||||
this.learningRate = opts.learningRate ?? ML_TRAIN_DEFAULTS.learningRate;
|
this.learningRate = opts.learningRate ?? ML_TRAIN_DEFAULTS.learningRate;
|
||||||
this.maxIterations = opts.maxIterations ?? ML_TRAIN_DEFAULTS.maxIterations;
|
this.maxIterations = opts.maxIterations ?? ML_TRAIN_DEFAULTS.maxIterations;
|
||||||
this.noiseCap = opts.noiseCap ?? 0.3;
|
this.noiseCap = opts.noiseCap ?? 0.3;
|
||||||
this.spread_ = opts.spread ?? 0.6;
|
this.spread_ = opts.spread ?? 0;
|
||||||
// Persist the configured default on the underlying MLP too (S26) — makes
|
// Persist the configured default on the underlying MLP too (S26) — makes
|
||||||
// the WASM engine's OWN training config match EngineApi's knobs, the same
|
// the WASM engine's OWN training config match EngineApi's knobs, the same
|
||||||
// real runtime-configurability firmware/VCV get for free from
|
// real runtime-configurability firmware/VCV get for free from
|
||||||
|
|
@ -216,6 +216,7 @@ export class EngineApi {
|
||||||
const spine = new Spine();
|
const spine = new Spine();
|
||||||
const iml = await WasmIML.create({
|
const iml = await WasmIML.create({
|
||||||
seed: opts.seed,
|
seed: opts.seed,
|
||||||
|
initialSpread: opts.spread ?? 0,
|
||||||
storageKey: opts.storageKey,
|
storageKey: opts.storageKey,
|
||||||
maxExamples: opts.maxExamples,
|
maxExamples: opts.maxExamples,
|
||||||
sink: spine,
|
sink: spine,
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,11 @@ export interface WasmIMLOptions {
|
||||||
outputSize?: number;
|
outputSize?: number;
|
||||||
hiddenLayers?: ReadonlyArray<number>;
|
hiddenLayers?: ReadonlyArray<number>;
|
||||||
seed?: number;
|
seed?: number;
|
||||||
|
/**
|
||||||
|
* Initial draw regime. Manifold defaults to 0 (full-range uniform); callers
|
||||||
|
* must opt into the legacy Xavier/spread behaviour explicitly.
|
||||||
|
*/
|
||||||
|
initialSpread?: number;
|
||||||
/** localStorage key the loaded weights/dataset will be persisted under. */
|
/** localStorage key the loaded weights/dataset will be persisted under. */
|
||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
/**
|
/**
|
||||||
|
|
@ -231,6 +236,10 @@ export class WasmIML {
|
||||||
const seed = (opts.seed ?? (Date.now() >>> 0)) >>> 0;
|
const seed = (opts.seed ?? (Date.now() >>> 0)) >>> 0;
|
||||||
this.mlHandle = this.module._nisps_ml_create(wantedIn, wantedOut, 0, 0, seed);
|
this.mlHandle = this.module._nisps_ml_create(wantedIn, wantedOut, 0, 0, seed);
|
||||||
if (!this.mlHandle) throw new Error('[wasm-iml] nisps_ml_create returned null');
|
if (!this.mlHandle) throw new Error('[wasm-iml] nisps_ml_create returned null');
|
||||||
|
// The C ABI constructor retains the cross-platform core's historical
|
||||||
|
// initialisation. Manifold deliberately overrides it at its boundary so an
|
||||||
|
// unconfigured browser engine starts with genuinely broad randomisation.
|
||||||
|
this.module._nisps_ml_draw_weights(this.mlHandle, opts.initialSpread ?? 0);
|
||||||
|
|
||||||
this.module._nisps_ml_describe(this.mlHandle, this.describePtr);
|
this.module._nisps_ml_describe(this.mlHandle, this.describePtr);
|
||||||
const dims = new Int32Array(this.module.HEAP32.buffer, this.describePtr, 7);
|
const dims = new Int32Array(this.module.HEAP32.buffer, this.describePtr, 7);
|
||||||
|
|
@ -354,7 +363,7 @@ export class WasmIML {
|
||||||
*/
|
*/
|
||||||
reshape(
|
reshape(
|
||||||
dims: { inputSize?: number; outputSize?: number; hidden?: readonly [number, number, number] },
|
dims: { inputSize?: number; outputSize?: number; hidden?: readonly [number, number, number] },
|
||||||
spread = 0.6,
|
spread = 0,
|
||||||
): boolean {
|
): boolean {
|
||||||
const wantIn = dims.inputSize ?? this.arch_.inputSize;
|
const wantIn = dims.inputSize ?? this.arch_.inputSize;
|
||||||
const wantOut = dims.outputSize ?? this.arch_.outputSize;
|
const wantOut = dims.outputSize ?? this.arch_.outputSize;
|
||||||
|
|
@ -743,7 +752,7 @@ export class WasmIML {
|
||||||
// RL ops
|
// RL ops
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
randomiseWeights(spread = 0.6): void {
|
randomiseWeights(spread = 0): void {
|
||||||
this.module._nisps_ml_draw_weights(this.mlHandle, spread);
|
this.module._nisps_ml_draw_weights(this.mlHandle, spread);
|
||||||
this.sink.emit('ml.delta_update', { reason: 'randomise' });
|
this.sink.emit('ml.delta_update', { reason: 'randomise' });
|
||||||
this.scheduleSave_();
|
this.scheduleSave_();
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ export interface FeedbackControllerState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FeedbackControllerOptions {
|
export interface FeedbackControllerOptions {
|
||||||
/** Master spread for randomise / nudge (mirrors the engine spread knob). */
|
/** Master spread for randomise / nudge. Defaults to full-range uniform (0). */
|
||||||
spread?: number;
|
spread?: number;
|
||||||
/** Nudge perturbation standard deviation (small bounded weight jitter). */
|
/** Nudge perturbation standard deviation (small bounded weight jitter). */
|
||||||
nudgeStddev?: number;
|
nudgeStddev?: number;
|
||||||
|
|
@ -145,7 +145,7 @@ export class FeedbackController {
|
||||||
|
|
||||||
constructor(engine: ControllerEngine, opts: FeedbackControllerOptions = {}) {
|
constructor(engine: ControllerEngine, opts: FeedbackControllerOptions = {}) {
|
||||||
this.engine = engine;
|
this.engine = engine;
|
||||||
this.spread = opts.spread ?? 0.6;
|
this.spread = opts.spread ?? 0;
|
||||||
this.nudgeStddev = opts.nudgeStddev ?? 0.05;
|
this.nudgeStddev = opts.nudgeStddev ?? 0.05;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
* - inputMap: the 2D input-surface shape. 'follow-mode' (default) uses the
|
* - inputMap: the 2D input-surface shape. 'follow-mode' (default) uses the
|
||||||
* active mode's declared input (joystick → circular, else rectangular);
|
* active mode's declared input (joystick → circular, else rectangular);
|
||||||
* 'rectangular' / 'circular' are explicit global overrides.
|
* 'rectangular' / 'circular' are explicit global overrides.
|
||||||
|
* - xavierSpreadEnabled: compatibility feature flag for the old centred
|
||||||
|
* Xavier/spread randomisation regime. Off by default, so Manifold initial
|
||||||
|
* weights and re-rolls use the full uniform range.
|
||||||
*
|
*
|
||||||
* British spelling in copy. No React inside the store itself — the hook is a
|
* British spelling in copy. No React inside the store itself — the hook is a
|
||||||
* separate, additive binding so a headless consumer (debug probe / test) can
|
* separate, additive binding so a headless consumer (debug probe / test) can
|
||||||
|
|
@ -35,6 +38,11 @@ export interface Settings {
|
||||||
* verdict buttons are intentionally exempt (separate tokens).
|
* verdict buttons are intentionally exempt (separate tokens).
|
||||||
*/
|
*/
|
||||||
cornerRadius: number;
|
cornerRadius: number;
|
||||||
|
/**
|
||||||
|
* Restore the legacy Xavier/spread randomisation regime and expose its
|
||||||
|
* Learning-drawer control. Off means full-range uniform randomisation.
|
||||||
|
*/
|
||||||
|
xavierSpreadEnabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: Settings = {
|
export const DEFAULT_SETTINGS: Settings = {
|
||||||
|
|
@ -42,6 +50,7 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||||
unfocusedIconColour: 'off-white',
|
unfocusedIconColour: 'off-white',
|
||||||
inputMap: 'follow-mode',
|
inputMap: 'follow-mode',
|
||||||
cornerRadius: 2,
|
cornerRadius: 2,
|
||||||
|
xavierSpreadEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const STORAGE_KEY = 'mf-settings';
|
const STORAGE_KEY = 'mf-settings';
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,33 @@ test.describe('ML engine — debug probe contract', () => {
|
||||||
expect(countChanged(before, after, 1e-3)).toBeGreaterThan(0);
|
expect(countChanged(before, after, 1e-3)).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('randomise defaults to a broad full-range mapping', async ({ page }) => {
|
||||||
|
const distribution = await page.evaluate(() => {
|
||||||
|
const probe = window.__nisps!;
|
||||||
|
const values: number[] = [];
|
||||||
|
for (let draw = 0; draw < 48; ++draw) {
|
||||||
|
probe.randomise();
|
||||||
|
probe.setInputs(0.5, 0.5);
|
||||||
|
values.push(...probe.getOutputs());
|
||||||
|
}
|
||||||
|
values.sort((a, b) => a - b);
|
||||||
|
const percentile = (p: number) => values[Math.floor((values.length - 1) * p)]!;
|
||||||
|
const centralFraction =
|
||||||
|
values.filter((value) => value >= 0.35 && value <= 0.65).length / values.length;
|
||||||
|
return {
|
||||||
|
p05: percentile(0.05),
|
||||||
|
p95: percentile(0.95),
|
||||||
|
centralFraction,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// The former implicit spread=0.6 regime put ~99.8% of values inside this
|
||||||
|
// central band. Uniform spread=0 must visibly reach both sides of it.
|
||||||
|
expect(distribution.p05).toBeLessThan(0.3);
|
||||||
|
expect(distribution.p95).toBeGreaterThan(0.7);
|
||||||
|
expect(distribution.centralFraction).toBeLessThan(0.8);
|
||||||
|
});
|
||||||
|
|
||||||
test('setInputs runs inference and yields bounded outputs', async ({ page }) => {
|
test('setInputs runs inference and yields bounded outputs', async ({ page }) => {
|
||||||
await page.evaluate(() => window.__nisps!.setInputs(0.25, 0.75));
|
await page.evaluate(() => window.__nisps!.setInputs(0.25, 0.75));
|
||||||
const outs = await getOutputs(page);
|
const outs = await getOutputs(page);
|
||||||
|
|
|
||||||
22
manifold/tests/e2e/randomisation-settings.spec.ts
Normal file
22
manifold/tests/e2e/randomisation-settings.spec.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { loadProbe } from './helpers';
|
||||||
|
|
||||||
|
test('legacy Xavier control is hidden until enabled in Settings', async ({ page }) => {
|
||||||
|
await loadProbe(page);
|
||||||
|
|
||||||
|
await page.getByTitle('Learning').click();
|
||||||
|
await page.getByTitle('Expand').click();
|
||||||
|
await expect(page.getByText('Xavier (centred) weight regime')).toHaveCount(0);
|
||||||
|
|
||||||
|
await page.getByTitle('Close').click();
|
||||||
|
await page.getByTitle('Settings').click();
|
||||||
|
const featureFlag = page.getByRole('switch', { name: 'Xavier / spread randomisation' });
|
||||||
|
await expect(featureFlag).toHaveAttribute('aria-checked', 'false');
|
||||||
|
await featureFlag.click();
|
||||||
|
await expect(featureFlag).toHaveAttribute('aria-checked', 'true');
|
||||||
|
|
||||||
|
await page.getByTitle('Close').click();
|
||||||
|
await page.getByTitle('Learning').click();
|
||||||
|
await page.getByTitle('Expand').click();
|
||||||
|
await expect(page.getByRole('switch', { name: 'Xavier (centred) weight regime' })).toBeVisible();
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue