feedback5: merge W5M — the nine 0.1.4 PRs (#150 #66 #146 #74 #72 #103 #149 #109 #160) and W4A (高台 per the trait, 阿戈尔 revive slots, 联防 devour) ported into the split layout

Conflicts: server/match/bot.js (W4A's comment + W5K's player data view pgd), test/golden/diy.json (regenerated). data/ equals an offline build of the merged tools/build-data.mjs. Golden regenerated on the merged code: against feedback5 the moved roster / bonds / fields / matches / standins scenarios are exactly W5M's per-PR attribution (#160 skill events, #109 deploy-timed lifecycle, #66 灵巧 sources and hidden layers, 83fc408 歌蕾蒂娅 placement), plus coop2-NORMAL-70-diy (fields operators those PRs touch) and diy-051 / diy-052 (events.skill only: Misery's deploy passives, #160); against fb5-merge4 only feedback5's own additions (the diy kits, coop2-NORMAL-70-diy).
This commit is contained in:
sganggs committed 2026-10-06 00:33:15 +08:00
commit 9ce5435373
74 files changed
+2966 -1338

No files matched your search

+3 -3
View File
@@ -68,7 +68,7 @@ export function BondStrip({ bonds, layersDisabled = false, onOpen, openId = null
const next = nextThreshold(b.count ?? 0, th);
return html`<div key=${b.bondId} role="listitem" data-bond=${b.bondId} data-harmony=${b.harmony > 0 ? b.harmony : null}
class=${cx('bslot', b.active && 'is-active', openId === b.bondId && 'is-open')}>
<${BondDisc} name=${rec?.name || b.bondId} icon=${bondIconUrl(m, b.bondId)} layers=${b.layers ?? 0}
<${BondDisc} name=${rec?.name || b.bondId} icon=${bondIconUrl(m, b.bondId)} layers=${rec?.noStack ? undefined : b.layers ?? 0}
tier=${b.tier ?? 0} maxTier=${Math.max(1, th.length)} active=${!!b.active} size="sm" showName=${true}
layersDisabled=${layersDisabled} onClick=${() => onOpen(b.bondId)}
title=${`${rec?.name || b.bondId} ${b.count ?? 0}/${next ?? th[th.length - 1] ?? '-'}${b.harmony > 0 ? `(含调和 +${b.harmony})` : ''}`} />
@@ -125,7 +125,7 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
<h3 class="bpop__name">${b.name}</h3>
<div class="bpop__facts">
${off ? null : html`<span>在场 <b class="num">${count}</b>${next != null ? html`<small class="num">/${next}</small>` : null}${countsHand ? html`<small>(含整备区)</small>` : null}${harmony ? html`<small class="bpop__hnote" data-harmony=${harmony}>(含${harmonyName} +${harmony})</small>` : null}</span>`}
<span>层数 <b class="num t-mint">${layers}</b></span>
${b.noStack ? html`<span>层数不显示</span>` : html`<span>层数 <b class="num t-mint">${layers}</b></span>`}
<span class=${active ? 't-mint' : 't-lo'}>${active ? `已激活${th.length > 1 ? ` · ${tier} 阶` : ''}` : off ? '本局禁用' : '未激活'}</span>
</div>
</div>
@@ -135,7 +135,7 @@ export function BondPopup({ bondId, entry, priv, banned = [], onClose, onMember,
${th.map((n, i) => html`<span key=${i} class=${cx('bpop__tier', i < tier && 'is-on')}><b class="num">${n}</b><small>${b.maxCount != null ? '名及以下' : '名'}</small></span>`)}
</div>
${hasNow ? html`<section class="bpop__sec bpop__sec--now">
<h4>当前效果 <small class="num">(${layers} 层)</small></h4>
<h4>当前效果 ${b.noStack ? null : html`<small class="num">(${layers} 层)</small>`}</h4>
<${RichText} as="p" text=${formatBondEffect(b, layers)} class="bpop__desc" />
</section>` : null}
<section class="bpop__sec">
+2 -2
View File
@@ -4,8 +4,8 @@
// light legal tiles while dragging; the server stays authoritative and may still refuse a move.
//
// Board = own normal field (GEO.FIELD rows 9–12, cols 2–10). Melee chess stand on `melee` deploy tiles
// (LOW, buildable ALL/MELEE) — elite 歌蕾蒂娅 carrying HOK-Y (shared/highGround.js, the player's loadout) on any
// deploy tile, the 高台 included (piecePosition 'ALL'); ranged chess on `melee ∪ rangedOnly` (stages.json → deployTiles.normal,
// (LOW, buildable ALL/MELEE) — a melee chess whose trait reads 「可以放置于远程位」 (shared/highGround.js: 歌蕾蒂娅, 崖心,
// 见行者, any module) on any deploy tile, the 高台 included (piecePosition 'ALL'); ranged chess on `melee ∪ rangedOnly` (stages.json → deployTiles.normal,
// derived from the tile legend when missing — the legend's `buildable` is the effective type: 深水区 tile_deepsea
// refuses deployment, PRTS 深水区 地形信息 "拒绝部署(待补充)", player report #3 after 0.1.0). Tokens follow their
// own `position`; a summon whose text reads "只能部署在召唤者攻击范围内" (tokens.json `ownerRange`: 伺夜's 狼群,
+6 -6
View File
@@ -6,7 +6,7 @@ import { meleeOnHighGround } from '../../../../shared/highGround.js';
import { pieceDir, rangeTiles } from '../facing.js';
import { isObj, tileKey } from './shared.js';
import { boardTileOf, fieldTile } from './camera.js';
import { deployedRecord, deployedModuleId, fieldsStandIn, standInOf } from './standIn.js';
import { deployedRecord, fieldsStandIn, standInOf } from './standIn.js';
// ---- placement (canPlace mirror) ------------------------------------------------------------------------
@@ -213,9 +213,10 @@ export function placementContext({ priv, stage, editable, field = 'normal', getC
}
/**
* Deploy position ('MELEE'|'RANGED'|'ALL') of a chess/token piece, or null for items. Elite 歌蕾蒂娅 carrying HOK-Y
* (the viewer's loadout, shared/highGround.js) is 'ALL': any deployable tile, the 高台 included
* (server/match/board.js placeClass; owner's decision 2026-10-04). Every other MELEE chess is ground-only.
* Deploy position ('MELEE'|'RANGED'|'ALL') of a chess/token piece, or null for items. A MELEE chess whose trait reads
* 「可以放置于远程位」 (shared/highGround.js: 歌蕾蒂娅, 崖心, 见行者, normal and elite, any module) is 'ALL': any deployable
* tile, the 高台 included (server/match/board.js positionClass; the owner's decision of 2026-10-05). Every other MELEE
* chess is ground-only.
*/
export function piecePosition(ctx, piece) {
if (!isObj(piece)) return null;
@@ -223,8 +224,7 @@ export function piecePosition(ctx, piece) {
const chess = ctx.getChess(piece.id);
// 0.2.0 补位: a chess the player fields as its stand-in is placed by the stand-in's position (server placeClass)
const rec = (fieldsStandIn(ctx.priv, chess) && standInOf(chess, ctx.backups)) || chess;
const moduleId = deployedModuleId(chess, ctx.priv, ctx.getChess, ctx.backups);
if (meleeOnHighGround(rec, moduleId)) return 'ALL';
if (meleeOnHighGround(rec)) return 'ALL';
return rec?.position === 'MELEE' ? 'MELEE' : 'RANGED';
}
// tokens: MELEE → ground only; RANGED / ALL → any deployable tile
+2 -2
View File
@@ -90,8 +90,8 @@ export function deployedRecord(chess, priv, getChess, backups) {
}
/**
* The module id a deployed piece carries (the placement rule of elite 歌蕾蒂娅's HOK-Y reads it): a stand-in's backup
* module, else the player's loadout.
* The module id a deployed piece carries: a stand-in's backup module, else the player's loadout. (The 高台 rule no
* longer reads it: since the owner's decision of 2026-10-05 it is the trait, whatever the module — shared/highGround.js.)
*/
export function deployedModuleId(chess, priv, getChess, backups) {
if (!isObj(chess)) return null;