Spaces:
Paused
Paused
Claude
Claude Opus 4.8
Address remaining review defects: B3βB6 + load-hardening + static checks
fdea120 unverified Download test/engine-guards.test.js from Almaatla/SpaceCities: direct link, hf CLI and curl.
- Browser
- Download file 4.15 kB
-
https://huggingface.co/spaces/Almaatla/SpaceCities/resolve/main/test/engine-guards.test.js
- Command line
-
hf download hf://spaces/Almaatla/SpaceCities/test/engine-guards.test.js
-
curl -L -o engine-guards.test.js https://huggingface.co/spaces/Almaatla/SpaceCities/resolve/main/test/engine-guards.test.js
4.15 kB
| /* ============================================================ | |
| Hardening guards for corrupt/tampered/missing data in the combat and | |
| movement paths. Every case here feeds the engine input a WELL-FORMED | |
| save can never contain (an entity whose owner has vanished from | |
| state.players; a move order with NaN coordinates) and asserts the tick | |
| degrades gracefully instead of throwing or NaN-poisoning shared state. | |
| Valid data is unaffected β those round-trip/determinism guarantees live | |
| in the determinism + combat suites; this file only covers the bad-input | |
| edges those suites never reach. | |
| ============================================================ */ | |
| import { test } from "node:test"; | |
| import assert from "node:assert/strict"; | |
| import { createGameState, makeUnit, makeBuilding } from "../engine/state.js"; | |
| import { updateCombat, updateBuildingCombat } from "../engine/combat.js"; | |
| import { tick } from "../engine/sim.js"; | |
| import { mulberry32 } from "../engine/rng.js"; | |
| // Seed createGameState's map generation so the world (and thus every derived | |
| // terrain/side modifier read during combat) is reproducible β createGameState | |
| // falls back to Math.random only when unseeded, which would make these runs vary. | |
| const seeded = () => createGameState({ planetId: "ferros", rng: mulberry32(1234) }); | |
| test("a combat unit whose owner is momentarily absent from state.players survives a tick without throwing", () => { | |
| const state = seeded(); | |
| const attacker = makeUnit("skiff", "player", 500, 500); | |
| const target = makeUnit("skiff", "ai", 510, 500); // 10 away: well inside skiff range (40) | |
| state.units.set(attacker.id, attacker); | |
| state.units.set(target.id, target); | |
| attacker.attackTimer = 0; // ready to fire this tick, so the attacker-side upgrade read is exercised | |
| const startHp = target.hp; | |
| // Tamper: the attacker's owner has vanished from the player table (a corrupt/partial | |
| // save). attackDamage's attacker-side `state.players[owner].upgrades` read used to throw | |
| // here; with the optional-chaining guard it reads no-upgrades (mult 1) and fires normally. | |
| delete state.players.player; | |
| assert.doesNotThrow(() => updateCombat(state, attacker, 0)); | |
| assert.ok(target.hp < startHp, "the attack still landed (proving attackDamage ran past the owner read, not that it bailed early)"); | |
| }); | |
| test("a static-defense building whose owner is momentarily absent from state.players survives a tick without throwing", () => { | |
| const state = seeded(); | |
| const turret = makeBuilding("turret", "player", 500, 500); // completed (not constructing): attack 20, range 130 | |
| const enemy = makeUnit("skiff", "ai", 560, 500); // 60 away: inside turret range | |
| state.buildings.set(turret.id, turret); | |
| state.units.set(enemy.id, enemy); | |
| turret.attackTimer = 0; // ready to fire, so acquire β performAttack β attackDamage all run | |
| const startHp = enemy.hp; | |
| // Same tamper on the turret's side: its owner is gone from state.players. The building | |
| // combat path shares attackDamage, so it exercises the same attacker-side guard. | |
| delete state.players.player; | |
| assert.doesNotThrow(() => updateBuildingCombat(state, turret, 0)); | |
| assert.ok(enemy.hp < startHp, "the turret still fired on the enemy in range"); | |
| }); | |
| test("a unit given a move order with NaN coordinates ends the tick with a finite position, not NaN", () => { | |
| const state = seeded(); | |
| const unit = makeUnit("skiff", "player", 500, 500); | |
| state.units.set(unit.id, unit); | |
| // A tampered/coercion-slipped order pointing at a non-finite destination. Stepping toward | |
| // it would drive the unit's position to NaN and poison the separation spatial hash for | |
| // every neighbour it buckets with β so the movement guard must refuse and clear the order. | |
| unit.order = { type: "move", x: NaN, y: NaN }; | |
| tick(state, 0.05); // full tick: routes through movement AND the separation pass that a NaN would poison | |
| assert.ok(Number.isFinite(unit.x), "x stayed finite"); | |
| assert.ok(Number.isFinite(unit.y), "y stayed finite"); | |
| assert.equal(unit.order, null, "the unreachable order was treated as complete and cleared"); | |
| }); | |