diff --git a/docs/DATA.md b/docs/DATA.md index 9ca2df9a..cb27fb01 100644 --- a/docs/DATA.md +++ b/docs/DATA.md @@ -675,7 +675,7 @@ one slot, the picks of a tier differ, no module of another game mode — `isDiyM "在【沉沦者的黑流树海】中") and the 生息演算 module RA-A (森蚺) are never a player's choice [ASSUMED], the owner's decision of 2026-10-05 for ISW-A and the same reason for SO / RA, while the record and the sim still compose them for the kits' tests). **In battle** a PlayerBattleInput entry of a DIY slot carries `diy` (the pick) and is fielded as `getChess(slotId, { diy })` (docs/SIM.md §12); its kit is `KITS[charId]` (kits/README.md "How to add an -operator (自选)"). (`shared/standIn.js diyRecord(slot, charId, backups, sel)` is the older prototype-only form.) +operator (自选)"). `diy`: diff --git a/server/sim/content/kits/README.md b/server/sim/content/kits/README.md index 69a73621..eb7b52e4 100644 --- a/server/sim/content/kits/README.md +++ b/server/sim/content/kits/README.md @@ -16,7 +16,6 @@ operators, the self-select 6★ operators, contributions from GitHub issue #136 | `shared/tier1.js` | the general kit helpers (blackboard readers, unit predicates, hit hooks, area queries, buffs, zones, free tiles, skill records) and the notes of the tier-1 kits | | `shared/tier2.js` … `tier6.js` | helpers two or more kits of that tier use, and that tier's notes (conventions, simplifications, fx kinds) | | `shared/summoner.js` | the 召唤师 summon deck the 自选 kits of 麦哲伦 / 令 / 电弧 share (`summonDeck`: the holding, a placed piece's return, recalls, the summons leaving with their owner; `holdBuff`, `tokenStat`) | -| `tier1.js` … `tier6.js` | re-export shims for the old import paths; removed after the 0.2.0 refactor | `content/index.js` takes `KITS` from `index.js`: a unit's kit is `KITS[def.baseId]` (also the exact or the suffix-less id), else the generic kit built from the skill blackboard (`content/generic.js`, docs/SIM.md §7.4). A 补位 stand-in's diff --git a/server/sim/content/kits/tier1.js b/server/sim/content/kits/tier1.js deleted file mode 100644 index 5370e714..00000000 --- a/server/sim/content/kits/tier1.js +++ /dev/null @@ -1,8 +0,0 @@ -// server/sim/content/kits/tier1.js — re-export shim (0.2.0 refactor, R4): the tier-1 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier1.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export * from './shared/tier1.js'; -export { tinmanKit } from './ops/chess_char_1_16-tinman.js'; -export default TIER_KITS[0]; diff --git a/server/sim/content/kits/tier2.js b/server/sim/content/kits/tier2.js deleted file mode 100644 index 76dc5d71..00000000 --- a/server/sim/content/kits/tier2.js +++ /dev/null @@ -1,6 +0,0 @@ -// server/sim/content/kits/tier2.js — re-export shim (0.2.0 refactor, R4): the tier-2 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier2.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export default TIER_KITS[1]; diff --git a/server/sim/content/kits/tier3.js b/server/sim/content/kits/tier3.js deleted file mode 100644 index 2a2ac5b5..00000000 --- a/server/sim/content/kits/tier3.js +++ /dev/null @@ -1,6 +0,0 @@ -// server/sim/content/kits/tier3.js — re-export shim (0.2.0 refactor, R4): the tier-3 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier3.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export default TIER_KITS[2]; diff --git a/server/sim/content/kits/tier4.js b/server/sim/content/kits/tier4.js deleted file mode 100644 index 36dd076d..00000000 --- a/server/sim/content/kits/tier4.js +++ /dev/null @@ -1,6 +0,0 @@ -// server/sim/content/kits/tier4.js — re-export shim (0.2.0 refactor, R4): the tier-4 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier4.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export default Object.freeze({ ...TIER_KITS[3] }); diff --git a/server/sim/content/kits/tier5.js b/server/sim/content/kits/tier5.js deleted file mode 100644 index 224472c7..00000000 --- a/server/sim/content/kits/tier5.js +++ /dev/null @@ -1,6 +0,0 @@ -// server/sim/content/kits/tier5.js — re-export shim (0.2.0 refactor, R4): the tier-5 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier5.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export default TIER_KITS[4]; diff --git a/server/sim/content/kits/tier6.js b/server/sim/content/kits/tier6.js deleted file mode 100644 index 1b0280a4..00000000 --- a/server/sim/content/kits/tier6.js +++ /dev/null @@ -1,7 +0,0 @@ -// server/sim/content/kits/tier6.js — re-export shim (0.2.0 refactor, R4): the tier-6 kits live one per file in ops/, -// registered by index.js; their shared helpers and notes are in shared/tier6.js (README.md). This module keeps the -// old imports working until the shims are removed after the 0.2.0 refactor wave. -import { TIER_KITS } from './index.js'; - -export { PITHST_ELEMENTS } from './ops/chess_char_1_15-pithst.js'; -export default TIER_KITS[5]; diff --git a/shared/standIn.js b/shared/standIn.js index 366b9660..739852c3 100644 --- a/shared/standIn.js +++ b/shared/standIn.js @@ -1,5 +1,6 @@ // shared/standIn.js — a chess fielded with another character's body: the 补位 stand-in (原型干员) of a NORMAL chess -// whose operator the player does not own, and a 自选 (DIY) slot filled with a prototype. Pure ESM shared by the build +// whose operator the player does not own, and the composition a 自选 (DIY) slot uses (shared/diy.js diyRecordOf calls +// composeUnitRecord with the pick). Pure ESM shared by the build // (tools/build-data.mjs proves composeUnitRecord rebuilds every PRESET chess from its own unit form), the simulation and // the client. Data: data/backups.json (docs/DATA.md §18). // @@ -130,21 +131,3 @@ export function standInRecord(chess, backups) { return composeUnitRecord(chess, unit, unitForm(backups, b.charId, chess.status), { skillIndex: b.skillIndex, moduleId: b.uniEquipId ?? null, standInFor: chess.charId }); } - -/** - * A 自选 slot (DIY chess record, normal or elite) filled with a prototype `charId` (backups.diy.prototypes[tier]): - * the slot's identity (tier, price, no 特质) with the pick's derived bonds (backups.diy.operators[charId].bonds). The - * skill and module are the caller's — which skill a prototype carries in a slot is not in the data (DESIGN 0.2.0 - * draft, open question). Null when the pick is not a legal prototype of the slot's tier or the data lacks it. - * @param {object} slot a DIY chess record (data/chess.json, `isDiy`) - * @param {string} charId the picked prototype - * @param {object} backups data/backups.json - * @param {{ skillIndex?: number, moduleId?: string|null }} [opts] - * @returns {object|null} - */ -export function diyRecord(slot, charId, backups, { skillIndex, moduleId = null } = {}) { - if (!slot?.isDiy || !(backups?.diy?.prototypes?.[slot.tier] || []).includes(charId)) return null; - const unit = backups.units?.[charId] ?? null; - return composeUnitRecord(slot, unit, unitForm(backups, charId, slot.status), - { skillIndex, moduleId, bonds: backups.diy.operators?.[charId]?.bonds ?? null }); -} diff --git a/test/backups.test.js b/test/backups.test.js index d986f1a5..594d7901 100644 --- a/test/backups.test.js +++ b/test/backups.test.js @@ -12,7 +12,8 @@ import assert from 'node:assert/strict'; import { readFileSync, existsSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; -import { standInRecord, diyRecord, composeUnitRecord, unitForm, statusKey, IDENTITY_FIELDS } from '../shared/standIn.js'; +import { standInRecord, composeUnitRecord, unitForm, statusKey, IDENTITY_FIELDS } from '../shared/standIn.js'; +import { diyRecordOf, lockedSelection } from '../shared/diy.js'; import { composeStats, composeTalents, resolveLoadout, loadoutRecord, normalizeChess } from '../server/sim/simdata.js'; const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); @@ -212,26 +213,26 @@ test('DIY: prototype picks — the 9 elites at tiers 5 and 6, six 4★ reserves assert.deepEqual(prototypes['6'], ELITES); assert.deepEqual(prototypes['5'], [...RESERVES.filter((id) => !['char_600_cpione', 'char_607_cspec'].includes(id)), ...ELITES]); assert.equal(prototypes['5'].length, 15); - // every pick composes in every slot form, with any of its skills (which skill a prototype carries in a slot is an open - // question — the data supports any answer), with the derived bonds (协防干员: no prototype has a faction) and no 特质 + // every pick composes in every slot form — at its locked selection (diy.locked: "技能携带规则与系统补位时一致"; shared/diy.js + // diyRecordOf, which replaced standIn.js's prototype-only diyRecord in 0.2.0) — with the derived bonds (协防干员: no + // prototype has a faction) and no 特质 + const data = { chess, backups }; for (const [id, s] of Object.entries(backups.diy.slots)) { for (const slot of [chess[id], chess[s.goldenId]]) { for (const p of prototypes[s.tier]) { const form = unitForm(backups, p, slot.status); assert.ok(form, `${slot.chessId} ${p}: form`); - for (const sk of form.skills) { - const mod = slot.status.equipLevel > 0 ? form.modules[0]?.uniEquipId ?? null : null; - const r = diyRecord(slot, p, backups, { skillIndex: sk.index, moduleId: mod }); - assert.ok(r && r.skill.index === sk.index && r.charId === p, `${slot.chessId} ${p} S${sk.index + 1}`); - assert.deepEqual([r.bonds, r.garrisonIds, r.price, r.tier], [['emptyShip'], [], 4, s.tier]); - if (mod) assert.equal(r.module.active, true, `${slot.chessId} ${p}: module`); - } + const lk = lockedSelection(data, s.tier, p); + const r = diyRecordOf(slot, { charId: p }, data); + assert.ok(r && r.skill.index === lk.skillIndex && r.charId === p, `${slot.chessId} ${p}`); + assert.deepEqual([r.bonds, r.garrisonIds, r.price, r.tier], [['emptyShip'], [], 4, s.tier]); + if (slot.status.equipLevel > 0 && lk.uniEquipId) assert.equal(r.module.active, true, `${slot.chessId} ${p}: module`); } } } - assert.equal(diyRecord(chess.chess_char_6_diy1_a, 'char_601_cguard', backups, { skillIndex: 2 }), null, 'a reserve is not a tier-6 pick'); - assert.equal(diyRecord(chess.chess_char_5_diy1_a, 'char_600_cpione', backups, { skillIndex: 2 }), null, '预备干员-先锋 is not a pick'); - assert.equal(diyRecord(chess.chess_char_5_01_a, 'char_608_acpion', backups, { skillIndex: 2 }), null, 'not a DIY slot'); + assert.equal(diyRecordOf(chess.chess_char_6_diy1_a, { charId: 'char_601_cguard' }, data), null, 'a reserve is not a tier-6 pick'); + assert.equal(diyRecordOf(chess.chess_char_5_diy1_a, { charId: 'char_600_cpione' }, data), null, '预备干员-先锋 is not a pick'); + assert.equal(diyRecordOf(chess.chess_char_5_01_a, { charId: 'char_608_acpion' }, data), null, 'not a DIY slot'); }); test('DIY: the 71 owned 6★ picks — a form at every slot status with all three skills, every module at stage 1 and 3, their summons', () => { diff --git a/test/content/kits_alt_t3.test.js b/test/content/kits_alt_t3.test.js index 986f2196..43102949 100644 --- a/test/content/kits_alt_t3.test.js +++ b/test/content/kits_alt_t3.test.js @@ -9,7 +9,9 @@ import { skillSpecSource } from '../../server/sim/content/index.js'; import { effectiveProfile } from '../../server/sim/ai.js'; import { kitCoverage } from '../../tools/kit-coverage.mjs'; import { wolfShadows, wolfTacticalPoint } from '../../server/sim/content/tokens.js'; -import KITS from '../../server/sim/content/kits/tier3.js'; +import { TIER_KITS } from '../../server/sim/content/kits/index.js'; + +const KITS = TIER_KITS[2]; const ds = getDefaultSource(); /** Skill index of `skillId` on chess `id`. */ diff --git a/test/content/kits_t3.test.js b/test/content/kits_t3.test.js index c5cdac93..bf8923cf 100644 --- a/test/content/kits_t3.test.js +++ b/test/content/kits_t3.test.js @@ -5,9 +5,11 @@ import assert from 'node:assert/strict'; import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; import { getDefaultSource } from '../../server/sim/simdata.js'; import { effectiveProfile } from '../../server/sim/ai.js'; -import KITS from '../../server/sim/content/kits/tier3.js'; +import { TIER_KITS } from '../../server/sim/content/kits/index.js'; import { COLS } from '../../server/sim/constants.js'; +const KITS = TIER_KITS[2]; + const ds = getDefaultSource(); const D = (id) => ds.getChess(id); const BB = (id) => D(id).skill.bb; diff --git a/test/content/kits_t4.test.js b/test/content/kits_t4.test.js index 22d99dcf..3a0b1c41 100644 --- a/test/content/kits_t4.test.js +++ b/test/content/kits_t4.test.js @@ -4,9 +4,11 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; import { getDefaultSource } from '../../server/sim/simdata.js'; -import kits from '../../server/sim/content/kits/tier4.js'; +import { TIER_KITS } from '../../server/sim/content/kits/index.js'; import { absoluteRangeKeys } from '../../server/sim/targeting.js'; +const kits = TIER_KITS[3]; + const ds = getDefaultSource(); const D = (id) => ds.getChess(id); const approx = (a, b, eps = 1e-6, msg = '') => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg} ${a} ≈ ${b}`); diff --git a/test/content/kits_t5.test.js b/test/content/kits_t5.test.js index d3189090..67be8009 100644 --- a/test/content/kits_t5.test.js +++ b/test/content/kits_t5.test.js @@ -3,7 +3,9 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { makeBattle, enemyRec, chessRec, checkInvariants } from '../helpers/battleHarness.js'; -import KITS from '../../server/sim/content/kits/tier5.js'; +import { TIER_KITS } from '../../server/sim/content/kits/index.js'; + +const KITS = TIER_KITS[4]; const approx = (a, b, eps = 1e-6, msg = '') => assert.ok(Math.abs(a - b) <= eps * Math.max(1, Math.abs(b)), `${msg} ${a} ≉ ${b}`); const dummy = (key = 'enemy_dummy', o = {}) => enemyRec({ key, hp: 1e7, speed: 0, ...o }); diff --git a/test/content/playtest5_elements.test.js b/test/content/playtest5_elements.test.js index 724f79af..77d0368a 100644 --- a/test/content/playtest5_elements.test.js +++ b/test/content/playtest5_elements.test.js @@ -14,7 +14,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import { makeBattle, enemyRec, chessRec, checkInvariants } from '../helpers/battleHarness.js'; import { getDefaultSource } from '../../server/sim/simdata.js'; -import { PITHST_ELEMENTS } from '../../server/sim/content/kits/tier6.js'; +import { PITHST_ELEMENTS } from '../../server/sim/content/kits/ops/chess_char_1_15-pithst.js'; import { spawnYanyou } from '../../server/sim/content/tokens.js'; import { COLS } from '../../server/sim/constants.js'; diff --git a/test/match/simServe.test.js b/test/match/simServe.test.js index 96102d8e..000e8fb7 100644 --- a/test/match/simServe.test.js +++ b/test/match/simServe.test.js @@ -12,7 +12,7 @@ after(async () => { if (srv) await srv.close(); }); test('/sim/ serves the simulation modules read-only; nothing else of server/ is reachable', async () => { srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true }); const get = (p, headers = {}) => fetch(srv.url + p, { redirect: 'manual', headers }); - for (const p of ['/sim/spec.js', '/sim/Battle.js', '/sim/simdata.js', '/sim/constants.js', '/sim/content/index.js', '/sim/content/kits/tier1.js', '/sim/content/support/index.js']) { + for (const p of ['/sim/spec.js', '/sim/Battle.js', '/sim/simdata.js', '/sim/constants.js', '/sim/content/index.js', '/sim/content/kits/index.js', '/sim/content/support/index.js']) { const r = await get(p); assert.equal(r.status, 200, p); assert.match(r.headers.get('content-type'), /^text\/javascript/, p); diff --git a/test/sim/facing.test.js b/test/sim/facing.test.js index 45d28512..d360681a 100644 --- a/test/sim/facing.test.js +++ b/test/sim/facing.test.js @@ -12,7 +12,7 @@ import { COLS } from '../../server/sim/constants.js'; import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/battleHarness.js'; import { findSummonTile } from '../../server/sim/content/tokens.js'; import { frontTile, sideTiles, alliesAround, N4 } from '../../server/sim/content/support/index.js'; -import { freeTileAround } from '../../server/sim/content/kits/tier1.js'; +import { freeTileAround } from '../../server/sim/content/kits/shared/tier1.js'; import { DIRS as PROTOCOL_DIRS } from '../../shared/protocol.js'; const K = (r, c) => r * COLS + c; diff --git a/test/sim/playtest6-combat.test.js b/test/sim/playtest6-combat.test.js index 9b39d693..fd16019b 100644 --- a/test/sim/playtest6-combat.test.js +++ b/test/sim/playtest6-combat.test.js @@ -22,7 +22,7 @@ import { makeBattle, chessRec, enemyRec, checkInvariants } from '../helpers/batt import { getDefaultSource, hasGeneratedData } from '../../server/sim/simdata.js'; import { effectiveProfile } from '../../server/sim/ai.js'; import { PUSH_TILES, PUSH_TILES_EFFECT, PULL_STOP_RADIUS, ASPD_MIN, COLS } from '../../server/sim/constants.js'; -import { alliesInGridOf } from '../../server/sim/content/kits/tier1.js'; +import { alliesInGridOf } from '../../server/sim/content/kits/shared/tier1.js'; import { Unit } from '../../server/sim/units.js'; import { spawnYanyou, TOKEN_IDS } from '../../server/sim/content/tokens.js';