Metabolism: decision-cadence (think slower than you move) + empty-block skip; flat typed-array bytecode; WebGPU decision-layer slice (experimental) + heavy-world benchmark
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
import { Rng } from '../rng.js';
|
||||
import { wellPos, wellEnv, buildKernel } from './wells.js';
|
||||
import {
|
||||
runProgram, SENSE_COUNT, GRAD_COUNT, ACTION_COUNT, REGS,
|
||||
runProgram, ensureFlat, SENSE_COUNT, GRAD_COUNT, ACTION_COUNT, REGS,
|
||||
S_FOOD, S_SELF, S_KIN, S_FOE, S_TRAIL, S_LAW, S_HUNGER, S_EDGE, S_TIME, S_TOTAL, S_WELL, S_PEACE, S_ROCK, S_GARDEN,
|
||||
G_GARDEN,
|
||||
G_FOOD, G_SELF, G_KIN, G_FOE, G_WELL, G_ROCK,
|
||||
@@ -48,6 +48,11 @@ export const MAX_BODIES = 8;
|
||||
// so a full cell writes a visible line in seconds).
|
||||
const DEPOSIT_SCALE = 0.012;
|
||||
|
||||
// The decision cadence: the strains think every VM_EVERY-th substep (their
|
||||
// decisions persist in the action buffer between thoughts). 2 = ~15 thoughts
|
||||
// per second of film at 5 substeps.
|
||||
const VM_EVERY = 2;
|
||||
|
||||
// Politics cadence (frames at the sim's fps). Allegiance never changes sides:
|
||||
// bodies are founded once and persist; war kills, it does not convert.
|
||||
const COLONIZE_EVERY = 60; // ~2 s: a blessed frontier settlement may found
|
||||
@@ -85,7 +90,7 @@ function buildBlockKernels(F, FC) {
|
||||
}
|
||||
|
||||
export class World {
|
||||
constructor(song, { fps = 30, field = FIELD, seed = song.seed, substeps = SUBSTEPS, genesis } = {}) {
|
||||
constructor(song, { fps = 30, field = FIELD, seed = song.seed, substeps = SUBSTEPS, genesis, vmEvery = VM_EVERY } = {}) {
|
||||
this.song = song;
|
||||
this.fps = fps;
|
||||
this.dt = 1 / fps;
|
||||
@@ -93,6 +98,7 @@ export class World {
|
||||
this.F = field;
|
||||
this.substeps = substeps;
|
||||
this.genome = song.genome;
|
||||
this.vmEvery = Math.max(1, Math.round(vmEvery));
|
||||
this.program = song.program;
|
||||
this.strains = song.strains;
|
||||
this.m = this.strains.length;
|
||||
@@ -298,6 +304,7 @@ export class World {
|
||||
};
|
||||
|
||||
this.frame = -1;
|
||||
this.substepCount = 0;
|
||||
this.pulses = [];
|
||||
this.totalPop = 0;
|
||||
this.moveAccum = 0;
|
||||
@@ -871,9 +878,13 @@ export class World {
|
||||
*/
|
||||
runStrains(regime) {
|
||||
const FC = this.FC;
|
||||
const fc = FC * FC;
|
||||
const blkRock = this.blkRock;
|
||||
const blkGarden = this.blkGarden;
|
||||
const fc = FC * FC;
|
||||
// The minds think slower than the world moves: programs run on a
|
||||
// fraction of the substeps (their decisions persist in the action
|
||||
// buffer), and empty ground thinks not at all.
|
||||
const decide = (this.substepCount % this.vmEvery) === 0;
|
||||
const m = this.m;
|
||||
const blkPop = this.blkPop;
|
||||
const blkTrail = this.blkTrail;
|
||||
@@ -903,13 +914,25 @@ export class World {
|
||||
const peace = clamp01((this.warmth + 1) / 2);
|
||||
const actFC = FC * FC;
|
||||
|
||||
const actBufAll = this.stAct;
|
||||
for (let b = 0; b < fc; b++) {
|
||||
const tot = blkTotal[b];
|
||||
if (tot < 1e-4) {
|
||||
// Empty ground: no mind, no force — just make sure no stale
|
||||
// decision lingers in the action buffer.
|
||||
if (decide) {
|
||||
for (let s = 0; s < m; s++) {
|
||||
const base = s * ACTION_COUNT * actFC + b;
|
||||
for (let k = 0; k < ACTION_COUNT; k++) actBufAll[base + k * actFC] = 0;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const ug = cU[b];
|
||||
const dg = cD[b];
|
||||
const lg = cL[b];
|
||||
const rg = cR[b];
|
||||
const r0 = blkR[b];
|
||||
const tot = blkTotal[b];
|
||||
const dom = domB[b];
|
||||
const domPres = dom >= 0 ? blkBody[dom][b] : 0;
|
||||
|
||||
@@ -927,6 +950,14 @@ export class World {
|
||||
const own = blkPop[s][b];
|
||||
const kinPres = dom >= 0 ? Math.max(0, blkBody[dom][b] - own) : 0;
|
||||
const foe = Math.max(0, tot - domPres);
|
||||
if (!decide) {
|
||||
// between decisions the strain coasts on its last one:
|
||||
// forces already sit in fxX/fxY, actions in the buffer.
|
||||
const syn = this.syn[s];
|
||||
const kinShare = dom >= 0 ? clamp01((blkBody[dom][b] - own) / Math.max(1e-6, syn.threshold)) : 0;
|
||||
synScratch[s * FC + b] = syn.base + (1 - syn.base) * kinShare;
|
||||
continue;
|
||||
}
|
||||
const nb = (blkPop[s][ug] + blkPop[s][dg] + blkPop[s][lg] + blkPop[s][rg]) / 4;
|
||||
|
||||
// ---- senses -------------------------------------------------
|
||||
@@ -984,7 +1015,8 @@ export class World {
|
||||
gg[G_GARDEN * 2] = Math.sign(blkGarden[rg] - blkGarden[lg]);
|
||||
gg[G_GARDEN * 2 + 1] = Math.sign(blkGarden[dg] - blkGarden[ug]);
|
||||
|
||||
runProgram(this.stMem, s * REGS * FC + b, ss, gg, act, strains[s].code);
|
||||
const flat = ensureFlat(strains[s]);
|
||||
runProgram(this.stMem, s * REGS * FC + b, ss, gg, act, flat._flatI, flat._flatF, flat._flatN);
|
||||
// Remember the aim: the next substep's work-senses look there.
|
||||
let nx = act[12] !== 0 ? Math.sign(act[12]) : Math.sign(act[0]);
|
||||
let ny = act[13] !== 0 ? Math.sign(act[13]) : Math.sign(act[1]);
|
||||
@@ -1029,6 +1061,7 @@ export class World {
|
||||
* Deterministic throughout: the only randomness is the static noise field.
|
||||
*/
|
||||
substep(regime) {
|
||||
this.substepCount++;
|
||||
const F = this.field;
|
||||
const FC = this.FC;
|
||||
const fc = FC * FC;
|
||||
|
||||
Reference in New Issue
Block a user