feedback5 WD: the sim fields a 补位 stand-in — getChess(id, { standIn: true }) over data/backups.json, PlayerBattleInput standIn: true, kits by the stand-in's charId (never the replaced operator's), kits/ops/standin-<codename>.js registry, harness standIn
- simdata: DataSource keeps `backups` (rawBackups follows the fallback chain); getStandIn = shared/standIn.js standInRecord normalised (the chess's ids / bonds / 特质 / tier, the stand-in's body, def.standInFor); a PRESET / DIY chess or a source without backups fields itself; the per-battle view maps id-only lookups. - spec.js keeps `standIn` only when exactly true (never on a token); Battle passes it to getChess; syncUnitLoadout carries it; UnitInfo `standInFor`. - content/index.js kitOf: a stand-in's kit is KITS[def.charId] only; no default skill (a stand-in kit writes `skills`); without a kit the generic kit plus genericTalents. kits/index.js STANDIN_KIT_FILES / STANDIN_KITS, merged after the tier groups (old keys keep their order; golden unchanged). - server/data.js and tools/setup.mjs expect backups.json. - test/content/standin.test.js: the plumbing and the eight 预备干员 on all 28 chess records they replace.
This commit is contained in:
1 parent
286a34064f
commit
ed2b679f83
14 files changed
+656
-58
No files matched your search
@@ -1,27 +1,44 @@
|
||||
// The operator kit layout (server/sim/content/kits/README.md): every file of kits/ops/ is listed once in kits/index.js
|
||||
// KIT_FILES and nothing else is; a file `<chessId>-<codename>.js` registers exactly the kit of `<chessId>_a`, whose
|
||||
// data record has that charId code name; the registry is the tier groups in order, and content/index.js serves that
|
||||
// object; no kit file imports a Node-only or server-only module or reads the clock / Math.random (the sim is
|
||||
// deterministic and also runs in the browser, /sim/).
|
||||
// KIT_FILES or STANDIN_KIT_FILES and nothing else is; a file `<chessId>-<codename>.js` registers exactly the kit of
|
||||
// `<chessId>_a`, whose data record has that charId code name; a 补位 file `standin-<codename>.js` registers exactly the
|
||||
// kit of the stand-in character whose charId has that code name (data/backups.json `units`); the registry is the tier
|
||||
// groups in order, then the stand-ins, and content/index.js serves that object; no kit file imports a Node-only or
|
||||
// server-only module or reads the clock / Math.random (the sim is deterministic and also runs in the browser, /sim/).
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync, readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { KIT_FILES, TIER_KITS, KITS } from '../../server/sim/content/kits/index.js';
|
||||
import { KIT_FILES, TIER_KITS, KITS, STANDIN_KIT_FILES, STANDIN_KITS } from '../../server/sim/content/kits/index.js';
|
||||
import { KITS as CONTENT_KITS } from '../../server/sim/content/index.js';
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..');
|
||||
const KIT_DIR = join(ROOT, 'server/sim/content/kits');
|
||||
const CHESS = JSON.parse(readFileSync(join(ROOT, 'data/chess.json'), 'utf8'));
|
||||
const BACKUPS = JSON.parse(readFileSync(join(ROOT, 'data/backups.json'), 'utf8'));
|
||||
const jsIn = (dir) => readdirSync(join(KIT_DIR, dir)).filter((f) => f.endsWith('.js')).sort();
|
||||
|
||||
test('kits/index.js lists every kit file of kits/ops/ exactly once, and only those', () => {
|
||||
const listed = KIT_FILES.flat();
|
||||
const listed = [...KIT_FILES.flat(), ...STANDIN_KIT_FILES];
|
||||
assert.equal(new Set(listed).size, listed.length, 'a file listed twice');
|
||||
assert.deepEqual([...listed].sort(), jsIn('ops'));
|
||||
});
|
||||
|
||||
test('a stand-in kit file standin-<codename>.js registers the kit of that stand-in charId (data/backups.json units)', async () => {
|
||||
const units = Object.keys(BACKUPS.units);
|
||||
for (const file of STANDIN_KIT_FILES) {
|
||||
const m = /^standin-([a-z0-9]+)\.js$/.exec(file);
|
||||
assert.ok(m, `${file}: name`);
|
||||
const charId = units.find((id) => id.replace(/^char_\d+_/, '') === m[1]);
|
||||
assert.ok(charId, `${file}: no stand-in with the code name ${m[1]}`);
|
||||
const mod = await import(`../../server/sim/content/kits/ops/${file}`);
|
||||
assert.deepEqual(Object.keys(mod.default), [charId], `${file}: default export`);
|
||||
assert.equal(typeof mod.default[charId], 'function', `${file}: kit builder`);
|
||||
assert.equal(STANDIN_KITS[charId], mod.default[charId], `${file}: registered`);
|
||||
assert.equal(KITS[charId], mod.default[charId], `${file}: in the merged registry`);
|
||||
}
|
||||
});
|
||||
|
||||
test('a kit file <chessId>-<codename>.js registers the kit of <chessId>_a (codename: its charId without char_<n>_)', async () => {
|
||||
for (const file of KIT_FILES.flat()) {
|
||||
const m = /^(chess_char_\w+?)-([a-z0-9]+)\.js$/.exec(file);
|
||||
@@ -37,9 +54,9 @@ test('a kit file <chessId>-<codename>.js registers the kit of <chessId>_a (coden
|
||||
}
|
||||
});
|
||||
|
||||
test('the registry is the tier groups in KIT_FILES order, one key per file; content/index.js serves the same object', () => {
|
||||
assert.deepEqual(Object.keys(KITS), TIER_KITS.flatMap((t) => Object.keys(t)));
|
||||
assert.equal(Object.keys(KITS).length, KIT_FILES.flat().length, 'one key per file, none twice');
|
||||
test('the registry is the tier groups in KIT_FILES order, then the stand-ins, one key per file; content/index.js serves the same object', () => {
|
||||
assert.deepEqual(Object.keys(KITS), [...TIER_KITS.flatMap((t) => Object.keys(t)), ...Object.keys(STANDIN_KITS)]);
|
||||
assert.equal(Object.keys(KITS).length, KIT_FILES.flat().length + STANDIN_KIT_FILES.length, 'one key per file, none twice');
|
||||
assert.equal(CONTENT_KITS, KITS);
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user