105 lines
5.0 KiB
JavaScript
105 lines
5.0 KiB
JavaScript
const test = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const data = require("../assets/scripts/pages/princess-lima-data.js");
|
|
const state = require("../assets/scripts/pages/princess-lima-state.js");
|
|
const systems = require("../assets/scripts/pages/princess-lima-systems.js");
|
|
|
|
test("movement is responsive and diagonal speed is normalized", () => {
|
|
let velocity = { x: 0, y: 0 };
|
|
for (let frame = 0; frame < 120; frame += 1) {
|
|
velocity = systems.approachVelocity(velocity.x, velocity.y, 1, 1, 16, false);
|
|
}
|
|
assert.equal(Math.round(Math.hypot(velocity.x, velocity.y)), 176);
|
|
for (let frame = 0; frame < 20; frame += 1) {
|
|
velocity = systems.approachVelocity(velocity.x, velocity.y, 0, 0, 16, false);
|
|
}
|
|
assert.deepEqual(velocity, { x: 0, y: 0 });
|
|
});
|
|
|
|
test("all named spawns are collision-safe and invalid positions recover", () => {
|
|
const current = state.fresh("Ada", "azure");
|
|
Object.entries(data.MAPS).forEach(([regionId, map]) => {
|
|
Object.entries(map.spawns).forEach(([spawnId, spawn]) => {
|
|
assert.equal(systems.isSafePosition(current, regionId, spawn.x, spawn.y), true, `${regionId}:${spawnId}`);
|
|
});
|
|
});
|
|
const recovered = systems.nearestSafeSpawn(current, "village", 100, 100);
|
|
assert.equal(systems.isSafePosition(current, recovered.region, recovered.x, recovered.y), true);
|
|
});
|
|
|
|
test("quest progression grants rewards once and unlocks routes", () => {
|
|
let current = state.fresh("Ada", "azure");
|
|
current = systems.completeQuest(current, "aftermath");
|
|
assert.equal(systems.quantity(current, "village_sword"), 1);
|
|
current = systems.progressQuest(current, "village_defence", 3);
|
|
assert.equal(current.quests.village_defence.status, "complete");
|
|
assert.equal(current.unlockedRoutes.includes("village_defended"), true);
|
|
const tonics = systems.quantity(current, "healing_tonic");
|
|
current = systems.completeQuest(current, "village_defence");
|
|
assert.equal(systems.quantity(current, "healing_tonic"), tonics);
|
|
});
|
|
|
|
test("optional quests remain independent of chapter progression", () => {
|
|
let current = state.fresh("Ada", "azure");
|
|
current = systems.startQuest(current, "healer_herbs");
|
|
current = systems.completeQuest(current, "healer_herbs");
|
|
assert.equal(current.quests.healer_herbs.status, "complete");
|
|
assert.equal(current.chapter, 1);
|
|
});
|
|
|
|
test("inventory caps stacks, protects quest items and healing consumes safely", () => {
|
|
let current = state.fresh("Ada", "azure");
|
|
current = systems.addItem(current, "healing_tonic", 30);
|
|
assert.equal(systems.quantity(current, "healing_tonic"), 9);
|
|
current = systems.addItem(current, "prison_key", 1);
|
|
assert.equal(systems.removeItem(current, "prison_key", 1).removed, false);
|
|
current.health = 30;
|
|
const result = systems.useItem(current, "healing_tonic");
|
|
assert.equal(result.used, true);
|
|
assert.equal(result.state.health, 70);
|
|
assert.equal(systems.quantity(result.state, "healing_tonic"), 8);
|
|
});
|
|
|
|
test("combat applies defence, invulnerability, defeat and checkpoint respawn", () => {
|
|
let current = systems.addItem(state.fresh("Ada", "azure"), "buckler", 1);
|
|
const hit = systems.damage(current, 10, 2000, 0);
|
|
assert.equal(hit.amount, 8);
|
|
assert.equal(hit.state.health, 92);
|
|
assert.equal(systems.damage(hit.state, 10, 2100, 2000).hit, false);
|
|
const defeated = systems.damage({ ...hit.state, health: 3 }, 20, 4000, 0);
|
|
assert.equal(defeated.defeated, true);
|
|
const respawned = systems.respawn(defeated.state);
|
|
assert.equal(respawned.region, "village");
|
|
assert.ok(respawned.health >= 50);
|
|
});
|
|
|
|
test("puzzles unlock chapter routes and cannot reward twice", () => {
|
|
let current = state.fresh("Ada", "azure");
|
|
current = systems.solvePuzzle(current, "forest_stones");
|
|
assert.equal(current.solvedPuzzles.includes("forest_stones"), true);
|
|
assert.equal(current.unlockedRoutes.includes("guide_found"), true);
|
|
const boots = systems.quantity(current, "trail_boots");
|
|
current = systems.solvePuzzle(current, "forest_stones");
|
|
assert.equal(systems.quantity(current, "trail_boots"), boots);
|
|
});
|
|
|
|
test("boss phases and victories are deterministic", () => {
|
|
assert.equal(systems.bossPhase(48, 48, 3), 1);
|
|
assert.equal(systems.bossPhase(24, 48, 3), 2);
|
|
assert.equal(systems.bossPhase(10, 48, 3), 3);
|
|
let current = state.fresh("Ada", "azure");
|
|
current = systems.recordBoss(current, "malrec");
|
|
assert.equal(current.defeatedBosses.includes("malrec"), true);
|
|
assert.equal(current.unlockedRoutes.includes("malrec_defeated"), true);
|
|
assert.equal(current.story, "rescue");
|
|
});
|
|
|
|
test("complete story path reaches the Princess Lima rescue state", () => {
|
|
let current = state.fresh("Ada", "azure");
|
|
["aftermath", "village_defence", "find_guide", "ruins_light", "wolf_miniboss", "repair_bridge", "stone_guardian", "free_scout", "free_prisoners", "break_wards", "defeat_malrec"]
|
|
.forEach((id) => { current = systems.completeQuest(current, id); });
|
|
assert.equal(current.chapter, 4);
|
|
assert.equal(current.unlockedRoutes.includes("wards_broken"), true);
|
|
assert.equal(current.unlockedRoutes.includes("malrec_defeated"), true);
|
|
});
|