feedback5: merge W5H — 自选 data (72 owned 6★ without the 6 collab operators, every DIY form, summons, prototypes' locked skills), shared/diy.js, the sim fields DIY picks, OPERATOR_KIT_FILES with 推进之王 as the first operator kit, golden family diy, the art plan (--add-only)

Conflicts: tools/golden.mjs / test/golden/README.md take both new families (standins from W5G, diy from W5H); DATA.md / SIM.md keep both texts. data/ equals an offline build of the merged tools/build-data.mjs. Golden regenerated on the merged code: against feedback5 nothing moved, diy.json is new; against fb5-diy the W5G / W5J / W5L scenarios moved, and diy-004 / diy-006 (the prototype battles with Touch, changed by W5L) — diy-001/002/003/005 identical.
This commit is contained in:
sganggs committed 2026-10-05 22:52:24 +08:00
commit 1006254beb
32 files changed
+2229 -174

No files matched your search

+9 -5
View File
@@ -22,6 +22,7 @@ npm run assets # = node tools/vendor.mjs && node tools/fetch-assets.mjs
| `--refresh-index` | Re-download the two upstream indexes: `audio_data.json` and `models_data.json`. |
| `--prune` | Delete files under `public/assets/` that the manifest no longer references, for example after a mapping change. Without this flag they are only listed in the report. `public/assets/local/` (written by `tools/local-extract`) is never pruned. Implies `--allow-shrink`. |
| `--allow-shrink` | Write `data/assets.json` even when it loses entries the current one has (see "The manifest never shrinks by accident" below). |
| `--add-only` | For a checkout whose `public/assets/` and `public/fonts/` are shared with another one (a git worktree with symlinked asset folders): download only the files missing on disk and never re-download, rewrite or delete an existing file — atlases already on disk are left as they are, the fonts are not rebuilt (the manifest keeps its current `fonts`). Not with `--prune` / `--force`. |
| `--local-spines` | Rewrite `tools/assets/local-enemy-spines.json` (the metadata of the enemy models only the local client has, see "Enemy aliases") from the models `tools/local-extract/extract.py` extracted to `public/assets/local/spine/enemy/`. Run it after a game update changed them; without it the committed file is used and a differing extraction only gets a warning. |
**The manifest never shrinks by accident.** An entry whose files are missing on this machine is left out of a rebuilt
@@ -71,7 +72,8 @@ The research JSONs in `docs/research/` (03, 05, 07) define **which** ids are nee
|---|---|---|
| Operator avatars, 180×180. Base, plus E2 when it exists. | yuanyan3060/ArknightsGameResource `avatar/` | `char/avatar/{charId}.png`, `char/avatar/{charId}_2.png` |
| Operator half-body portraits, 180×360 | yuanyan `portrait/` | `char/portrait/{charId}_1.png`, `_2.png` |
| Default-skill icons, including backup operators' skills | yuanyan `skill/` | `skill/{iconId sanitized}.png` |
| Skill icons: every skill index of every planned operator (DESIGN §16 loadouts, 自选 picks) | yuanyan `skill/` | `skill/{iconId sanitized}.png` |
| Module type icons: the `typeIcon` of every module of `data/chess.json` and `data/backups.json` (the 干员调配 / 自选 module tiles when the local-client art lacks them) | AA2 `cn` `arts/ui/uniequiptype/{typeIcon}.png` (else its lower-case name: `WAH-Y` → `wah-y.png`) | `module/{typeIcon}.png` |
| Enemy icons | yuanyan `enemy/`. Fallbacks: the handbook id, then the base id. | `enemy/icon/{enemyId}.png` |
| Token avatars | yuanyan `avatar/` | `token/avatar/{tokenId}.png` |
| Bond icons (the real autochess glyphs) | ArknightsAssets2 `cn` `ui/autochess/[uc]autochesscommon/arts/bondicon/`. Fallback: the camp logo. | `bond/{bondId}.png` |
@@ -92,8 +94,8 @@ The `stem` of a Spine model is the upstream file name. Two examples: `char_107_l
### Id scope
- **Operators:** all 138 pool charIds from `activity_table` (`charShopChessDatas[*].charId ∪ backupCharId`), including hidden chess and backup operators.
- **Tokens:** the 20 pool tokens.
- **Operators:** all 138 pool charIds from `activity_table` (`charShopChessDatas[*].charId ∪ backupCharId`), including hidden chess and backup operators; plus every unit of `data/backups.json` research 07 does not list — the 72 自选 owned-6★ picks (`diy.ownedPool`, DATA.md §18; the collab operators are not in the data) — planned from 07's URL patterns (`tools/assets/plan.mjs patternOperator`, `tools/fetch-assets.mjs dataExtras`): avatar and portrait (E0–E1 and E2), the default-skin battle Spine Front / Back, the icon and skill sound of each skill, the sub-profession icon. 210 operators in all (207 with a Back model).
- **Tokens:** the 20 pool tokens, and the 38 summons of the 自选 picks (`data/backups.json tokens`) as extra tokens (below): their avatars; their battle Spine exists upstream only as skin variants, which the default locations miss — no Spine yet (the client draws the avatar).
- **Enemies:** 253 ids planned, 252 in the manifest (心烛 has no assets). The set is the union of:
- the 07 enemy list;
- every enemy in the `act1autochess_*` wave, boss and 联防 levels that act2 modes use (from `05-maps.json`; the tutorial is excluded);
@@ -104,7 +106,7 @@ The `stem` of a Spine model is the upstream file name. Two examples: `char_107_l
- enemy units spawned by operator kits (research 03 skills/talents). For example, 隐德来希's default S3 summons 心烛 `enemy_5601_entlec` through the talent key `take_extra_enemy_key`. 心烛 has no icon and no Spine in any dump, so it has no manifest entry: it is reported as a miss, and the client must draw a glyph.
- **Extra tokens:** any `token_*` key of `data/tokens.json` that research does not list gets the default avatar and Spine locations.
- The non-token summons in that file (`enemy_9012_acloon` 炎佑, `char_605_cmedic`, `char_613_acmedc`) are found under `enemies` and `chars`.
- **Skill icons:** the default skill of every chess (`defaultSkillIndex`), plus each backup operator's `skillIndex`. That makes 144 icons.
- **Skill icons:** every skill index of every planned operator (the default one first; DESIGN §16 loadouts, the 自选 picks' three skills): 525 icons.
- **UI:**
- every group from `07-assets.json → autochessUi`: rarity, elite and chess-level sprites, the shop panel and cards, HUD, bond board, equip slot, round dialog, band choose, settlement, prepare backdrop;
- `arts` (rarity stars, elite icons, the camp logos of pool nations, the loading illustrations used by the act2 modes, battle common sprites, act2 entry backdrops and season logo, item rarity frames);
@@ -136,7 +138,7 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
hash: 'a1b2c3d4e5f6', // content hash (cache busting)
generator: 'tools/fetch-assets.mjs',
stats: { files, bytes, chars, charsWithBack, enemies, enemiesWithSpine, tokens, tokensWithSpine,
spineModels, bonds, items, bands, skills, ui, sfxUnits },
spineModels, bonds, items, bands, skills, modules, ui, sfxUnits },
chars: { [charId]: { avatar, avatarE2?, portrait, portraitE2?, spine: { front: Spine, back?: Spine } } },
enemies: { [enemyId]: { icon, spine?: Spine, spineAliasOf?: enemyId,
spineLocal?: { group, skel, atlas, textures, …Spine } } },
@@ -148,6 +150,8 @@ All paths are URL paths relative to the site root, for example `/assets/char/ava
bands: { [bandId]: url },
skills: { [iconId]: url }, // iconId = skill_table iconId ?? skillId
skillsById: { [skillId]: iconId },
modules: { [typeIcon]: url }, // module type icon (uniequip typeIcon, e.g. 'sol-x'): public/js/screens/loadout.js
// moduleIconOf after the local-client art
ui: { ['group/key']: url }, // e.g. 'hudPanel/icon_hp', 'shopCard/frame_lv1', 'loading/loading_ac_core';
// 'emoticon/basic/pic_happy_battle', 'guide/autochess_home_1': the data/local-assets.json
// group + name of the same picture (the client takes the local one first)
+46 -21
View File
@@ -501,8 +501,9 @@ Glyph legend (`rows`):
`…_eagle1/3` stay listed (`sources:["display"]`, belong to S1/S3). With another skill selected (DESIGN §16) the
talent follows that skill's eagle (`simdata loadoutRecord`; `variants[o].bySkill[i].sources`).
2. **DIY chess** (`chess_char_5_diy1/2`, `chess_char_6_diy1/2`, `_a` and `_b` = 8 records): no `charId`, no stats/skill; `visible:false`,
name placeholder `甄选干员`, not in the shop. Their picks and bond rule are in `backups.json diy` (§18); the gameplay
is not implemented yet.
name placeholder `甄选干员`, not in the shop. Their picks and bond rule are in `backups.json diy` (§18); a slot fights
only as a 自选 piece — a PlayerBattleInput entry with its `diy` pick (docs/SIM.md §12; shared/diy.js) — and a slot
without a legal pick fields nothing. The picker and the per-player shop are not implemented yet.
3. **Module-less chess**: 蒂比 (`chess_char_2_13`) and 凛御银灰 (`chess_char_5_14`) have no module; their golden
record has `module:{id:null,active:false}` and no module stat bonus.
4. **Hidden chess (17)** are kept with `visible:false`; several operators exist in two tiers with one hidden
@@ -562,14 +563,16 @@ Glyph legend (`rows`):
`chess 266 (112 visible; 283 selectable skills over the visible chess, 184 module choices over 129 goldens; 74 PRESET / 55 NORMAL / 4 DIY base chess)`, `bonds 23`, `garrisons 249 (43 effect keys)`, `items 115`, `bands 40`, `effects 361`,
`enemies 249`, `factions 67 entries`, `waves 38`, `stages 11 (8 active)`, `bosses 10`, `tokens 22`, `choice events 109`,
`bounty cards 129`, `tactic cards 43`, `backups: 17 stand-in units (43 forms), 4 DIY slots, 15 / 9 prototype picks (tier 5 / 6), 78 owned-6★ picks`.
`bounty cards 129`, `tactic cards 43`, `backups: 89 units (17 stand-ins, 72 owned-6★ picks; 259 forms), 38 自选 summons, 4 DIY slots, 15 / 9 prototype picks (tier 5 / 6), 72 owned-6★ picks (6 collab operators excluded)`.
## 17. Integrity guarantees (checked by the builder and `test/data.test.js`)
Every PRESET chess is its own backup and the stand-in builder gives it back field for field (§18); every NORMAL chess
(both forms) composes into its stand-in with an unlocked backup skill and, on the elite form, its module at that level;
every DIY prototype pick has a form for both slot statuses; derived DIY bonds exist (`test/backups.test.js` also
re-derives the backup fields, the stand-in numbers and the faction ids from the raw tables).
every DIY prototype pick has a form for both slot statuses and its locked skill / module there; every owned pick has
both slot forms with its three skills, every module of the character at the elite's stage and a `tokens` variant per
summon; derived DIY bonds exist; no 自选 summon id is a tokens.json record (`test/backups.test.js` also re-derives the
backup fields, the stand-in numbers, the owned pool and the faction ids from the raw tables).
Chess bonds/garrisons/tokens/base/golden ids resolve; talent tokens are in `chess.tokens`; every non-DIY chess has
`skills[]` with exactly one default equal to `skill` (and every skill token listed in `chess.tokens`); golden module
choices are consistent (one default iff `module.active`, base fields present; composing the default reproduces the
@@ -589,15 +592,16 @@ included) × allowed entry, and `test/sim/pathing-crosscheck.test.js` re-impleme
whole map for every walkable tile of the normal / 联防 / boss fields of the 8 stages (extended by the road-over-floor
preference; against the pure official algorithm: identical route lengths, never more non-blockable tiles crossed).
## 18. `backups.json` — 补位 stand-ins and 自选 (DIY) data — `{ units, diy }`
## 18. `backups.json` — 补位 stand-ins and 自选 (DIY) data — `{ units, tokens, diy }`
The data of two official features: **补位** — a NORMAL chess whose operator the player does not own is fielded as its
official stand-in (原型干员); played since 0.2.0: the player marks operators as not owned on the 干员持有 tab
(`room.ownership { notOwned }`, DESIGN 0.2.0 draft, docs/PLAYING.md §3), the match fields those chess with
`standIn: true` — and **自选编队** — two tier-5 and two tier-6 DIY slots, each filled with a 6★ the player owns or a
prototype (gameplay not implemented yet). Built by `tools/build-data.mjs buildBackups`;
`shared/standIn.js` composes it into chess-shaped records. The rules in the data (activity_table act2autochess
`charShopChessDatas`; PRTS 卫戍协议, 卫戍协议:盟约 下半/PRTS盟约记录):
prototype. Built by `tools/build-data.mjs buildBackups`; `shared/standIn.js` composes it into chess-shaped records,
`shared/diy.js` adds the 自选 rules on top. The sim fields both (docs/SIM.md §12); the 自选 picker and per-player shop
come later. The rules in the data (activity_table act2autochess `charShopChessDatas`; PRTS 卫戍协议, 卫戍协议:盟约
下半/PRTS盟约记录):
- PRESET (74, 特许干员) always fields the real operator (`backup.charId` = itself); NORMAL (55) names one of 17 stand-ins —
the 4★ 预备干员 `char_600–607`, the 6★ `char_608–615` and 领主·Sharp `char_617` (the "其它分支"); DIY (4) has none.
@@ -606,21 +610,22 @@ prototype (gameplay not implemented yet). Built by `tools/build-data.mjs buildBa
character takes S2 on one chess and S3 on another), module `backup.uniEquipId` (null at tiers 3–4 and for every 4★, so
even their elite form has none) and potential 0 (`potRank` 0 on all 55).
`units[charId]` — the 17 stand-ins (no unit for a PRESET or DIY chess):
`units[charId]` — first the 17 stand-ins, then the 72 owned-6★ 自选 picks (`diy.ownedPool`); no unit for a PRESET or DIY
chess:
| Field | Example (`char_611_acnipe`) | Meaning |
|---|---|---|
| `charId`, `name`, `appellation`, `rarity`, `profession`, `subProfessionId`, `subProfessionName`, `position`, `nationId`, `isNotObtainable` | `"char_611_acnipe"`, `"Stormeye"`, `"Stormeye"`, `6`, `"SNIPER"`, `"fastshot"`, `"速射手"`, `"RANGED"`, `null`, `true` | as on a chess record |
| `assets` | `{"avatar":"char_611_acnipe","avatarGolden":"char_611_acnipe","portrait":"char_611_acnipe_1","portraitGolden":"char_611_acnipe_1","spine":"char_611_acnipe","subProfIcon":"sub_fastshot_icon"}` | art ids (URLs in `data/assets.json`, which already carries all 17); the elite form takes the E2 art when it exists — only 领主·Sharp has it |
| `charId`, `name`, `appellation`, `rarity`, `profession`, `subProfessionId`, `subProfessionName`, `position`, `nationId`, `isNotObtainable` | `"char_611_acnipe"`, `"Stormeye"`, `"Stormeye"`, `6`, `"SNIPER"`, `"fastshot"`, `"速射手"`, `"RANGED"`, `null`, `true` | as on a chess record (`isNotObtainable` false for the owned picks) |
| `assets` | `{"avatar":"char_611_acnipe","avatarGolden":"char_611_acnipe","portrait":"char_611_acnipe_1","portraitGolden":"char_611_acnipe_1","spine":"char_611_acnipe","subProfIcon":"sub_fastshot_icon"}` | art ids (URLs in `data/assets.json`, which carries all 89 — the owned picks since 0.2.0, ASSETS.md); the elite form takes the E2 art when it exists — of the stand-ins only 领主·Sharp has it, every owned pick does |
| `moduleNames` | `{"uniequip_001_acnipe":{"name":"Stormeye证章","typeName":"ORIGINAL"},"uniequip_002_acnipe":{"name":"Stormeye证章","typeName":"MAR-X"}}` | every module of the character (a composed record names its module on the normal form too) |
| `standsIn[]` | `["chess_char_3_21_a","chess_char_4_02_a","chess_char_5_18_a","chess_char_5_22_a","chess_char_6_01_a","chess_char_6_05_a"]` | the NORMAL base chess it replaces |
| `forms[statusKey]` | keys `"2/1/4/0"`, `"2/60/7/1"`, `"2/60/7/3"` | the character at every status it fights at — of the chess it stands in for and of the DIY slots it may fill (`statusKey(status)` = `phase/level/skillLevel/equipLevel`): 3 forms per 6★, 2 per 4★ |
| `standsIn[]` | `["chess_char_3_21_a","chess_char_4_02_a","chess_char_5_18_a","chess_char_5_22_a","chess_char_6_01_a","chess_char_6_05_a"]` | the NORMAL base chess it replaces (`[]` for an owned pick) |
| `forms[statusKey]` | keys `"2/1/4/0"`, `"2/60/7/1"`, `"2/60/7/3"` | the character at every status it fights at — of the chess it stands in for and of the DIY slots it may fill (`statusKey(status)` = `phase/level/skillLevel/equipLevel`): 3 forms per 6★ (an owned pick: exactly the three DIY slot statuses — E2 Lv1 skill rank 4 without a module, E2 Lv60 rank 7 with every module at stage 1 and at stage 3), 2 per 4★ |
A **form** holds the operator fields of a chess record with **nothing selected**: `status`; `stats`, `trait`, `talents`
**without** a module; `immunities`, `rangeId`, `rangeGrid`, `dmgType`, `attackKind`, `projectile`, `canHitFly`,
`targetPriority`; `skills[]` — every skill unlocked at the status, at its skill level, `trigger` resolved per skill, no
`isDefault`; `displayTokens` / `tokens` (summons — none of the 17 has one); at `equipLevel > 0` `modules[]` (§2.2 shape
without `isDefault`). `buildUnitForm` uses buildChess's helpers and rules, and the build fails when `buildUnitForm` +
`isDefault`; `displayTokens` / `tokens` (summons — none of the 17 stand-ins has one; 27 of the owned picks do, their
records in `tokens` below); at `equipLevel > 0` `modules[]` (§2.2 shape without `isDefault`). `buildUnitForm` uses buildChess's helpers and rules, and the build fails when `buildUnitForm` +
`composeUnitRecord` do not give back every PRESET chess field for field (each is its own backup), so a later change to
buildChess that the stand-ins would miss stops the build. The chess trigger deviations (§2.2, DESIGN §21.29) name chess
and never apply to a form; the 重装 stand-ins have their own, by charId (`tools/build-data.mjs STANDIN_TRIGGER_DEVIATIONS`,
@@ -643,8 +648,19 @@ the player's own field, 联防 and the boss fields alike) is fielded as `getChes
§12: this record, normalised), and its kit is found by its `charId` (`server/sim/content/index.js kitOf`;
kits/README.md "Stand-in kits") — never by the chess id it keeps, which names the replaced operator's kit. The client
composes the same record (`public/js/ui/gameLogic/standIn.js standInOf`, the renderer's `data.standIn`) for the cards'
「替补:X」 badge, the board model and the detail card. `diyRecord(slot, charId, backups, { skillIndex, moduleId })` fills a DIY slot with a prototype (the slot's
tier and price, no 特质, the pick's derived bonds); which skill a prototype carries in a slot is not in the data.
「替补:X」 badge, the board model and the detail card.
A 自选 piece is composed by `shared/diy.js`:
`checkDiyPick(slotId, pick, data)` checks one pick `{ charId, skillIndex?, uniEquipId? }` against a slot (a pick of the
slot's tier; a prototype takes its `diy.locked` selection, another skill is refused; an owned pick names one of its three
skills and optionally a module of its elite form at the slot's stage), `diyRecord(slotId, pick, { elite, data })` /
`diyRecordOf(slot, pick, data)` give the record — the slot's identity (tier, price, merge, status; no 特质), the pick's
derived bonds, the operator's form at the slot's status with that skill and module (active on the elite only), plus
`diyFor` = the slot's base id —, `diyPool(tier, { data, kitted })` the legal picks of a tier (prototypes, then the owned
pool; with `kitted`, only operators with a kit: server/sim/content/kits/index.js `KITTED_CHARS`) and
`validateDiyPicks(picks, { data, kitted })` a roster (a prototype may fill a tier-5 and a tier-6 slot, an owned operator
one slot, the picks of a tier differ). **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.)
`diy`:
@@ -652,9 +668,18 @@ tier and price, no 特质, the pick's derived bonds); which skill a prototype ca
|---|---|---|
| `slots[slotId]` | `{"tier":5,"goldenId":"chess_char_5_diy1_b","shopLevel":5,"requirement":"TIER_6"}` | the four DIY chess `chess_char_5_diy1/2_a`, `chess_char_6_diy1/2_a` (chess.json: price 4, sell 1, `diyRequirement`, empty `bonds` / `garrisonIds`); `shopLevel` = the 调度中心 level whose `shopLevelDisplayDataDict.charChessDiySlotIdList` lists the slot |
| `prototypes[tier]` | `{"5":[…15],"6":[…9]}` | the legal prototype picks: the nine 6★ at both tiers, at tier 5 also the six 4★ that are not 先锋 / 特种 ("第5阶可额外从6名四星原型干员(先锋、特种职业除外)中选取"; `DIY_EXTRA_PROTOTYPES`). A prototype may fill a tier-5 and a tier-6 slot ("原型干员可于5、6阶之间重复选取") |
| `ownedPool[]` | 78 charIds | the owned 6★ a player may slot: obtainable, rarity = the requirement, and no chess names it — hidden chess included ("不可甄选加入已在名单中的固定干员"); each at most once per roster ("玩家已拥有干员不可重复选取"). The data does not exclude collab operators |
| `locked[tier][charId]` | `{"skillIndex":2,"uniEquipId":"uniequip_002_acguad","from":["chess_char_5_06_a","chess_char_5_13_a"]}` | the skill and module a prototype carries in a slot of that tier — "技能携带规则与系统补位时一致" (PRTS 卫戍协议), read as [ASSUMED] (the owner's decision of 2026-10-05) the selection of its 补位 rows at that tier (`from`): the eight 6★ elites S3 with their own module, 领主·Sharp S1, the reserves S3 without a module; 预备干员-医疗 has no tier-5 row: S3 by analogy (`from` `[]`, `DIY_PROTOTYPE_FALLBACK_SKILL`) |
| `ownedPool[]` | 72 charIds | the owned 6★ a player may slot: obtainable, rarity = the requirement, and no chess names it — hidden chess included ("不可甄选加入已在名单中的固定干员"); each at most once per roster ("玩家已拥有干员不可重复选取"). Not the collab operators (`excluded`) |
| `excluded[]` | `["char_456_ash","char_1029_yato2","char_4123_ela","char_4141_marcil","char_4182_oblvns","char_4217_makoto"]` | the 6 obtainable 6★ outside the pool whose `mainPower` / `subPower` team is a collab (`DIY_EXCLUDED_TEAMS`: rainbow, action4, mujica, sees, laios — 灰烬, 麒麟R夜刀, 艾拉, 玛露西尔, 丰川祥子, 结城理): left out of the data and the pool by the owner's decision of 2026-10-05 (copyright); the excel does not exclude them |
| `operators[charId]` | `{"name":"煌","rarity":6,"profession":"WARRIOR","subProfessionId":"centurion","obtainable":true,"powers":["rhodes","elite","yan","victoria"],"bonds":["yanShip","victoriaShip"]}` | every pick (owned pool + prototypes): `powers` = the `nationId` / `groupId` / `teamId` of `mainPower` and of every `subPower` (隐藏势力); `bonds` = the core bonds whose `powerIdList` meets them — one or several — else `economy.fallbackBondId` 协防干员 ("甄选加入的干员会根据其实际阵营所属分配核心盟约,若没有可匹配的则改为分配协防干员盟约"); every prototype gets `["emptyShip"]` |
`tokens[tokenId]` — the summons of the owned picks (38 tokens of 27 operators): a tokens.json record (§14: name, text,
`placeable`, `ownerRange`, `abnormal`, `assets`) whose `variants` are keyed by the owner FORM `<charId>@<statusKey>`
(`owners` = those keys; shared/diy.js `diyTokenOwner`) instead of a chess id — a DIY piece is a slot, and two players may
fill one slot with different operators. A variant is the token at the owner's status for its first skill and no module,
`bySkill[i]` the token skill / count / sources under each other skill, `byModule[id]` each module's token attributes,
trait and talents (`tools/build-data.mjs buildDiyTokens`); simdata `getToken` merges them with the pick as the owner's
loadout. No id is also a tokens.json record.
Not in this data (the gameplay workstreams): a player's ownership roster, 助战 borrows (`borrowCount` 20), DIY stock, the
owned-operator training bonus (`prepareStateDict`), forms and kits of owned 6★ picks (the same `buildUnitForm` builds
them once kits exist).
owned-operator training bonus (`prepareStateDict`), potential (0 for every 自选 pick, [ASSUMED]: no account).
+1 -1
View File
@@ -21,7 +21,7 @@ In scope:
- Rendering with the real Spine battle chibis (PixiJS 7 + pixi-spine 4), procedural tiles, VFX, damage numbers, real BGM/SFX, emotes, broadcast ticker.
- Reconnect, AI take-over of disconnected players, robust validation of every client intent.
Out of scope v1: matchmaking queue, training/tutorial, DIY (甄选) slots (the 4 DIY chess are removed from the pool), trophies/progression persistence, reporting.
Out of scope v1: matchmaking queue, training/tutorial, DIY (甄选) slots (the 4 DIY chess are removed from the pool — 0.2.0 adds their data and battle side: DATA.md §18, shared/diy.js), trophies/progression persistence, reporting.
---
+21 -2
View File
@@ -887,6 +887,11 @@ true })`, §12): the def keeps the chess's ids, so `content/index.js kitOf` look
stand-in's own, `kits/ops/standin-<codename>.js` registered under that charId (kits/README.md "Stand-in kits") — never
by the chess id, which names the replaced operator's kit. A stand-in has no default skill: its kit authors each skill in
`skills` (`skill` is ignored); without a kit it gets the generic kit plus `genericTalents(def)` (§7.4).
自选 pieces (DATA.md §18; a PlayerBattleInput entry of a DIY slot with `diy: { charId, skillIndex, uniEquipId }` ⇒ the def
of `getChess(slotId, { diy })`, §12) take their kit the same way, `KITS[def.charId]` only (`content/index.js isDiyDef`): an
owned 6★'s `kits/ops/op-<codename>.js` (kits/index.js `OPERATOR_KIT_FILES`, kits/README.md "How to add an operator
(自选)"), a prototype's stand-in kit, a 预备干员's generic kit; no default skill either (the pick chooses any of the
three). `KITTED_CHARS` lists who has one — shared/diy.js offers no other pick.
Operator loadouts (DESIGN §16): the unit's def is `getChess(chessId, { skillIndex, moduleId })` of its PlayerBattleInput
entry (no loadout fields = the default). The skill spec is `kit.skills[selectedSkillId]` when authored, else `kit.skill`
only when the selected skill is the default one, else the GENERIC spec of the selected skill (its generic install is
@@ -1211,7 +1216,7 @@ Unknown subprofessions fall back to the profession default (test `professions.te
`['fx', kind, x, y, extra]` (`hitCap` `{ id, n }`: a leader's hit cancelled by 限伤 — the renderer draws nothing;
`extra.form` = the unit's model form from then on — an enemy's `content/enemies/helpers.js setForm`, a 傀儡师's 替身 — `shared/protocol.js fxForm`),
`['layer', playerId, bondId, n]` (n = the layers actually added, capped at 999), `['bounty', playerId, coins]`.
- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, form?, skillIndex?, moduleId?, items?, standInFor? }` (`standInFor`: a 补位 stand-in's replaced operator charId — `spine` / `avatar` / `name` are the stand-in's, DATA.md §18; the prep scouting units of `Match.prepFieldMeta` carry it too: a board piece with the stand-in's art, a held one with the chess's own; `skillIndex`: an ally's equipped skill, DESIGN §16; `form`: the unit's current model form — `content/enemies/helpers.js setForm`: 掠海漂移体 `'crawl'`, 暴鸰 `'bombed'` after its drop, 转译基底·α's forms …; a 傀儡师 fighting as its 替身 `'doll'` — so a view built mid-battle from `fieldMeta()` starts on that clip set; `items`: an ally operator's equipped item ids — a 变形同构体 wearer is a member of the bond it grants on the client too, the bond popup and the detail card's chips)
- `UnitInfo = { id, kind, side, ownerId, defId, name, tier, golden, spine, avatar, x, y, facing, dir, maxHp, motion?, boss?, uid?, form?, skillIndex?, moduleId?, items?, standInFor?, diy? }` (`standInFor`: a 补位 stand-in's replaced operator charId — `spine` / `avatar` / `name` are the stand-in's, DATA.md §18; the prep scouting units of `Match.prepFieldMeta` carry it too: a board piece with the stand-in's art, a held one with the chess's own; `diy`: a 自选 piece's pick `{ charId, skillIndex, uniEquipId }` — `defId` is its slot, `spine` / `avatar` / `name` the operator's; `skillIndex`: an ally's equipped skill, DESIGN §16; `form`: the unit's current model form — `content/enemies/helpers.js setForm`: 掠海漂移体 `'crawl'`, 暴鸰 `'bombed'` after its drop, 转译基底·α's forms …; a 傀儡师 fighting as its 替身 `'doll'` — so a view built mid-battle from `fieldMeta()` starts on that clip set; `items`: an ally operator's equipped item ids — a 变形同构体 wearer is a member of the bond it grants on the client too, the bond popup and the detail card's chips)
(`dir` = the unit direction, allies meaningful, enemies 'RIGHT'; `facing` = its horizontal sign for sprite flipping)
(`spine`/`avatar` are asset ids from data).
- flags: UF bits (blocked 1, stunned 2, frozen 4, stealth 8 — 隐匿 (an enemy's only while not blocked / revealed and not within 3 s of a block's end) or an ally's 迷彩 — skill 16, shield 32, invuln 64, cold 128, sleep 256, flying 512);
@@ -1250,7 +1255,10 @@ or `players`; `enemies:
`timeLimit`; `content` (`'full'|'generic'|'none'`); `kits` (inject `{ baseId: kitFn }`; a stand-in's by its charId); a unit's
`standIn: true` fields a NORMAL chess as its 补位 stand-in (the production path, the chess's backup skill / module; `_a` =
normal, `_b` = elite), `standIn: { skillIndex?, moduleId? }` another skill / module of it (the composed record `standInRec(chessId,
sel)` replaces that chess's record for the battle; kits/README.md "How to test a stand-in kit"); `defs: { chess, enemies, tokens }`
sel)` replaces that chess's record for the battle; kits/README.md "How to test a stand-in kit"); a unit `{ diy: { slot,
charId, skillIndex?, uniEquipId? }, elite?, row, col }` fields a 自选 piece the production way (`slot` = a DIY slot's base
id or its tier 5 / 6, `elite` = its `_b` form; `getChess(slotId, { diy })`, kits/README.md "How to add an operator
(自选)"); `defs: { chess, enemies, tokens }`
(extra/override records; build them with `chessRec({...})` / `enemyRec({...})`); `bonds`, `bandId`, `playerEffects`,
`flags`, `sharedBoss`, `setup(battle)`, `hooks` (names to capture; `captureNoisy` to keep tick/hit/damaged/heal/spGain/
attack contexts), `autoFinish` (default: true when enemies are scheduled).
@@ -1309,6 +1317,17 @@ the replaced operator's, `loadout` the backup selection (`standIn: true`); its s
("对于补位干员其技能不可更改", shared/standIn.js). A PRESET / DIY chess or a source without backups (a browser that did not
fetch backups.json) gives the chess's own def. A PlayerBattleInput entry carries it as `standIn: true` (spec.js keeps
nothing else); the per-battle view maps id-only lookups of that chess id to the stand-in.
自选 (DATA.md §18): `getChess(slotId, { diy })` = `getDiy(slotId, pick)` — shared/diy.js `diyRecordOf` of the DIY slot
record (`_a` normal, `_b` elite) with the pick over this source, normalised: `id` / `baseId` / `golden` / `tier` of the
slot, `bonds` the pick's derived ones, `raw.garrisonIds` `[]` (no 特质), every combat field of the operator at the slot's
status with the pick's skill and module (a prototype's locked ones), `charId` the operator's, `diyFor` the slot's base
id, `loadout` the selection with `diy` = the checked pick `{ charId, skillIndex, uniEquipId }`, `tokenOwner` =
`<charId>@<statusKey>`; null for an illegal pick (shared/diy.js `checkDiyPick`); cached per (id, pick). Its summons:
`getToken(id, slotId, def.loadout)` reads `rawBackups().tokens[id].variants[tokenOwner]` with `bySkill[skillIndex]` /
`byModule[uniEquipId]` merged (`getDiyToken`); `rawToken(id)` falls back to `backups.tokens`, and content/tokens.js /
`Battle.producesToken` key a 自选 owner's variant by `def.tokenOwner`. A PlayerBattleInput entry carries the pick as
`diy` (spec.js keeps `charId` / `skillIndex` / `uniEquipId` when well-formed); Battle fields a DIY slot only with a legal
pick (none ⇒ no unit), and each unit resolves its own pick — two players filling one slot id differently are exact.
Assumptions taken here (documented choices): element burst numbers for necrosis/apoptosis; unspawned enemies at timeout
are dropped (not leaks); geek drain is non-lethal; tactician reinforcement stats; displacement is instantaneous (the official 失衡 slide over ≈ 0.4–1.2 s is not
modelled — only its distance, `push` / `pull`); pushes PRTS does not classify use the 弹道 distance column (`PUSH_TILES`);