Remove conversions for old saves (t83)

This commit is contained in:
2026-10-29 21:59:40 -04:00
parent 78eeb86171
commit f88035a58f
6 changed files with 40 additions and 22 deletions
+2 -2
View File
@@ -392,8 +392,8 @@ Dev commands are pure simulation operations; the browser owns the panel and conf
- functions: resetDevConsole, enableDevConsole, devError, devName, devGiveable, devMaterialList, devDistance, devSuggestions, devHistory, devRemember, devRecall, devComplete, devMaterial, devCommand
- variables: devWorld, devLog, devFree, devSpeed, devPaused, devSteps, DEV_COMMANDS, DEV_MATERIALS
### src/sim/save.js (216 lines)
Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration.
### src/sim/save.js (201 lines)
Saving and loading a world (serializeWorld / deserializeWorld).
- functions: serializeWorld, deserializeWorld, startLoadWake, wakeLoadArea, wakeLoadNearBot, loadWakeTick
- variables: SAVE_VERSION, TOFF
+10
View File
@@ -4,6 +4,16 @@ What agents have done, newest first. Add an entry for every piece of work: the d
note or request it was for, what changed, the files touched, the tests run, and anything left
open or not verified. Keep each entry short; the git history has the details.
## 2026-10-29: t83 remove old-save conversions (Codex)
- Removed only spring, solid-pipe, single-material-filter and foreground-tree conversions
from `src/sim/save.js`; world generation and its spring random draws are untouched.
- Files: save.js, test/t_{load,weather,conveyors}.js, docs/PROJECT_MAP.md, tasks/t83*.
Three expectations failed first; updated obsolete spring/filter migration tests deliberately.
- All 41 headless suites pass; pixel `test/play.py shots/t83-play` passes with no console
errors. Sandbox lacked WebGPU; host GPU rerun passed. Inspected saving-progress.png and
10-after.png (saved/reloaded world). Map regenerated; nothing left open.
## 2026-10-29: t81 shared piece direction variants (Codex)
- `pieceV` accepts a direction and resolved code; `dragPlan` calls it for previews and
+1 -16
View File
@@ -1,4 +1,4 @@
// Saving and loading a world (serializeWorld / deserializeWorld) and old-save migration.
// Saving and loading a world (serializeWorld / deserializeWorld).
// ---------- Save / load ----------
const SAVE_VERSION = 2;
const TOFF = 64; // temperatures are saved with an offset so sub-zero snow and ice survive
@@ -60,7 +60,6 @@ function deserializeWorld(meta, buffers) {
mat.set(buffers.mat); life.set(buffers.life); flags.set(buffers.flags);
for (let i = 0; i < N; i++) {
if (mat[i] >= NMAT) mat[i] = EMPTY;
else if (mat[i] === SPRING) mat[i] = STONE; // springs are gone: older worlds keep plain rock there
}
blockP.set(buffers.blockP); blockV.set(buffers.blockV); filtersDirty = true;
for (let b = 0; b < NB; b++) {
@@ -81,22 +80,8 @@ function deserializeWorld(meta, buffers) {
}
s.open = open === true; rackState.set(b, s);
}
// pipes used to be solid copper blocks; now they are thin and let things past
for (let b = 0; b < NB; b++) if (blockP[b] === BP_PIPE) for (const i of blockCellList(b % BW, (b / BW) | 0)) if (mat[i] === PIPE) { mat[i] = EMPTY; part[i] = P_PIPE; }
for (const [b, m, inv] of meta.filters || []) if (isFilter(blockP[b])) filterCfg.set(b, filterRec({ m, inv }));
for (let b = 0; b < NB; b++) if (isFilter(blockP[b])) {
// the first filters kept one material in their block variant
if (!filterCfg.has(b)) filterCfg.set(b, filterRec({ m: [blockV[b] & 127], inv: 0 }));
blockV[b] &= 128;
}
if (buffers.bgm) { bgm.set(buffers.bgm); }
else {
// older saves kept trees in the foreground: move them behind
for (let i = 0; i < N; i++) {
const m = mat[i], y = (i / W) | 0;
if (m === LEAVES || (m === WOOD && (flags[i] & F_NAT) && y < surfY[i % W])) { bgm[i] = m; mat[i] = EMPTY; flags[i] = 0; }
}
}
const t16 = buffers.temp, off = meta.tOff || 0; for (let i = 0; i < N; i++) temp[i] = t16[i] / 16 - off;
saplings = (meta.saplings || []).map(([x, y, age, th, seed]) => ({ x, y, age, th, seed }));
findTreeBases();
+2 -2
View File
@@ -199,9 +199,9 @@ run('clearAllGhosts(); pasteGhost(flipBlueprint(copyRegion(120, 100, 123, 103)),
check('pasting it builds a filter with the same materials and mode', run('blockP[25*BW+34]') === run('BP_FILTER') && run('JSON.stringify(filterInfo(25*BW+34))') === JSON.stringify({ m: [run('CUORE'), run('FEORE')].sort((a, b) => a - b), inv: 1 }), run('JSON.stringify(filterInfo(25*BW+34))'));
run('globalThis.__s = serializeWorld(); deserializeWorld(__s.meta, __s.buffers);');
check('save and load keep filters, their materials, mode and direction', run('blockP[25*BW+30]') === run('BP_FILTER') && run('blockV[25*BW+30]') === 128 && run('filterInfo(25*BW+30).inv') === 1 && run('filterInfo(25*BW+30).m.length') === 2 && run('part[100*W+120]') === run('P_FILTER'));
// a save from before: one material kept in the block variant
// A retired single-material variant is no longer reconstructed on load.
run('__s.meta.filters = []; __s.buffers.blockV[25*BW+30] = 128 | SAND; deserializeWorld(__s.meta, __s.buffers);');
check('older filters keep the one material they had', run('JSON.stringify(filterInfo(25*BW+30))') === JSON.stringify({ m: [run('SAND')], inv: 0 }) && run('blockV[25*BW+30]') === 128);
check('loading does not reconstruct retired single-material filters', run('!filterCfg.has(25*BW+30) && blockV[25*BW+30] === (128 | SAND)'));
// the run of matching filters in a row
fresh();
run('for (let bx = 20; bx < 26; bx++) placeBlock(BP_FILTER, 128, bx, 25, true, { m: [SAND] }); placeBlock(BP_FILTER, 128, 26, 25, true, { m: [COAL] }); placeBlock(BP_FILTER, 0, 27, 25, true, { m: [COAL] });');
+23
View File
@@ -51,6 +51,29 @@ for (const [label, doctor, clean] of [
})()`));
}
// Retired save formats are no longer translated into current construction or trees.
flatWorld(run, 256, 256, 200);
run(`
placeBlock(BP_FILTER, 128, 5, 10, true, { m: [SAND, COAL], inv: 1 });
placeBlock(BP_PIPE, 0, 8, 10, true);
bgm[100 * W + 40] = LEAVES;
globalThis.currentSave = serializeWorld();
deserializeWorld(currentSave.meta, currentSave.buffers);
`);
check('current filter settings and direction survive loading', run('blockV[10 * BW + 5] === 128 && JSON.stringify(filterCfg.get(10 * BW + 5)) === JSON.stringify(filterRec({ m: [SAND, COAL], inv: 1 }))'));
check('current thin pipes and background trees survive loading', run('part[40 * W + 32] === P_PIPE && mat[40 * W + 32] === EMPTY && bgm[100 * W + 40] === LEAVES'));
run(`
currentSave.meta.filters = [];
currentSave.buffers.blockV[10 * BW + 5] = 128 | SAND;
delete currentSave.buffers.bgm;
currentSave.buffers.mat[100 * W + 40] = LEAVES;
currentSave.buffers.mat[110 * W + 40] = WOOD;
currentSave.buffers.flags[110 * W + 40] = F_NAT;
deserializeWorld(currentSave.meta, currentSave.buffers);
`);
check('loading does not invent a filter setting from its old block variant', run('!filterCfg.has(10 * BW + 5) && blockV[10 * BW + 5] === (128 | SAND)'));
check('loading does not move foreground trees into a missing background layer', run('mat[100 * W + 40] === LEAVES && mat[110 * W + 40] === WOOD && flags[110 * W + 40] === F_NAT && bgm[100 * W + 40] === EMPTY && bgm[110 * W + 40] === EMPTY'));
flatWorld(run, 1024, 256, 200);
run(`
bot.x = 510; bot.y = 170;
+2 -2
View File
@@ -8,9 +8,9 @@ const rainInAir = (m) => run(`flyers.filter(f => f.rain && f.m === ${m}).length`
run('generateWorld(777, 1024, 640, null)');
check('new worlds have no springs', count(run, run('SPRING')) === 0);
check('and the same seed still makes the same land', run('surfY[512]') > 0 && run('!!dock()'));
// an older save with a spring in it: the spring becomes plain rock
// Loading no longer converts retired spring cells to rock.
run('globalThis.__s = serializeWorld(); __s.buffers.mat[300 * W + 500] = SPRING; deserializeWorld(__s.meta, __s.buffers);');
check('springs in older worlds turn into plain rock', run('mat[300 * W + 500]') === run('STONE') && count(run, run('SPRING')) === 0);
check('loading leaves retired spring cells unchanged', run('mat[300 * W + 500]') === run('SPRING') && count(run, run('SPRING')) === 1);
// steam in the open sky rises into the clouds instead of condensing
flatWorld(run, 320, 200, 120);