Refactor platform UI and data flow across core pages
All checks were successful
Build Org Website / build (push) Successful in 50s
All checks were successful
Build Org Website / build (push) Successful in 50s
This commit is contained in:
40
tests/princess-lima-polish.test.cjs
Normal file
40
tests/princess-lima-polish.test.cjs
Normal file
@@ -0,0 +1,40 @@
|
||||
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-data.js");
|
||||
const intro = require("../assets/scripts/pages/princess-lima-intro.js");
|
||||
|
||||
const root = path.resolve(__dirname, "..");
|
||||
const rpgSource = fs.readFileSync(path.join(root, "play/rpg.org"), "utf8");
|
||||
|
||||
test("all nine regions have authored art data and valid collision-safe transitions", () => {
|
||||
const mapsSource = fs.readFileSync(path.join(root, "assets/scripts/pages/princess-lima-maps.js"), "utf8");
|
||||
data.MAP_IDS.forEach((region) => {
|
||||
assert.match(mapsSource, new RegExp(`${region}:\\s*\\{`));
|
||||
Object.values(data.MAPS[region].spawns).forEach((spawn) => {
|
||||
assert.ok(spawn.x > 0 && spawn.x < data.WIDTH);
|
||||
assert.ok(spawn.y > 0 && spawn.y < data.HEIGHT);
|
||||
});
|
||||
data.MAPS[region].exits.forEach((exit) => {
|
||||
assert.ok(data.MAPS[exit.target]);
|
||||
assert.ok(data.MAPS[exit.target].spawns[exit.spawn]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("keyboard-only page contains one left HUD and no touch controls", () => {
|
||||
assert.match(rpgSource, /class="lima-hud"/);
|
||||
assert.match(rpgSource, /Inventory <kbd>I<\/kbd>/);
|
||||
assert.match(rpgSource, /Menu <kbd>M<\/kbd>/);
|
||||
assert.doesNotMatch(rpgSource, /data-lima-touch|data-lima-move|Touch controls|data-lima-attack/);
|
||||
});
|
||||
|
||||
test("opening cinematic is complete, replayable and accessible", () => {
|
||||
assert.equal(intro.BEATS.length, 5);
|
||||
assert.ok(intro.BEATS.reduce((total, beat) => total + beat.duration, 0) >= 60000);
|
||||
assert.ok(intro.BEATS.every((beat) => beat.subtitle.length > 20));
|
||||
assert.match(rpgSource, /data-lima-replay-intro/);
|
||||
assert.match(rpgSource, /data-lima-intro-pause/);
|
||||
assert.match(rpgSource, /Hold Escape to skip/);
|
||||
});
|
||||
16
tests/princess-lima-state.test.cjs
Normal file → Executable file
16
tests/princess-lima-state.test.cjs
Normal file → Executable file
@@ -12,6 +12,22 @@ test("fresh save uses the Princess Lima schema and safe village start", () => {
|
||||
assert.equal(fresh.region, "village");
|
||||
assert.deepEqual({ x: fresh.position.x, y: fresh.position.y }, data.MAPS.village.spawns.start);
|
||||
assert.equal(fresh.settings.soundEnabled, false);
|
||||
assert.equal(fresh.introSeen, false);
|
||||
assert.equal(fresh.settings.subtitles, true);
|
||||
assert.equal(fresh.settings.narrationEnabled, true);
|
||||
});
|
||||
|
||||
test("old v1 saves continue without unexpectedly replaying the new introduction", () => {
|
||||
const candidate = state.fresh("Legacy", "azure");
|
||||
delete candidate.introSeen;
|
||||
delete candidate.settings.voice;
|
||||
delete candidate.settings.subtitles;
|
||||
delete candidate.settings.narrationEnabled;
|
||||
const restored = state.normalize(candidate);
|
||||
assert.equal(restored.introSeen, true);
|
||||
assert.equal(restored.settings.voice, 0.85);
|
||||
assert.equal(restored.settings.subtitles, true);
|
||||
assert.equal(restored.settings.narrationEnabled, true);
|
||||
});
|
||||
|
||||
test("names and appearances validate", () => {
|
||||
|
||||
24
tests/princess-lima-systems.test.cjs
Normal file → Executable file
24
tests/princess-lima-systems.test.cjs
Normal file → Executable file
@@ -16,6 +16,30 @@ test("movement is responsive and diagonal speed is normalized", () => {
|
||||
assert.deepEqual(velocity, { x: 0, y: 0 });
|
||||
});
|
||||
|
||||
test("directional attacks separate wind-up, active hit and recovery timing", () => {
|
||||
assert.equal(systems.attackPhase(0), "windup");
|
||||
assert.equal(systems.attackPhase(systems.ATTACK_TIMING.windup), "active");
|
||||
assert.equal(systems.attackPhase(systems.ATTACK_TIMING.windup + systems.ATTACK_TIMING.active), "recovery");
|
||||
assert.equal(systems.attackPhase(999), "complete");
|
||||
const origin = { x: 500, y: 400 };
|
||||
const north = systems.attackHitbox("north", origin.x, origin.y);
|
||||
const south = systems.attackHitbox("south", origin.x, origin.y);
|
||||
const east = systems.attackHitbox("east", origin.x, origin.y);
|
||||
const west = systems.attackHitbox("west", origin.x, origin.y);
|
||||
assert.ok(north.y + north.height <= origin.y);
|
||||
assert.ok(south.y >= origin.y);
|
||||
assert.ok(east.x >= origin.x);
|
||||
assert.ok(west.x + west.width <= origin.x);
|
||||
});
|
||||
|
||||
test("enemy knockback stops before walls and map boundaries", () => {
|
||||
const current = state.fresh("Ada", "azure");
|
||||
const safe = systems.wallSafeKnockback(current, "village", 430, 400, 1, 0, 60);
|
||||
assert.equal(systems.isSafePosition(current, "village", safe.x, safe.y), true);
|
||||
const edge = systems.wallSafeKnockback(current, "village", 50, 400, -1, 0, 90);
|
||||
assert.equal(systems.isSafePosition(current, "village", edge.x, edge.y), true);
|
||||
});
|
||||
|
||||
test("all named spawns are collision-safe and invalid positions recover", () => {
|
||||
const current = state.fresh("Ada", "azure");
|
||||
Object.entries(data.MAPS).forEach(([regionId, map]) => {
|
||||
|
||||
Reference in New Issue
Block a user