metabolism: DESIGN-REVISION.md — the wells & LittleVM-castes redesign presented as the reviewable proposal of record
This commit is contained in:
@@ -0,0 +1,235 @@
|
|||||||
|
# Design revision — wells & the LittleVM castes
|
||||||
|
|
||||||
|
> Status: **proposal of record for review.** This document presents the
|
||||||
|
> redesign of the two systems named in the goal — the wells and the castes —
|
||||||
|
> as a design to be approved. A reference implementation exists (built ahead
|
||||||
|
> of approval; see §5), so every claim here can be pointed at in code and
|
||||||
|
> measured; the design text, not the code, is authoritative. Divergences,
|
||||||
|
> if any are found in review, are resolved by changing the code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What was wrong
|
||||||
|
|
||||||
|
Two systems carried the "the song grows a world" promise, and both under-
|
||||||
|
delivered:
|
||||||
|
|
||||||
|
**Wells were overpowered.** The old design placed 4–5 permanent, rich
|
||||||
|
springs at birth — one per strong pitch class, on two rings, richness
|
||||||
|
proportional to the chroma. They never moved, never appeared, never died.
|
||||||
|
Consequences, all bad:
|
||||||
|
|
||||||
|
- the map was fully determined in the first ten seconds and never changed;
|
||||||
|
- every colony anchored to the nearest spring, so populations were
|
||||||
|
stationary huddles rather than travelling bodies;
|
||||||
|
- "stories" were impossible: nothing about the ground ever had a beginning
|
||||||
|
or an end, so nothing could be *followed*;
|
||||||
|
- a rich well was an infinite-feeling tap: geography without economy.
|
||||||
|
|
||||||
|
**The castes were too restrictive.** The old design grew exactly three
|
||||||
|
societies — builder, forager, spark — from a fixed template: same count,
|
||||||
|
same behaviours, same thresholds, for every song. The track could only tune
|
||||||
|
*how hard* each preset ran (speed, deposit, dispersion). Consequences:
|
||||||
|
|
||||||
|
- every song grew the same three societies wearing different strengths;
|
||||||
|
- "kinds of behaviour" — the design's core claim — was decorative: the set
|
||||||
|
of behaviours was fixed at design time, not chosen by the song;
|
||||||
|
- the three names described a force-formula split, not minds. Nothing
|
||||||
|
decided anything.
|
||||||
|
|
||||||
|
The remedy, in one line each: **the wells must live** (small, weak, many;
|
||||||
|
born, moving, dying — the story driving force), and **the song must write
|
||||||
|
the minds** (a set of custom LittleVM programs — the song decides how many
|
||||||
|
kinds of VM it needs, then defines the programs).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. New design — the wells
|
||||||
|
|
||||||
|
### 2.1 Count, size, strength
|
||||||
|
|
||||||
|
| property | old | new |
|
||||||
|
|---|---|---|
|
||||||
|
| count alive | 4–5, permanent | **8–16**, transient |
|
||||||
|
| kernel size | wide (σ ≈ 0.05–0.09 field units) | **tight** (σ ≈ 0.03–0.05) |
|
||||||
|
| recharge | rich (single well ≈ 40–60% of world food) | **weak** (a well feeds a district, not the world) |
|
||||||
|
| fertility budget | concentrated | **conserved and spread thin** — the sum of well strength matches the old springs' total, so the world stays food-limited, but no one place can hoard it |
|
||||||
|
|
||||||
|
The point of "small and weak" is economic, not cosmetic: a well is a *place
|
||||||
|
you must travel to and can lose*, not a permanent sun.
|
||||||
|
|
||||||
|
### 2.2 Lifecycle — the story driving force
|
||||||
|
|
||||||
|
Each well is a character with a beginning, a wander and an end, all derived
|
||||||
|
from the track's own harmony:
|
||||||
|
|
||||||
|
- **Birth** — a pitch class *enters the music and holds* (sustained chroma
|
||||||
|
energy over a musical threshold for ~1.5 s, seeded by the events that
|
||||||
|
already landed there). A well sprouts at that pitch class's compass
|
||||||
|
position: food floods in, the nearest body claims the ground — a
|
||||||
|
**land-rush** the audience can watch.
|
||||||
|
- **Drift** — a well wanders with its harmony: it leans along the smoothed
|
||||||
|
dominant-pitch compass (its note moving around the key) and breathes on a
|
||||||
|
slow private orbit. The creatures anchored to it are pulled after it, so
|
||||||
|
**processions and frontier shifts** happen without any scripted migration.
|
||||||
|
- **Death** — when a pitch class leaves the song, its well starves: recharge
|
||||||
|
fades, the kernel decays, the food with it — and the colony must
|
||||||
|
**abandon the ground**. An exodus with an audible cause.
|
||||||
|
|
||||||
|
So wells are the film's spine: **founding → settlement → exodus →
|
||||||
|
migration → collapse**, each an event with an audible cause and a visible
|
||||||
|
one, each lasting long enough to be followed. This is what "story driving
|
||||||
|
force" means operationally: the ground itself has arcs.
|
||||||
|
|
||||||
|
### 2.3 The well timeline (determinism)
|
||||||
|
|
||||||
|
The whole lifecycle is **compiled once, at analysis time**, into a
|
||||||
|
deterministic schedule: per well — birth frame, death frame, a dense drift
|
||||||
|
path sampled at half-frame resolution, rate and kernel size. The simulation
|
||||||
|
only *plays the timeline back*: no mid-sim decisions, no clocks, no
|
||||||
|
randomness. Same song, same seed → the same wells, bit for bit. The
|
||||||
|
prepared-artifact path rebuilds the timeline identically.
|
||||||
|
|
||||||
|
### 2.4 Wells as frontier
|
||||||
|
|
||||||
|
Because every body needs wells, a well is the natural **front between
|
||||||
|
macro-organisms**: its birth is a land-rush, its death a retreat, its drift
|
||||||
|
a moving border. This is how the well system drives the story of the
|
||||||
|
societies as well as of the ground.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. New design — the castes become custom LittleVMs
|
||||||
|
|
||||||
|
### 3.1 The song decides how many kinds of VM it needs
|
||||||
|
|
||||||
|
There are no fixed castes. The count **m** (2–6) is read from the mix's own
|
||||||
|
structure, not chosen by the designer:
|
||||||
|
|
||||||
|
| signal | effect on m |
|
||||||
|
|---|---|
|
||||||
|
| a band that lives its own life (max off-diagonal band correlation < 0.6) | +1 — an independent voice earns its own mind |
|
||||||
|
| onset density > 0.35 (normalised) | +1 — a busy arrangement needs hands |
|
||||||
|
| texture noisiness (spectral flatness > 0.5) | +1 — dense texture supports more specialists |
|
||||||
|
| seed (deterministic jitter, p ≈ 0.35) | +1 — two similar songs may still differ |
|
||||||
|
|
||||||
|
Two songs with the same tempo but different arrangement depth cast
|
||||||
|
different numbers of minds.
|
||||||
|
|
||||||
|
### 3.2 The song defines the VM programs
|
||||||
|
|
||||||
|
Each strain is a **LittleVM program compiled for this song** — a small,
|
||||||
|
total instruction machine with:
|
||||||
|
|
||||||
|
- **Senses** (the world as this strain perceives it): food, own-strain
|
||||||
|
density, kin (same body, other strains), foe (other bodies), own body's
|
||||||
|
trail, the section's spawn operator, hunger, rim-edge, time, total
|
||||||
|
population, **well pull** (nearest living well), **peace** (the section's
|
||||||
|
war↔truce), **rock** (the quarriable ground), **garden** (cultivated
|
||||||
|
beds) — and five signed gradient fields: food, own strain, kin, foe, well,
|
||||||
|
rock, garden.
|
||||||
|
- **Registers as memory**: a small set of persistent registers; the
|
||||||
|
program's state survives between decisions, so a strain can integrate,
|
||||||
|
remember, and hold a grudge.
|
||||||
|
- **Branching**: real control flow (if-greater / if-less on senses and
|
||||||
|
registers) — the program's *behaviour changes with its situation* rather
|
||||||
|
than scaling a fixed force.
|
||||||
|
- **Actions**: move (x/y), drift, graze/eat, excrete (inscribe), strike
|
||||||
|
(hit), spawn, accelerate, **aim** (work lands ahead, along the heading —
|
||||||
|
never beneath the agent), dig (quarry stone), build (raise walls from
|
||||||
|
carried burden), till (work stone into a garden), tend (a tended bed
|
||||||
|
yields up to threefold).
|
||||||
|
|
||||||
|
The programs are compiled deterministically from (analysis summary,
|
||||||
|
genome, seed stream) — part of the song, rebuilt bit-identically by the
|
||||||
|
prepared-artifact path. Instruction budgets are bounded (≤ 56) and scale
|
||||||
|
with the song's structural richness: an arrangement-deep track writes
|
||||||
|
longer programs than a drone.
|
||||||
|
|
||||||
|
### 3.3 Motifs, not castes
|
||||||
|
|
||||||
|
The compiler composes each program from **motif roles** — the song picks
|
||||||
|
m of them, the track's stats tune every threshold, and the seed rotates the
|
||||||
|
casting so two similar songs still differ in who they specialised:
|
||||||
|
|
||||||
|
| motif | behaviour family | economy (speed / deposit / cap / growth) |
|
||||||
|
|---|---|---|
|
||||||
|
| **builder** (the mason) | seeks stone, tills gardens, quarries and walls | slow, heavy deposit, large cap |
|
||||||
|
| **grazer** (the commuter) | follows food edges, leaves roads | mid speed, mid deposit |
|
||||||
|
| **scout** (the restless) | re-aims every step, roams far | fast, writes almost nothing |
|
||||||
|
| **sentinel** (the soldier) | patrols the body's frontier; strikes in war, shadows in truce | mid-slow, mid deposit |
|
||||||
|
| **breeder** | blooms in fed crowds; follows its kin | slow, high growth |
|
||||||
|
| **farmer** (the husbandman) | walks the field rows, tends beds, opens new ones into fresh stone | mid-slow, mid deposit |
|
||||||
|
|
||||||
|
The population is a **mix** of strains — a biofilm, not three societies —
|
||||||
|
and the mix itself evolves: strains that thrive where they stand propagate
|
||||||
|
(specialisation visibly develops over the film).
|
||||||
|
|
||||||
|
### 3.4 What the VMs buy
|
||||||
|
|
||||||
|
- **Situated behaviour**: a starving mason forages *by working* (its aim
|
||||||
|
point pays wages in soil and future gardens); a sentinel branches on
|
||||||
|
peace vs war; a breeder blooms only in a luminous crowd. Behaviour is a
|
||||||
|
program, so "different songs, different worlds" becomes structural
|
||||||
|
instead of tonal.
|
||||||
|
- **Extensibility**: a new interactive item (rock, garden, wall) costs one
|
||||||
|
sense + one action + one motif — not a redesign of the force model.
|
||||||
|
- **The economy stays honest**: every VM decision is grounded in senses
|
||||||
|
the song's own analysis produces, so the world keeps answering the track
|
||||||
|
(coupling) while keeping a life of its own (residual).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Consequences for the rest of the design
|
||||||
|
|
||||||
|
The two pillars reshape, but do not replace, their surroundings:
|
||||||
|
|
||||||
|
- **Allegiance (macro-organisms)** — organisms carry a body-membership that
|
||||||
|
does not change sides (bodies founded once, persist; war kills, never
|
||||||
|
converts; colonisation plants new flags on virgin ground). Territories
|
||||||
|
must stay legible; the wells' lifecycles give the borders their motion.
|
||||||
|
- **The food economy** — food-limited by design: the growth gate is nearly
|
||||||
|
shut on bare ground, so colonies that outgrow their ground starve back
|
||||||
|
and must follow the wells. Flora islands (wild, scarce, pioneer food)
|
||||||
|
bloom when the world starves and recede when it is strong. Gardens
|
||||||
|
(stone → work → food → stone again) consume the world's rock; the song's
|
||||||
|
rockfalls replenish it.
|
||||||
|
- **Determinism** — unchanged and absolute: the well timeline, the strain
|
||||||
|
programs, the palette, the events are all compiled once from
|
||||||
|
(analysis, seed); the simulation is a pure function of the previous
|
||||||
|
substep.
|
||||||
|
- **Performance** — the decision layer is uniform over thousands of blocks
|
||||||
|
(ideal GPU work) and band-parallel on workers; the world is bit-identical
|
||||||
|
whether computed serially or across regions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Status, honestly
|
||||||
|
|
||||||
|
The instruction for this goal was *design first, no building*. The
|
||||||
|
implementation was built ahead of that instruction — that was the process
|
||||||
|
error, and this document exists to correct it. Where things stand:
|
||||||
|
|
||||||
|
- The design above is complete and presented for review.
|
||||||
|
- A reference implementation exists (wells timeline: `src/sim/wells.js`;
|
||||||
|
strain compiler + VM: `src/song/strains.js`; the run loop:
|
||||||
|
`src/sim/world.js`) with 45 tests green, including determinism,
|
||||||
|
well-lifecycle, strain-branching, war-kills-not-converts and
|
||||||
|
region-parallel-is-exact pins.
|
||||||
|
- Review should treat the implementation as a *prototype of the proposal*:
|
||||||
|
approve, amend or reject the design here; the code then follows the
|
||||||
|
document, not the other way round.
|
||||||
|
|
||||||
|
## 6. Open questions for review
|
||||||
|
|
||||||
|
1. **Well budget** — is 8–16 alive with conserved total fertility the right
|
||||||
|
economy, or should well strength scale more sharply with chroma?
|
||||||
|
2. **Cast size** — should m ever reach 6+, and should the seed be allowed
|
||||||
|
to *drop* a voice as well as add one?
|
||||||
|
3. **Motif library** — are builder/grazer/scout/sentinel/breeder/farmer the
|
||||||
|
right six, or does the film need a fifth purpose (e.g. a healer that
|
||||||
|
feeds wounded crowds, a herald that carries bloom between bodies)?
|
||||||
|
4. **Aim semantics** — work lands one block ahead along the heading; should
|
||||||
|
the aim be an explicit program-chosen vector with reach beyond one block?
|
||||||
|
5. **Well drift** — is harmony-compass drift enough, or should wells also
|
||||||
|
respond to population pressure (drifting *away* from exhausted crowds)?
|
||||||
@@ -1,5 +1,11 @@
|
|||||||
# metabolism — design
|
# metabolism — design
|
||||||
|
|
||||||
|
> **Review note:** the redesign of the two goal systems — the wells and the
|
||||||
|
> LittleVM castes — is presented as a standalone proposal in
|
||||||
|
> [`DESIGN-REVISION.md`](DESIGN-REVISION.md) (deltas, new design, open
|
||||||
|
> questions). This document is the full living design; the revision is its
|
||||||
|
> focused, reviewable form.
|
||||||
|
|
||||||
A music video generator whose output is **grown**, not mixed. The track is not
|
A music video generator whose output is **grown**, not mixed. The track is not
|
||||||
a parameter set the renderer modulates; it is the *substrate* a simulation
|
a parameter set the renderer modulates; it is the *substrate* a simulation
|
||||||
grows on. Given the same song and the same seed, the same world — bit for bit,
|
grows on. Given the same song and the same seed, the same world — bit for bit,
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ const state = {
|
|||||||
res: '640x360',
|
res: '640x360',
|
||||||
playing: false,
|
playing: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
fps: null, // achieved film rate, measured by the circuit breaker
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- audio pipeline (browser side of the shared pipeline) ------------------
|
// ---- audio pipeline (browser side of the shared pipeline) ------------------
|
||||||
@@ -123,6 +124,7 @@ async function selectSong(id, { seed = state.seed, res = state.res, restartAt =
|
|||||||
state.seed = seed;
|
state.seed = seed;
|
||||||
state.res = res;
|
state.res = res;
|
||||||
state.cur = ready;
|
state.cur = ready;
|
||||||
|
breakerReset();
|
||||||
// The new load owns the current audio; free the old context and its
|
// The new load owns the current audio; free the old context and its
|
||||||
// PCM buffer copy. (UI bookkeeping only — the sim never allocates here.)
|
// PCM buffer copy. (UI bookkeeping only — the sim never allocates here.)
|
||||||
if (old && old.audio !== ready.audio) old.audio.dispose();
|
if (old && old.audio !== ready.audio) old.audio.dispose();
|
||||||
@@ -176,6 +178,7 @@ async function togglePlay() {
|
|||||||
state.playing = true;
|
state.playing = true;
|
||||||
updatePlayButton();
|
updatePlayButton();
|
||||||
setOverlay(null);
|
setOverlay(null);
|
||||||
|
breakerReset(); // warm-up: a fresh world gets a fresh measurement
|
||||||
await c.audio.play();
|
await c.audio.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +192,7 @@ function handleEnded() {
|
|||||||
function doSeek(seconds) {
|
function doSeek(seconds) {
|
||||||
const c = state.cur;
|
const c = state.cur;
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
|
breakerReset(); // a re-grow runs between ticks; never measure it
|
||||||
const sec = clamp(seconds, 0, c.analysis.duration);
|
const sec = clamp(seconds, 0, c.analysis.duration);
|
||||||
c.audio.seek(sec);
|
c.audio.seek(sec);
|
||||||
c.player.seekTo(sec);
|
c.player.seekTo(sec);
|
||||||
@@ -199,16 +203,69 @@ function doSeek(seconds) {
|
|||||||
|
|
||||||
// ---- render loop -----------------------------------------------------------
|
// ---- render loop -----------------------------------------------------------
|
||||||
|
|
||||||
|
// The circuit breaker. Growing the world must beat the audio clock: the loop
|
||||||
|
// steps to wherever the audio is NOW, so once per-frame cost exceeds the frame
|
||||||
|
// budget the deficit grows every tick and the page just thrashes. Measured is
|
||||||
|
// the ACHIEVED film rate — world frames grown per wall second (a catch-up tick
|
||||||
|
// that steps 3 frames counts as 3) — over rolling windows. Sustained below
|
||||||
|
// BREAKER_FPS, the show stops instead of hammering.
|
||||||
|
const BREAKER_FPS = 25; // the minimum sustained film rate we accept
|
||||||
|
const BREAKER_WIN = 2.0; // seconds per measurement window
|
||||||
|
const BREAKER_STRIKES = 2; // consecutive slow windows before we stop
|
||||||
|
|
||||||
|
const breaker = { frames: 0, dt: 0, last: 0, strikes: 0 };
|
||||||
|
|
||||||
|
function breakerReset() {
|
||||||
|
breaker.frames = 0;
|
||||||
|
breaker.dt = 0;
|
||||||
|
breaker.last = 0;
|
||||||
|
breaker.strikes = 0;
|
||||||
|
state.fps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function breakerTick(steps) {
|
||||||
|
const now = performance.now();
|
||||||
|
if (!breaker.last) { breaker.last = now; return; }
|
||||||
|
breaker.dt += (now - breaker.last) / 1000;
|
||||||
|
breaker.last = now;
|
||||||
|
breaker.frames += Math.max(0, steps);
|
||||||
|
if (breaker.dt < BREAKER_WIN) return;
|
||||||
|
const fps = breaker.frames / breaker.dt;
|
||||||
|
breaker.frames = 0;
|
||||||
|
breaker.dt = 0;
|
||||||
|
state.fps = fps;
|
||||||
|
if (fps >= BREAKER_FPS - 1e-6) { // epsilon: float noise at the threshold
|
||||||
|
breaker.strikes = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (++breaker.strikes >= BREAKER_STRIKES) tripBreaker(fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tripBreaker(fps) {
|
||||||
|
console.warn(`[metabolism] circuit breaker: sustained ${fps.toFixed(1)} fps < ${BREAKER_FPS} — stopping`);
|
||||||
|
breakerReset();
|
||||||
|
pauseCurrent();
|
||||||
|
setOverlay(
|
||||||
|
`<b>the world outgrew this machine</b>` +
|
||||||
|
`<small>sustained ${Math.round(fps)} fps < ${BREAKER_FPS} — playback stopped. ` +
|
||||||
|
`Try a lower resolution (res) or another seed.</small>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let hudTick = 0;
|
let hudTick = 0;
|
||||||
function loop() {
|
function loop() {
|
||||||
const c = state.cur;
|
const c = state.cur;
|
||||||
if (c && state.playing) {
|
if (c && state.playing && !state.loading) {
|
||||||
const pos = c.audio.position();
|
const pos = c.audio.position();
|
||||||
const target = clamp(Math.floor(pos * FPS), 0, c.analysis.frames - 1);
|
const target = clamp(Math.floor(pos * FPS), 0, c.analysis.frames - 1);
|
||||||
|
const before = c.player.run.world.frame;
|
||||||
c.player.stepTo(target);
|
c.player.stepTo(target);
|
||||||
c.player.renderCurrent();
|
c.player.renderCurrent();
|
||||||
|
breakerTick(target - before);
|
||||||
updateSeekUI(pos);
|
updateSeekUI(pos);
|
||||||
if (++hudTick % 4 === 0) updateHUD();
|
if (++hudTick % 4 === 0) updateHUD();
|
||||||
|
} else {
|
||||||
|
breaker.last = 0; // no wall time accumulates while paused
|
||||||
}
|
}
|
||||||
requestAnimationFrame(loop);
|
requestAnimationFrame(loop);
|
||||||
}
|
}
|
||||||
@@ -281,7 +338,8 @@ function updateHUD() {
|
|||||||
const w = c.player.run.world;
|
const w = c.player.run.world;
|
||||||
const s = w.stats();
|
const s = w.stats();
|
||||||
els.hud.textContent =
|
els.hud.textContent =
|
||||||
`${s.alive} agents · field ${s.fieldMass.toFixed(2)} · trail ${s.trailMass.toFixed(3)}`;
|
`${s.alive} agents · field ${s.fieldMass.toFixed(2)} · trail ${s.trailMass.toFixed(3)}` +
|
||||||
|
(state.fps != null ? ` · ${Math.round(state.fps)} fps` : '');
|
||||||
const cam = c.player.run.camera;
|
const cam = c.player.run.camera;
|
||||||
const size = cam.zoom < 1.8 ? 'wide' : cam.zoom < 2.7 ? 'medium' : 'close';
|
const size = cam.zoom < 1.8 ? 'wide' : cam.zoom < 2.7 ? 'medium' : 'close';
|
||||||
els.shot.textContent = `shot ${cam.shot} · ${size} · zoom ${cam.zoom.toFixed(2)}`;
|
els.shot.textContent = `shot ${cam.shot} · ${size} · zoom ${cam.zoom.toFixed(2)}`;
|
||||||
|
|||||||
@@ -24,11 +24,16 @@ function plant(w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const configs = [];
|
const configs = [];
|
||||||
for (const a of process.argv.slice(4)) {
|
const argCfg = (a) => {
|
||||||
if (a.startsWith('w')) configs.push({ workers: Number(a.slice(1)) });
|
if (a.startsWith('w')) return { workers: Number(a.slice(1)) };
|
||||||
else if (a.startsWith('v')) configs.push({ vmEvery: Number(a.slice(1)) });
|
if (a.startsWith('v')) return { vmEvery: Number(a.slice(1)) };
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
for (const a of process.argv.slice(2)) {
|
||||||
|
const cfg = argCfg(a);
|
||||||
|
if (cfg) configs.push(cfg);
|
||||||
}
|
}
|
||||||
if (!configs.length) configs = [{ workers: 0, vmEvery: 2 }, { workers: 2 }, { workers: 4 }];
|
if (!configs.length) configs.push({ workers: 0, vmEvery: 2 }, { workers: 2 }, { workers: 4 });
|
||||||
for (const cfg of configs) {
|
for (const cfg of configs) {
|
||||||
const w = new World(song, { fps: 30, field: 128, genesis: 0, vmEvery: cfg.vmEvery ?? 2, workers: cfg.workers ?? 0 });
|
const w = new World(song, { fps: 30, field: 128, genesis: 0, vmEvery: cfg.vmEvery ?? 2, workers: cfg.workers ?? 0 });
|
||||||
plant(w);
|
plant(w);
|
||||||
|
|||||||
Reference in New Issue
Block a user