feedback7: merge fb7-loadout — per-operator 潜能 (1–6) and 练度 (未精英化 / 精英1 / 精英2 / 精英2 Lv.60) set in 干员调配, defaults 潜能 6 and 精英2 Lv.60 (the owner's decision of 2026-10-08); PR #301 ported as one aligned list; #398 the skill frame, #400 REA-Y's heal text
- Conflicts (the branch started at ae22335c): the four i18n packs (union of keys), game.js DetailPanel props (ops + the
voices' voice=true), detailPanel.js resolveDetail (cultOpts + the voices' tap), match/views.js (both helpers),
docs/PLAYING.md and design/network.md merged line by line against the base. test/match/bosshp.test.js's ATK formula
now includes the unit's 练度 multiplier (cultMul).
- Grok review: MERGE AFTER FIXES — the note and the module card at the set potential fixed on the branch.
- Golden: every owned operator ×1.1 ATK / DEF / max HP by default — roster 49, bonds 46, fields 22, matches 18, diy 134
(stand-in battles and prototype picks unchanged). Broad node --test 5666 (0 fail after the bosshp formula; 2 skipped);
lint 0 errors; typecheck; i18n strict clean (1152 msgids).
This commit is contained in:
commit
0ced21d321
82 files changed
+9156
-6840
No files matched your search
@@ -28,6 +28,7 @@
|
||||
|
||||
import { genericKit, genericTalents } from './generic.js';
|
||||
import { withUnitLoadouts } from '../simdata.js';
|
||||
import { isPotential } from '../../../shared/potential.js';
|
||||
|
||||
// Content files are loaded with guarded dynamic imports: a module that fails to load (syntax error, throwing
|
||||
// top-level code, missing file) is logged and replaced by an empty module instead of breaking the server.
|
||||
@@ -136,7 +137,8 @@ function inputEntry(unit) {
|
||||
* The loadout `{ skillIndex, moduleId }` a unit's PlayerBattleInput entry gives; an entry without loadout fields means
|
||||
* the DEFAULT loadout (`{}`), never "whatever the data view maps this chess id to" (another player's choice in a
|
||||
* multi-player field). `standIn: true` rides along (补位: the stand-in def whatever the skill / module fields say), as
|
||||
* does `diy` (自选: the pick of a DIY slot's piece). Null when the unit has no input entry.
|
||||
* do `diy` (自选: the pick of a DIY slot's piece) and `potential` (0.2.2: 1–6, never a stand-in's). Null when the unit
|
||||
* has no input entry.
|
||||
*/
|
||||
function inputLoadout(unit) {
|
||||
const x = inputEntry(unit);
|
||||
@@ -144,6 +146,7 @@ function inputLoadout(unit) {
|
||||
const extra = {
|
||||
...(x.standIn === true ? { standIn: true } : null),
|
||||
...(x.diy && typeof x.diy === 'object' ? { diy: x.diy } : null),
|
||||
...(x.standIn !== true && isPotential(x.potential) ? { potential: x.potential } : null),
|
||||
};
|
||||
if (x.skillIndex == null && x.moduleId == null) return extra;
|
||||
return { skillIndex: x.skillIndex ?? null, moduleId: x.moduleId ?? null, ...extra };
|
||||
|
||||
@@ -206,7 +206,7 @@ hook), and clear the owner's `mem.summonStock[tokenId]` when the skill takes the
|
||||
`units: [op, { uid, kind: 'token', tokenId, ownerUid, row, col }]` (`test/content/op_bgsnow.test.js`); the match side is
|
||||
covered by `test/match/diy-shop.test.js` (a DIY 鸿雪's 打字机 from prep to battle). A 召唤师 whose talent holds a deck of
|
||||
summons ("可以使用5个召唤物(最多同时部署3个)") takes `shared/summoner.js summonDeck` — the holding, a placed piece's return
|
||||
on its tile, recalls, its pieces' kits (`op-mgllan.js`, `op-ling.js`, `op-radian.js`). Every pick fights at full potential, as every chess (the owner's decision of 2026-10-07: data/backups.json forms, build-data `OPERATOR_POTENTIAL`). A new kit makes the operator a legal pick at once:
|
||||
on its tile, recalls, its pieces' kits (`op-mgllan.js`, `op-ling.js`, `op-radian.js`). Every owned pick fights at its player's potential and 练度, as every chess (default 潜能 6 and 精英2 Lv.60 — the owner's decision of 2026-10-08; data/backups.json forms are built at full potential and carry the lower ranks, DATA.md §2.3); a prototype pick has neither. A new kit makes the operator a legal pick at once:
|
||||
the server's `welcome.diyKitted` lists `KITTED_CHARS`, so the 自选编队 picker offers it and `room.diy` keeps it.
|
||||
|
||||
**The fidelity rule and the checklist** above apply item by item: every skill at rank 4 and 7, every talent, every
|
||||
|
||||
@@ -58,6 +58,7 @@ import { num, talentBb, moduleBb, traitBb, up, toggleBuff, summonTileFree } from
|
||||
import { absoluteRangeKeys, canTargetEnemy } from '../../../targeting.js';
|
||||
import { bodyOnTile } from '../../../body.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { atPotential } from '../../../../../shared/potential.js';
|
||||
|
||||
const S1 = 'skchr_doroth_1';
|
||||
const S2 = 'skchr_doroth_2';
|
||||
@@ -112,7 +113,7 @@ export default {
|
||||
/** The token's deploy limit for her loadout (variant stats, the module's when it carries one); her max stock too. */
|
||||
function limitOf(battle, unit) {
|
||||
const raw = battle.data.rawToken?.(RESONATOR);
|
||||
const v = raw?.variants?.[unit.def?.tokenOwner];
|
||||
const v = atPotential(raw?.variants?.[unit.def?.tokenOwner], unit.def?.loadout?.potential);
|
||||
const mod = unit.def?.loadout?.moduleId;
|
||||
const n = num(v?.byModule?.[mod]?.stats?.deployLimit, num(v?.stats?.deployLimit, num(raw?.deployLimit, 0)));
|
||||
if (n > 0) return n;
|
||||
|
||||
@@ -59,6 +59,7 @@ import { num, skillRec, batMod, up } from '../shared/tier1.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { dirVec } from '../../../dir.js';
|
||||
import { hypot } from '../../../detmath.js';
|
||||
import { atPotential } from '../../../../../shared/potential.js';
|
||||
|
||||
const S1 = 'skchr_mcnist_1';
|
||||
const S2 = 'skchr_mcnist_2';
|
||||
@@ -286,7 +287,7 @@ export default {
|
||||
},
|
||||
talents: [
|
||||
{ install(battle, unit) { // 结构性原理: her pieces run the token's kit (set up before the battle starts)
|
||||
const raw = battle.data.rawToken?.(MCGRAF)?.variants?.[unit.def?.tokenOwner] ?? null;
|
||||
const raw = atPotential(battle.data.rawToken?.(MCGRAF)?.variants?.[unit.def?.tokenOwner] ?? null, unit.def?.loadout?.potential);
|
||||
for (const t of battle.allyUnits) {
|
||||
if (!isGrafOf(t, unit) || t.alive || t.deployed) continue;
|
||||
// its life: the base variant's (the SO-A stage-3 skill@duration −1 is 集成战略-only)
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
// its owner), else the engine's removal reason.
|
||||
|
||||
import { num, up } from './tier1.js';
|
||||
import { atPotential } from '../../../../../shared/potential.js';
|
||||
|
||||
/** Seconds between two tries of a piece waiting to come back (its owner off the field, timer, holding, DP, tile). */
|
||||
export const DECK_RETRY = 0.25;
|
||||
@@ -73,8 +74,9 @@ export function summonTriggerArea(battle, owner, skillId, area) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A field of a summon's token record at its owner's form and module (data/backups.json `tokens[id].variants[<charId>@
|
||||
* <status>]`, `byModule[uniEquipId]`): the stats the normalised def drops (`deployLimit`, `deckStack`).
|
||||
* A field of a summon's token record at its owner's form, module and potential (data/backups.json `tokens[id].variants[<charId>@
|
||||
* <status>]`, `byModule[uniEquipId]`; shared/potential.js atPotential): the stats the normalised def drops (`deployLimit`,
|
||||
* `deckStack`).
|
||||
* @param {object} battle
|
||||
* @param {object} owner the summoner unit
|
||||
* @param {string} tokenId
|
||||
@@ -82,7 +84,7 @@ export function summonTriggerArea(battle, owner, skillId, area) {
|
||||
*/
|
||||
export function tokenStat(battle, owner, tokenId, key) {
|
||||
const raw = battle.data?.rawToken?.(tokenId) ?? null;
|
||||
const v = raw?.variants?.[owner?.def?.tokenOwner] ?? null;
|
||||
const v = atPotential(raw?.variants?.[owner?.def?.tokenOwner] ?? null, owner?.def?.loadout?.potential);
|
||||
const mod = owner?.def?.loadout?.diy?.uniEquipId ?? owner?.def?.loadout?.moduleId ?? null;
|
||||
const st = (mod && v?.byModule?.[mod]?.stats) || v?.stats || raw?.stats || null;
|
||||
return st && Number.isFinite(st[key]) ? st[key] : (Number.isFinite(raw?.[key]) ? raw[key] : null);
|
||||
|
||||
@@ -97,6 +97,7 @@ import { bardRegen } from '../professions.js';
|
||||
import { normDir, localOrder } from '../dir.js';
|
||||
import { SKILL_SUMMON_START_DEPLOY } from '../../../shared/constants.js';
|
||||
import { hypot } from '../detmath.js';
|
||||
import { atPotential } from '../../../shared/potential.js';
|
||||
|
||||
const num = (v, d = 0) => (typeof v === 'number' && Number.isFinite(v) ? v : (typeof v === 'string' && v.trim() !== '' && Number.isFinite(+v) ? +v : d));
|
||||
const GRID_3X3 = Object.freeze([[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1, -1], [-1, 0], [-1, 1]]);
|
||||
@@ -161,7 +162,8 @@ function ownVariant(raw, owner) {
|
||||
const oid = owner?.def?.tokenOwner ?? owner?.defId;
|
||||
if (!vs || typeof vs !== 'object' || !oid) return null;
|
||||
const v = vs[oid] ?? vs[String(oid).replace(/_b$/, '_a')] ?? null;
|
||||
return v ? withLoadout(v, owner.def?.loadout) : null;
|
||||
// at the owner's potential (0.2.2: a 自选 summon's deploy limit / count, the talents — shared/potential.js)
|
||||
return v ? withLoadout(atPotential(v, owner.def?.loadout?.potential), owner.def?.loadout) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user