Add lock-file load and stable ID assignment
Lock-file determinism guarantees re-bakes do not renumber existing tiles. Atlas_id mismatch is a hard-fail to prevent silent corruption. Deleted tile IDs move to deleted[] and are not recycled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
75
tests/lock.test.js
Normal file
75
tests/lock.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const path = require('node:path');
|
||||
const { loadLock, assignIds, serializeLock } = require('../src/lock');
|
||||
|
||||
const EXISTING_LOCK = path.resolve(__dirname, 'fixtures/lock-existing.json');
|
||||
|
||||
test('lock: load existing', () => {
|
||||
const lock = loadLock(EXISTING_LOCK);
|
||||
assert.strictEqual(lock.atlas_id, 'small_demo');
|
||||
assert.strictEqual(lock.bindings.grass, 1);
|
||||
assert.strictEqual(lock.bindings.stone, 2);
|
||||
assert.strictEqual(lock.next_id, 3);
|
||||
});
|
||||
|
||||
test('lock: load nonexistent returns null', () => {
|
||||
const lock = loadLock('/nonexistent/path.json');
|
||||
assert.strictEqual(lock, null);
|
||||
});
|
||||
|
||||
test('lock: assign IDs to fresh sources', () => {
|
||||
const sources = [
|
||||
{ name: 'apple' },
|
||||
{ name: 'banana' },
|
||||
{ name: 'cherry' },
|
||||
];
|
||||
const { tiles, lock } = assignIds(sources, null, 'fresh_atlas');
|
||||
assert.strictEqual(tiles[0].id, 1);
|
||||
assert.strictEqual(tiles[1].id, 2);
|
||||
assert.strictEqual(tiles[2].id, 3);
|
||||
assert.strictEqual(lock.atlas_id, 'fresh_atlas');
|
||||
assert.strictEqual(lock.next_id, 4);
|
||||
assert.deepStrictEqual(lock.deleted, []);
|
||||
});
|
||||
|
||||
test('lock: preserve existing bindings on re-bake', () => {
|
||||
const existing = loadLock(EXISTING_LOCK);
|
||||
const sources = [
|
||||
{ name: 'grass' },
|
||||
{ name: 'stone' },
|
||||
{ name: 'water' }, // new
|
||||
];
|
||||
const { tiles, lock } = assignIds(sources, existing, 'small_demo');
|
||||
const idByName = Object.fromEntries(tiles.map(t => [t.name, t.id]));
|
||||
assert.strictEqual(idByName.grass, 1); // preserved
|
||||
assert.strictEqual(idByName.stone, 2); // preserved
|
||||
assert.strictEqual(idByName.water, 3); // new
|
||||
assert.strictEqual(lock.next_id, 4);
|
||||
});
|
||||
|
||||
test('lock: deleted tiles move to deleted[]', () => {
|
||||
const existing = loadLock(EXISTING_LOCK);
|
||||
const sources = [
|
||||
{ name: 'grass' },
|
||||
// stone removed
|
||||
];
|
||||
const { lock } = assignIds(sources, existing, 'small_demo');
|
||||
assert.deepStrictEqual(lock.deleted, [2]);
|
||||
assert.strictEqual(lock.bindings.stone, undefined);
|
||||
assert.strictEqual(lock.bindings.grass, 1);
|
||||
});
|
||||
|
||||
test('lock: atlas_id mismatch -> error', () => {
|
||||
const existing = loadLock(EXISTING_LOCK);
|
||||
assert.throws(
|
||||
() => assignIds([{ name: 'grass' }], existing, 'WRONG_ID'),
|
||||
/atlas_id mismatch/,
|
||||
);
|
||||
});
|
||||
|
||||
test('lock: serialize is JSON with 2-space indent', () => {
|
||||
const lock = { atlas_id: 'x', bindings: { a: 1 }, deleted: [], next_id: 2 };
|
||||
const s = serializeLock(lock);
|
||||
assert.ok(s.includes(' "atlas_id"'));
|
||||
});
|
||||
Reference in New Issue
Block a user