Playtest #6 fixes: summons placed by hand, boss pool stall, bounties, 联防 leak counter, distinct offers, 机变 card text, phone buy tap, skill triggers, blocking, push force, 孤立, enemies, element bar

- #1 #2 skill / device summons (赫默's drone, 巫恋's doll, 凯瑟琳's devices) are hand cards placed by the player;
  placed summons deploy once at battle start, then a skill summon re-appears at its tile on each cast (the user's call)
- #3 叙拉古 隐匿 ends on time (32 + 0.4 × layers s); 忍冬 S3 迷彩 lasts until her next cast (official), kept apart from 隐匿
- #4 multi-round bounty cards last two battles (the user's call; MULTI_ROUND_BOUNTY_BATTLES), blue / red card text
- #5 the shared boss pool's float dust (≈3.6e-12 HP) stalled Final Assault leaders at 0 %; < 1 HP now clears the pool,
  and the team LP reaching 0 first is a defeat (PRTS)
- #6 #10 机变 cards show their official effect text; an invisible 详情 button no longer swallows the confirming tap on phones
- #7 live ×N counter of a leaker's enemies still on the 联防 field (LP loss stays capped at 10)
- #8 #15 #16 skill triggers per 技能策略: skill-range triggers, injured-ally heals (古米, 塞雷娅), tank take-damage
  triggers with a 3 s cooldown; 新约能天使 S2 shields the highest-aggro operator in range
- #9 official per-enemy model scales (威龙)
- #11 element bar under the unit's HP bar (white = 元素值 left, element-coloured refill after a burst), floor rounding
- #12 枯朽战车 kit per PRTS; every ranged enemy reaches its target's 0.25 collider
- #13 恐惧 wander (萨科塔之翼 at half HP, operator fears make enemies flee) with re-planning after pushes
- #14 push / pull distance by 力度 − 重量 (PRTS table); #17 every blocker attacks what it blocks; ASPD floor 20
- #18 炎佑 is 孤立: no heals or ally buffs
- #19 every free pick-one chess offer (promotion reward, 信标, 寻呼模块) shows different operators
- DESIGN §20 and the normative lines, SIM / META / DATA / PLAYING updated; 2747 unit tests and all 27 browser suites
  green; dev recordings regenerated
This commit is contained in:
sganggs committed 2026-10-01 19:08:23 +08:00
1 parent e6628f0cfe
commit 6464e4b46a
138 files changed
+7946 -1379

No files matched your search

+5 -4
View File
@@ -57,12 +57,12 @@ html:not(.sp-in-match) .toast-host { margin-top: var(--sa-t); }
control, no layout change. Only controls that do not use ::before themselves are listed. */
:where(.sp-coarse) :where(.tapx, .gtop__exit, .gtop__iconbtn, .enemybtn, .readybtn, .gm__gear, .fsbtn, .ewheel__btn, .ewheel__nav,
.toolbtn, .funds__collapse, .team__btn, .effect, .bslot .bond, .vswitch__arrow, .edrawer__close, .dpanel__close, .bpop__close,
.modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .scard__detail, .title-guide, .stephead__guide, .set-toggle, .set-seg button) {
.modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .title-guide, .stephead__guide, .set-toggle, .set-seg button) {
position: relative;
}
.sp-coarse :is(.tapx, .gtop__exit, .gtop__iconbtn, .enemybtn, .readybtn, .gm__gear, .fsbtn, .ewheel__btn, .ewheel__nav,
.toolbtn, .funds__collapse, .team__btn, .effect, .bslot .bond, .vswitch__arrow, .edrawer__close, .dpanel__close, .bpop__close,
.modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .scard__detail, .title-guide, .stephead__guide, .set-toggle, .set-seg button)::before {
.modal__close, .uf__btn, .chud__back, .tabs__tab, .btn, .pill-btn, .title-guide, .stephead__guide, .set-toggle, .set-seg button)::before {
content: '';
position: absolute; left: 50%; top: 50%; z-index: 0;
width: max(100%, var(--tap-min)); height: max(100%, var(--tap-min));
@@ -113,8 +113,9 @@ html:not(.sp-in-match) .toast-host { margin-top: var(--sa-t); }
}
.sp-reduced-motion .team__status.is-gold, .sp-reduced-motion .ff-tile--gate { animation: none; }
/* the shop card's ⓘ corner: a smaller hit area (the card itself is the main target; its first tap shows the detail) */
.sp-coarse .scard__detail::before { width: 30px; height: 30px; }
/* (the shop cards have no ⓘ corner: its invisible 44 px touch area — the 30 px override here lost on specificity to the
generic rule above — covered the bottom-right quarter of a phone's 67 × 97 px card and half of its 确认购买 strip, so
the confirming tap re-opened the detail instead; user playtest #6 item 10) */
/* Narrow viewports (< 768 CSS px: the root scale is clamped at 40 px, so the layout is narrower than the 19.2 rem
design): the shop bar would cover the corner's ⚙ / 📖 / ⛶ buttons — they wrap into a row above the 交流 button. */
+57 -20
View File
@@ -340,12 +340,15 @@
.spov__who.is-done .spov__wstate { color: var(--mint-400); }
.spov__who.is-cur .spov__wstate { color: var(--gold); }
.spov__grid { flex: 1; min-height: 0; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(2, 1fr); gap: .2rem; }
.spov__grid--3 { grid-template-columns: repeat(3, minmax(0, 4.3rem)); grid-template-rows: minmax(0, 5rem); justify-content: center; align-content: start; gap: .36rem; margin-top: .3rem; }
.spov__grid--3 .spcard { justify-content: flex-start; padding-top: .62rem; }
.spov__grid--3 .spcard__icon { width: 1.6rem; height: 1.6rem; margin-bottom: .1rem; }
.spov__grid--3 .spcard--bounty .spcard__icon { width: 1.4rem; height: 1.4rem; }
.spov__grid--3 { grid-template-columns: repeat(3, minmax(0, 4.3rem)); grid-template-rows: minmax(0, 4.6rem); justify-content: center; align-content: start; gap: .36rem; margin-top: .3rem; }
.spov__grid--3 .spcard__icon { width: 1.3rem; height: 1.3rem; }
/* The official card (research 06 §11.5; the official 悬赏 / 机密商店 / 战术 screenshots): a framed icon at the top-left
with the bold name beside it, the effect text under both across the card, left-aligned; the tier chip on the icon's
corner, the taker's round avatar at the top-right (user playtest #6 item 6: the centred icon + name used to push the
text out of a phone-sized card). --sp-px: the card's side padding; --tk / --tk-at: the taker avatar's size / inset. */
.spcard {
position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .12rem; padding: .3rem .3rem .24rem; overflow: hidden; text-align: center;
--sp-px: .2rem; --tk: .5rem; --tk-at: .08rem;
position: relative; display: flex; flex-direction: column; align-items: stretch; justify-content: flex-start; gap: .12rem; padding: .16rem var(--sp-px); overflow: hidden; text-align: left;
background: linear-gradient(180deg, rgba(210, 224, 219, .12), rgba(120, 140, 132, .06)); border: 1px solid rgba(200, 220, 212, .25); color: var(--text-hi);
transition: transform var(--t-fast) var(--ease-out), border-color var(--t-fast), background var(--t-fast), opacity var(--t-med);
}
@@ -357,24 +360,34 @@
.spcard.is-taken > :not(.spcard__taker) { opacity: .38; filter: grayscale(.7); }
.spcard.is-mine { border-color: var(--mint-400); box-shadow: inset 0 0 0 1px var(--mint-400); }
.spcard.is-mine > :not(.spcard__taker) { opacity: 1; filter: none; }
.spcard__tier { position: absolute; left: .12rem; top: .12rem; }
.spcard__icon { width: 1.3rem; height: 1.3rem; display: grid; place-items: center; flex: none; }
.spcard__tier { position: absolute; left: .08rem; top: .08rem; z-index: 1; }
.spcard__head { flex: none; display: flex; align-items: center; gap: .18rem; min-width: 0; }
.spcard__icon { width: 1rem; height: 1rem; display: grid; place-items: center; flex: none; padding: .04rem;
border: 2px solid rgba(214, 228, 222, .7); background: radial-gradient(circle at 50% 40%, rgba(255, 255, 255, .1), rgba(0, 0, 0, .32)); }
.spcard__icon img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 .06rem .12rem rgba(0, 0, 0, .6)); }
.spcard__icon .icon { width: .7rem; height: .7rem; color: var(--mint-500); }
.spcard--bounty .spcard__icon { width: 1.1rem; height: 1.1rem; border: 2px solid rgba(255, 90, 74, .5); background: radial-gradient(circle, #3a1c18, #140d0c); }
.spcard__icon .icon { width: 60%; height: 60%; color: var(--mint-500); }
.spcard--bounty .spcard__icon { padding: 0; border-color: rgba(255, 90, 74, .55); background: radial-gradient(circle, #3a1c18, #140d0c); }
.spcard--bounty .spcard__icon img { object-fit: cover; }
.spcard__body { display: flex; flex-direction: column; align-items: center; gap: .08rem; min-height: 0; }
.spcard__name { font-size: .26rem; font-weight: 900; letter-spacing: .06em; }
.spcard__desc { font-size: .16rem; line-height: 1.65; color: var(--text-md); max-width: 4.6rem; display: -webkit-box; -webkit-line-clamp: 4; -webkit-box-orient: vertical; overflow: hidden; }
.spov__grid--3 .spcard__desc { -webkit-line-clamp: 8; }
.spcard__tags { display: flex; gap: .08rem; }
.spcard__title { display: flex; flex-direction: column; align-items: flex-start; gap: .06rem; min-width: 0; }
.spcard__name { font-size: .28rem; font-weight: 900; letter-spacing: .04em; line-height: 1.25; }
/* the effect text: the official rich text (highlighted numbers, set bonuses, line breaks), clamped by lines — 7 fit a 3×2
card (the longest item, 信标, takes 7 at 16:9 and on phones narrower than ≈ 740 px); it never shrinks below them
(flex: none) */
.spcard__desc { flex: none; font-size: .2rem; line-height: 1.4; color: var(--text-md); display: -webkit-box; -webkit-line-clamp: 7; -webkit-box-orient: vertical; overflow: hidden; }
.spov__grid--3 .spcard__desc { -webkit-line-clamp: 10; }
.spcard__tags { display: flex; flex-wrap: wrap; gap: .06rem; }
.spcard__tag { font-size: .13rem; font-weight: 700; padding: .01rem .08rem; }
.spcard__tag--team { background: var(--mint-500); color: var(--text-on-mint); }
.spcard__tag--coin { background: var(--gold); color: #1d1400; }
.spcard__taker { position: absolute; right: .12rem; top: .12rem; display: flex; align-items: center; gap: .06rem; padding: .03rem .1rem .03rem .03rem;
background: rgba(8, 11, 10, .92); border: 1px solid var(--mint-500); border-radius: .3rem; font-size: .13rem; color: var(--mint-400); font-weight: 700; }
.spcard__taker .pavatar { --pa: .4rem; border-radius: 50%; overflow: hidden; border: 2px solid var(--mint-400); }
/* the taker's badge: the round avatar alone at the card's top-right corner, as in the official screenshots (the player's
name is its tooltip and part of the card's accessible name); a taken card's head keeps clear of it, so a long name
wraps before the badge instead of running under it (the avatar + name pill of v2.4.1 covered long names once the name
moved into the top row) */
.spcard__taker { position: absolute; right: var(--tk-at); top: var(--tk-at); z-index: 1; display: block; border-radius: 50%;
box-shadow: 0 0 0 1px rgba(8, 11, 10, .9), 0 .04rem .12rem rgba(0, 0, 0, .6); }
.spcard__taker .pavatar { --pa: var(--tk); border-radius: 50%; overflow: hidden; border: 2px solid var(--mint-400); }
.spcard.is-taken .spcard__taker { filter: none; }
.spcard.is-taken .spcard__head { padding-right: calc(var(--tk) + var(--tk-at) - var(--sp-px) + .08rem); }
/* the card being picked (g.choice in flight): mint frame + a 选择中 strip with the sweeping busy bar of the buttons
(components.css btn-busy) — no spinner over the card's text (user playtest #3 item 9) */
.spcard.is-busy { border-color: var(--mint-400); box-shadow: inset 0 0 0 1px var(--mint-400); }
@@ -393,13 +406,37 @@
.spov.has-armed .spcard.is-pickable:not(.is-armed) { filter: brightness(.7); }
.spov.has-armed .spcard.is-pickable:not(.is-armed):hover { filter: none; }
.spcard__confirm {
position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; flex-direction: column; align-items: center;
padding: .05rem 0 .05rem; background: linear-gradient(0deg, var(--gold), #e0a800); color: #1d1400; pointer-events: none;
position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; display: flex; align-items: baseline; justify-content: center; gap: .1rem;
padding: .04rem 0 .05rem; background: linear-gradient(0deg, var(--gold), #e0a800); color: #1d1400; pointer-events: none;
animation: confirm-in var(--t-fast) var(--ease-out);
}
.spcard__confirm b { font-size: .18rem; font-weight: 900; letter-spacing: .22em; padding-left: .22em; }
.spcard__confirm small { font-size: .11rem; letter-spacing: .1em; opacity: .75; }
.spcard__confirm small { font-size: .12rem; letter-spacing: .1em; opacity: .75; }
.spov__confirm { animation: pulse-gold 1.2s var(--ease-in-out) infinite; }
/* Short landscape phones (height < 10.8rem: the root size is clamped at 40 px, theme.css, so the page is only ≈ 9rem
tall): a tighter header (no CONTINGENCY micro line, smaller margins) gives the grid ≈ .65rem more, and a smaller icon
and tighter spacing leave a 3×2 card room for 7 lines of text (≈ 4 – 5.4 × 2.9rem: 162 × 116 px at 640×360, the
smallest supported phone; 201 × 119 at 756×366, the user's Android) — every shop item, bounty and tactic text fits in
full, taken or not (test/ui/playtest6-ui.e2e.test.js). */
@media (max-height: 431.98px) {
.spov__inner { top: .84rem; bottom: .22rem; gap: .1rem; }
.spov__titles > .micro { display: none; }
.spov__grid { gap: .12rem; }
.spcard { --sp-px: .12rem; --tk: .4rem; --tk-at: .04rem; gap: .06rem; padding: .08rem var(--sp-px); }
.spcard__tier { left: .03rem; top: .03rem; }
.spcard__head { gap: .12rem; }
.spcard__icon { width: .72rem; height: .72rem; padding: .02rem; }
.spov__grid--3 .spcard__icon { width: 1rem; height: 1rem; }
.spcard__name { font-size: .23rem; }
.spcard__desc { font-size: .19rem; }
.spov__grid--3 .spcard__desc { -webkit-line-clamp: 10; }
}
/* The narrowest of them (640×360): with Noto Sans SC — the page's web font and Android's CJK font — the longest text
(海沟实验体) wraps to 8 lines in a 162 px card, one more than the clamp; a slightly tighter line height gives the 8th
line room (QA, playtest #6). */
@media (max-height: 431.98px) and (max-width: 699.98px) {
.spcard__desc { line-height: 1.3; -webkit-line-clamp: 8; }
}
/* Grid image boxes: definite 100% tracks so `height: 100%` images resolve against the box (an auto track would
let non-square art overflow its frame). */
+7 -4
View File
@@ -82,6 +82,8 @@
.lvcard.is-max { background: linear-gradient(180deg, #d9b24a, #a07a1c); color: #1d1400; cursor: default; filter: none; }
/* ---- operator / item card ---- */
/* A tap anywhere on a card is the card's tap (no ⓘ corner — user playtest #6 item 10); the 确认购买 strip takes no
pointer. The detail opens with the first tap, a right-click / long press, or a tap while the bar is not editable. */
.scard-wrap { display: flex; }
.scard {
--tc: var(--tier-1);
@@ -126,14 +128,11 @@
.scard__bond .bglyph { width: .18rem; height: .18rem; }
.scard__class { display: flex; align-items: center; gap: .05rem; font-size: .13rem; color: var(--text-lo); }
.scard__prof { width: .18rem; height: .18rem; object-fit: contain; opacity: .85; }
.scard__detail { position: absolute; right: .04rem; bottom: .04rem; z-index: 3; width: .24rem; height: .24rem; display: grid; place-items: center; color: var(--text-dim); opacity: 0; transition: opacity var(--t-fast), color var(--t-fast); cursor: help; }
.scard__detail .icon { width: .18rem; height: .18rem; }
.scard:hover .scard__detail { opacity: 1; }
.scard__detail:hover { color: var(--mint-400); }
.scard__mergetag { position: absolute; z-index: 3; right: 0; top: .38rem; font-size: .12rem; font-weight: 700; padding: .01rem .06rem; background: var(--gold); color: #1d1400; letter-spacing: .08em; }
.scard:not(.is-disabled):not(.scard--sold):hover { transform: translateY(-.08rem); border-color: var(--mint-400); box-shadow: 0 0 0 1px var(--mint-400), 0 .1rem .24rem rgba(23, 249, 183, .22); }
.scard:not(.is-disabled):not(.scard--sold):active { transform: translateY(-.04rem); }
.scard.is-disabled { cursor: not-allowed; }
.shopbar.is-locked .scard:not(.scard--sold) { cursor: help; } /* not editable: a tap opens the detail */
.scard.is-disabled .scard__art, .scard.is-disabled .scard__itemart { filter: grayscale(.7) brightness(.6); }
.scard.is-merge { border-color: var(--gold); box-shadow: 0 0 0 1px rgba(255, 198, 0, .6), 0 0 .2rem rgba(255, 198, 0, .3); }
.scard.is-frozen::after { content: ''; position: absolute; inset: 0; z-index: 2; pointer-events: none;
@@ -204,6 +203,10 @@
@keyframes pulse-gold { 50% { box-shadow: 0 0 .24rem rgba(255, 198, 0, .5); } }
/* ---- two-tap buy / upgrade (research 09 §5): the first tap lifts + enlarges the card and shows 确认购买 ---- */
/* while a card is armed the bar sits above the detail card its first tap opened (.dpanel, z-index 9): on phones narrower
than 768 px the left-docked detail reaches down over the bar's first cards (bottom 87 px from the screen's bottom,
devices.css) and took the confirming tap on their top quarter (640×360: 108 of 144 points; user playtest #6 item 10) */
.shopbar.has-armed { z-index: 10; }
.scard.is-armed {
z-index: 3; transform: translateY(-.16rem) scale(1.08) !important; border-color: var(--gold);
box-shadow: 0 0 0 2px var(--gold), 0 .14rem .32rem rgba(0, 0, 0, .65), 0 0 .3rem rgba(255, 198, 0, .35) !important;
+16
View File
@@ -116,6 +116,22 @@
.capsule__kills { font-size: .26rem; color: var(--text-lo); letter-spacing: .04em; }
.capsule__kills b { color: #fff; font-size: .3rem; }
.capsule__tag { font-size: .14rem; padding: .01rem .08rem; background: var(--orange); color: #1a0800; font-weight: 700; letter-spacing: .1em; }
/* 联防: a leaker's enemies still standing (MissTag, user playtest #6 item 7) — the official bg_miss_enemy pill (runner ×
on its left 3/5, the number on the rest) or an orange look-alike */
.misstag {
display: inline-flex; align-items: center; justify-content: flex-end; gap: .03rem; height: .26rem; min-width: .84rem;
padding: 0 .1rem 0 .08rem; border-radius: .13rem; background: #3b1a08; border: 1px solid var(--orange-hi);
color: #fff; font-weight: 700; line-height: 1; flex: none;
}
.misstag.has-art { border: 0; border-radius: 0; background: transparent no-repeat left center / 100% 100%; width: .84rem; padding-right: .12rem; }
.misstag__icon { display: inline-flex; align-items: center; gap: .01rem; margin-right: auto; color: var(--orange-hi); font-size: .16rem; }
.misstag__icon .icon { width: .16rem; height: .16rem; }
.misstag__n { font-size: .2rem; }
.misstag.is-clear .misstag__n { color: var(--mint-400); }
.team__line .misstag { height: .21rem; min-width: .68rem; padding-right: .08rem; }
.team__line .misstag.has-art { width: .68rem; }
.team__line .misstag__n { font-size: .16rem; }
.team__line .misstag__icon { font-size: .13rem; }
.capsule--combat { --cap: var(--orange-hi); }
.capsule--boss { --cap: var(--orange-hi); min-width: 4.2rem; }
.bossbar { position: relative; flex: 1; min-width: 2.2rem; height: .2rem; background: rgba(0, 0, 0, .6); border: 1px solid rgba(255, 122, 51, .5); overflow: hidden; }