Files
org_web/tests/archive-world-systems.test.cjs
gitea-actions 2f125f32ae
All checks were successful
Build Org Website / build (push) Successful in 38s
Refine Archive World navigation and gate progression
2026-07-30 10:02:58 +01:00

128 lines
5.7 KiB
JavaScript
Executable File

const test = require("node:test");
const assert = require("node:assert/strict");
const data = require("../assets/scripts/pages/archive-world-data.js");
const state = require("../assets/scripts/pages/archive-world-state.js");
const systems = require("../assets/scripts/pages/archive-world-systems.js");
test("movement normalization prevents faster diagonals", () => {
const straight = systems.normalizedVector(1, 0, 175);
const diagonal = systems.normalizedVector(1, 1, 175);
assert.equal(Math.round(Math.hypot(straight.x, straight.y)), 175);
assert.equal(Math.round(Math.hypot(diagonal.x, diagonal.y)), 175);
});
test("movement acceleration is frame-rate aware, responsive, and speed capped", () => {
const movement = data.AREAS.town.movement;
const first = systems.approachVelocity(0, 0, 1, 1, movement, 16, false);
assert.ok(Math.hypot(first.x, first.y) > 0);
assert.ok(Math.hypot(first.x, first.y) < movement.speed);
let velocity = { x: 0, y: 0 };
for (let frame = 0; frame < 120; frame += 1) {
velocity = systems.approachVelocity(velocity.x, velocity.y, 1, 1, movement, 16, false);
}
assert.equal(Math.round(Math.hypot(velocity.x, velocity.y)), movement.speed);
for (let frame = 0; frame < 20; frame += 1) {
velocity = systems.approachVelocity(velocity.x, velocity.y, 0, 0, movement, 16, false);
}
assert.deepEqual(velocity, { x: 0, y: 0 });
});
test("landmark discoveries deduplicate and complete at eight", () => {
let current = state.fresh("Ada", "brass");
current = systems.discover(current, "gate");
current = systems.discover(current, "gate");
assert.deepEqual(current.discovered, ["gate"]);
data.LANDMARK_IDS.forEach((id) => { current = systems.discover(current, id); });
assert.equal(current.complete, true);
});
test("quest progression and completion grant each reward once", () => {
let current = state.fresh("Ada", "brass");
current = systems.startQuest(current, "gate");
assert.equal(current.quests.gate.status, "active");
current = systems.completeQuest(current, "gate");
const xp = current.xp;
assert.equal(current.quests.gate.status, "complete");
assert.deepEqual(current.sigils, ["gate"]);
assert.equal(current.inventory.some((item) => item.id === "lantern"), true);
current = systems.completeQuest(current, "gate");
assert.equal(current.xp, xp);
assert.deepEqual(current.sigils, ["gate"]);
});
test("inventory collection caps stacks and consumables heal", () => {
let current = state.fresh("Ada", "brass");
current = systems.addItem(current, "ink_vial", 30);
assert.equal(systems.itemQuantity(current, "ink_vial"), 9);
current.health = 30;
const used = systems.consumeItem(current, "ink_vial");
assert.equal(used.used, true);
assert.equal(used.state.health, 65);
assert.equal(systems.itemQuantity(used.state, "ink_vial"), 8);
});
test("damage respects invulnerability and assist, defeat preserves progress", () => {
let current = state.fresh("Ada", "brass");
current.sigils = ["gate"];
const assisted = state.normalize({ ...current, settings: { ...current.settings, assist: true } });
const first = systems.takeDamage(assisted, 20, 2000, 0);
assert.equal(first.state.health, 90);
const ignored = systems.takeDamage(first.state, 20, 2200, 2000);
assert.equal(ignored.hit, false);
const defeated = systems.takeDamage({ ...first.state, health: 5, settings: { ...first.state.settings, assist: false } }, 20, 5000, 0);
assert.equal(defeated.defeated, true);
const respawned = systems.respawn(defeated.state);
assert.deepEqual(respawned.sigils, ["gate"]);
assert.equal(respawned.health >= 50, true);
});
test("XP levels, boss completion and sigil story stage restore correctly", () => {
let current = state.fresh("Ada", "brass");
current = systems.grantXp(current, 200);
assert.equal(current.level, 3);
assert.equal(current.maxHealth, 120);
current = systems.recordBoss(current, "sentinel");
assert.equal(current.defeatedBosses.includes("sentinel"), true);
assert.equal(current.quests.workshop.status, "complete");
data.LANDMARK_IDS.forEach((id) => {
if (!current.sigils.includes(id)) current = systems.completeQuest(current, id);
});
assert.equal(current.sigils.length, 8);
assert.equal(current.story.stage, "vault");
current = systems.recordBoss(current, "redactor");
assert.equal(current.story.stage, "postgame");
});
test("invalid collision positions resolve to a named safe spawn", () => {
assert.equal(systems.isSafe("town", 200, 150), false);
const safe = systems.nearestSafeSpawn("town", 200, 150);
assert.equal(systems.isSafe(safe.area, safe.x, safe.y), true);
assert.ok(["gate", "square", "library", "study", "inn", "workshop", "playroom", "museum", "garden"].includes(safe.spawn));
});
test("all declared safe spawns and enemy homes are outside collision geometry", () => {
Object.entries(data.AREAS).forEach(([areaId, area]) => {
Object.entries(area.safeSpawns).forEach(([spawnId, spawn]) => {
assert.equal(systems.isSafe(areaId, spawn.x, spawn.y), true, `${areaId}:${spawnId}`);
});
area.enemies.forEach((entry) => {
const spawn = Array.isArray(entry)
? { type: entry[0], x: entry[1], y: entry[2] }
: entry;
assert.equal(systems.isSafe(areaId, spawn.x, spawn.y), true, `${areaId}:${spawn.type}`);
assert.equal(systems.isCombatSafe(areaId, spawn.x, spawn.y), false, `${areaId}:${spawn.type} is in a safe zone`);
});
});
});
test("town landmarks and NPC interaction points remain reachable", () => {
Object.values(data.LANDMARKS).forEach((landmark) => {
assert.equal(systems.isSafe("town", landmark.x, landmark.y), true, `landmark:${landmark.id}`);
});
data.AREAS.town.npcs.forEach(([npcId, x, y]) => {
assert.equal(systems.isSafe("town", x, y), true, `npc:${npcId}`);
});
});