Validates atlas declarations against the registry, resolves tile names per atlas, bounds-checks each cell, and emits a flat row-major (y*w + x) GID array per layer. Roof block is only serialised when at least one roof directive was present so empty maps stay compact.
134 lines
4.9 KiB
JavaScript
134 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const { parseDsl } = require('../src/dsl-parser');
|
|
const { loadAtlasDirs } = require('../src/atlas-loader');
|
|
const { buildMap } = require('../src/builder');
|
|
const { encodeGid } = require('../src/gid');
|
|
|
|
const FIX_ATLAS = path.join(__dirname, 'fixtures', 'atlases');
|
|
const { registry } = loadAtlasDirs([FIX_ATLAS]);
|
|
|
|
function build(src, opts = {}) {
|
|
const ast = parseDsl(src);
|
|
return buildMap(ast, registry, opts);
|
|
}
|
|
|
|
test('buildMap: minimal map produces schema-v2 shape', () => {
|
|
const map = build('size 2 2\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass\n', { defaultId: 'fallback' });
|
|
assert.equal(map.schema_version, 2);
|
|
assert.equal(map.id, 'fallback');
|
|
assert.deepEqual(map.size, { w: 2, h: 2 });
|
|
assert.deepEqual(map.atlases, ['atlas_a']);
|
|
assert.equal(map.layers.surface.tiles.length, 4);
|
|
// Cell (0,0) is index 0 (row-major: y*w + x)
|
|
assert.equal(map.layers.surface.tiles[0], encodeGid(0, 1, 0));
|
|
assert.equal(map.layers.surface.tiles[1], 0); // (1,0) empty
|
|
});
|
|
|
|
test('buildMap: AST id wins over defaultId', () => {
|
|
const map = build('id from_dsl\nsize 1 1\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass\n', { defaultId: 'fallback' });
|
|
assert.equal(map.id, 'from_dsl');
|
|
});
|
|
|
|
test('buildMap: fill produces correct row-major GID array', () => {
|
|
const src = 'size 3 3\natlas atlas_a\nlayer surface\n fill 0 0 2 2 atlas_a:stone\n';
|
|
const map = build(src, { defaultId: 'test' });
|
|
const gid = encodeGid(0, 2, 0);
|
|
for (let i = 0; i < 9; i++) assert.equal(map.layers.surface.tiles[i], gid, `cell ${i}`);
|
|
});
|
|
|
|
test('buildMap: rotation encodes into GID', () => {
|
|
const src = 'size 1 1\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass rot 3\n';
|
|
const map = build(src, { defaultId: 't' });
|
|
assert.equal(map.layers.surface.tiles[0], encodeGid(0, 1, 3));
|
|
});
|
|
|
|
test('buildMap: multiple atlases get correct atlas_index', () => {
|
|
const src = 'size 1 1\natlas atlas_a\natlas atlas_b\nlayer surface\n set 0 0 atlas_b:water\n';
|
|
const map = build(src, { defaultId: 't' });
|
|
// atlas_b is index 1 in declaration order
|
|
assert.equal(map.layers.surface.tiles[0], encodeGid(1, 1, 0));
|
|
});
|
|
|
|
test('buildMap: roof block produces flat array of size w*h', () => {
|
|
const src = 'size 2 2\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass\nroof\n set 1 1 1\n';
|
|
const map = build(src, { defaultId: 't' });
|
|
assert.equal(map.roof.length, 4);
|
|
assert.equal(map.roof[3], 1);
|
|
assert.equal(map.roof[0], 0);
|
|
});
|
|
|
|
test('buildMap: empty layers are not serialised', () => {
|
|
const src = 'size 2 2\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass\n';
|
|
const map = build(src, { defaultId: 't' });
|
|
assert.deepEqual(Object.keys(map.layers), ['surface']);
|
|
assert.equal(map.roof, undefined);
|
|
});
|
|
|
|
test('buildMap: unknown atlas reference is an error', () => {
|
|
const src = 'size 1 1\natlas atlas_a\nlayer surface\n set 0 0 atlas_c:grass\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => err.message.match(/atlas_c.*not declared/) && err.line === 4
|
|
);
|
|
});
|
|
|
|
test('buildMap: undeclared atlas in DSL is an error', () => {
|
|
const src = 'size 1 1\natlas missing_atlas\nlayer surface\n set 0 0 missing_atlas:grass\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => !!err.message.match(/missing_atlas.*registry/)
|
|
);
|
|
});
|
|
|
|
test('buildMap: unknown tile name in atlas is an error', () => {
|
|
const src = 'size 1 1\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:nonexistent\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => err.message.match(/nonexistent/) && err.line === 4
|
|
);
|
|
});
|
|
|
|
test('buildMap: out-of-bounds set is an error', () => {
|
|
const src = 'size 4 4\natlas atlas_a\nlayer surface\n set 5 0 atlas_a:grass\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => err.message.match(/out of bounds/) && err.line === 4
|
|
);
|
|
});
|
|
|
|
test('buildMap: out-of-bounds fill is an error', () => {
|
|
const src = 'size 4 4\natlas atlas_a\nlayer surface\n fill 0 0 4 4 atlas_a:grass\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => err.message.match(/out of bounds/) && err.line === 4
|
|
);
|
|
});
|
|
|
|
test('buildMap: out-of-bounds roof set is an error', () => {
|
|
const src = 'size 2 2\natlas atlas_a\nlayer surface\n set 0 0 atlas_a:grass\nroof\n set 2 2 1\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => err.message.match(/out of bounds/) && err.line === 6
|
|
);
|
|
});
|
|
|
|
test('buildMap: no atlases declared is an error', () => {
|
|
const src = 'size 1 1\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: 't' }),
|
|
(err) => !!err.message.match(/atlases.*non-empty/)
|
|
);
|
|
});
|
|
|
|
test('buildMap: empty defaultId AND no DSL id is an error', () => {
|
|
const src = 'size 1 1\natlas atlas_a\n';
|
|
assert.throws(
|
|
() => build(src, { defaultId: '' }),
|
|
(err) => !!err.message.match(/id/)
|
|
);
|
|
});
|