109 lines
5.0 KiB
JavaScript
Executable File
109 lines
5.0 KiB
JavaScript
Executable File
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const data = require("../assets/scripts/pages/princess-lima-v2-data.js");
|
|
const state = require("../assets/scripts/pages/princess-lima-v2-state.js");
|
|
const systems = require("../assets/scripts/pages/princess-lima-v2-systems.js");
|
|
|
|
const root = path.resolve(__dirname, "..");
|
|
const source = (file) => fs.readFileSync(path.join(root, file), "utf8");
|
|
|
|
test("v2 content contracts and all dialogue graphs validate", () => {
|
|
assert.deepEqual(data.validateAll(), []);
|
|
assert.equal(data.SCHEMA_VERSION, 2);
|
|
assert.equal(Object.keys(data.DIALOGUES).length >= 8, true);
|
|
Object.values(data.DIALOGUES).forEach((dialogue) => assert.equal(data.validateDialogue(dialogue), true));
|
|
});
|
|
|
|
test("all nine regions are genuine Tiled maps with authored runtime layers", () => {
|
|
assert.equal(data.MAP_IDS.length, 9);
|
|
data.MAP_IDS.forEach((region) => {
|
|
const map = JSON.parse(source(`assets/maps/princess-lima/${region}.json`));
|
|
assert.equal(map.type, "map");
|
|
assert.equal(map.tilewidth, 32);
|
|
assert.equal(map.tileheight, 32);
|
|
assert.deepEqual(systems.validateMap(map, data.MAP_IDS), [], region);
|
|
assert.deepEqual(map.layers.map((layer) => layer.name), systems.REQUIRED_MAP_LAYERS);
|
|
assert.ok(map.layers.find((layer) => layer.name === "Base terrain").data.some((tile) => tile > 0));
|
|
});
|
|
});
|
|
|
|
test("the runtime loads Tiled maps rather than regional backdrop frames", () => {
|
|
const maps = source("assets/scripts/pages/princess-lima-v2-maps.js");
|
|
const scenes = source("assets/scripts/pages/princess-lima-v2-scenes.js");
|
|
assert.match(maps, /make\.tilemap/);
|
|
assert.match(maps, /createLayer/);
|
|
assert.match(scenes, /tilemapTiledJSON/);
|
|
assert.doesNotMatch(maps, /regional-style-atlas|REGION_FRAME/);
|
|
});
|
|
|
|
test("character animation, cinematic dialogue and eight enemy roles are present", () => {
|
|
const roles = new Set(Object.values(data.ENEMIES).filter((enemy) => !enemy.boss).map((enemy) => enemy.role));
|
|
["melee", "shield", "ranged", "fast", "ambush", "caster", "flying", "support", "elite"].forEach((role) => assert.ok(roles.has(role)));
|
|
assert.ok(fs.existsSync(path.join(root, "assets/images/play/princess-lima/actor-atlas-v2.png")));
|
|
assert.ok(fs.existsSync(path.join(root, "assets/images/play/princess-lima/portrait-atlas-v2.png")));
|
|
const ui = source("assets/scripts/pages/princess-lima-v2-ui.js");
|
|
assert.match(ui, /lima-cinematic-dialogue/);
|
|
assert.match(ui, /aria-label/);
|
|
assert.match(ui, /Conversation history/);
|
|
});
|
|
|
|
test("the standalone page loads only the local v2 runtime and accessibility surface", () => {
|
|
const rpg = source("play/rpg.org");
|
|
assert.match(rpg, /easystar-0\.4\.4\.min\.js/);
|
|
assert.match(rpg, /princess-lima-v2-game\.js/);
|
|
assert.match(rpg, /data-lima-screenreader/);
|
|
assert.match(rpg, /Block Shift/);
|
|
assert.doesNotMatch(rpg, /princess-lima-game\.js\?v=/);
|
|
assert.doesNotMatch(rpg, /https?:\/\//);
|
|
});
|
|
|
|
test("v1 progress migrates to v2 without overwriting legacy fields", () => {
|
|
const legacy = {
|
|
version: 1,
|
|
player: { name: "Rowan", appearance: "pine" },
|
|
chapter: 4,
|
|
region: "fortressInterior",
|
|
quests: { break_wards: { status: "complete", count: 3, rewarded: true } },
|
|
inventory: [{ id: "tempered_sword", quantity: 1 }, { id: "bad_item", quantity: 5 }],
|
|
defeatedBosses: ["stone_guardian", "unknown"],
|
|
settings: { subtitles: false, highContrast: true },
|
|
introSeen: true
|
|
};
|
|
const migrated = state.migrateLegacy(legacy);
|
|
assert.equal(migrated.version, 2);
|
|
assert.equal(migrated.player.name, "Rowan");
|
|
assert.equal(migrated.quests.break_wards.status, "complete");
|
|
assert.equal(migrated.inventory.some((item) => item.id === "bad_item"), false);
|
|
assert.equal(migrated.position.spawn, "frontHall");
|
|
assert.equal(migrated.settings.highContrast, true);
|
|
});
|
|
|
|
test("combat phases, hitboxes and enemy states are deterministic", () => {
|
|
assert.equal(systems.attackPhase(0, "charged"), "windup");
|
|
assert.equal(systems.attackPhase(520, "charged"), "active");
|
|
assert.equal(systems.attackPhase(670, "charged"), "recovery");
|
|
const north = systems.attackHitbox("north", 100, 100, "light1");
|
|
assert.ok(north.y + north.height <= 100);
|
|
const enemy = data.ENEMIES.raider;
|
|
assert.equal(systems.nextEnemyState("patrol", {
|
|
distance: 30, homeDistance: 0, spec: enemy, health: 5, leash: 190,
|
|
stunned: false, telegraphDone: true, attackDone: true, cooldownDone: true
|
|
}), "telegraph");
|
|
});
|
|
|
|
test("Malrec is demanding but leaves readable counterplay", () => {
|
|
const malrec = data.ENEMIES.malrec;
|
|
assert.equal(malrec.phases, 3);
|
|
assert.ok(malrec.health <= 52);
|
|
assert.ok(malrec.damage <= 13);
|
|
assert.ok(malrec.telegraph >= 750);
|
|
assert.ok(malrec.cooldown >= 1500);
|
|
const scenes = source("assets/scripts/pages/princess-lima-v2-scenes.js");
|
|
assert.match(scenes, /phase \+ 1/);
|
|
assert.match(scenes, /state\.health \+ 25/);
|
|
assert.match(scenes, /defences_disabled/);
|
|
assert.match(scenes, /prisoners_freed/);
|
|
});
|