fix: 准备阶段显示外勤医疗预备干员 (#315)
This commit is contained in:
1 parent
3b57f72704
commit
9db9f3ceea
9 files changed
+132
-15
No files matched your search
@@ -784,6 +784,10 @@ PREP ready/acting · COMBAT/boss combat/done · UNITE helping/done · others don
|
||||
`m.private` = DESIGN §8.3 exactly (sent per player whenever it changed). `nextEnemies` = the current round's wave
|
||||
(+ the player's bounty enemies, tag `bounty`; boss rounds: the player's boss field, tag `boss` — the leader's entry with its
|
||||
spawn tile `start`, where the boss-field prep shows it —, + its bounties).
|
||||
During PREP, `prepMapChars` carries the read-only 预备干员-医疗 UnitInfo for every living player's field when any living
|
||||
player holds 外勤医疗. It uses the stage's normal-board map-character slot; the own board draws it separately from
|
||||
`board` (so it cannot be moved or sold), and `prepFieldMeta` includes it for scouts. In a boss prep, the same slot is
|
||||
mapped to each player's half, including `bossMate.units`. Touch replaces this reserve medic only at battle start.
|
||||
|
||||
Bond layers in the views (DESIGN §20.15): from the end of COMBAT (`_finishCombat`, every normal result in) until SETTLE,
|
||||
`m.private bonds` and `m.public players[].bonds` add the finished battle's IN_BATTLE gains (`PlayerState.pendingLayerGains`
|
||||
|
||||
@@ -77,6 +77,6 @@ Target: a **real 3D board layer** rendered with three.js (vendored ESM, no CDN)
|
||||
- One camera model drives both layers: `public/js/render/projection.js` keeps its public API (`worldToScreen`, `screenToWorld`/tile picking, presets, transitions) but derives it from a three.js `PerspectiveCamera` initialised from the official configBlackBoard framing, so Spine units, FX, drag/drop and hit-testing stay aligned with the 3D board.
|
||||
- Must degrade gracefully: when local-client art or WebGL2 features are missing, fall back to the current 2D atlas board; keep 60 fps (static geometry merged into few draw calls; textures mipmapped; DPR-aware).
|
||||
- Implemented (`public/js/render/board3d/*`, `render/app.js`): three.js is downloaded only when the local-art manifest lists the board atlas; the built 3D area, the drawn 2D rows and the lit rect follow `viewKind(kind, {rect})` (the field the camera actually shows: a 'prep' camera on rows ≤ 6 = `bossPrep`); the background plane is extended with mirrored copies and the clear colour matches the fog (no visible edge at any aspect); battle crates live in their own group (survive scene rebuilds) and follow the board mode (`switchableBox`: 3D mesh ⇄ 2D box on a mode switch or a lost WebGL context); quality 'low' drops shadows; `?board=2d|3d` overrides. The direction wheel's range stripes sit between the board canvas and the unit canvas.
|
||||
- Done: Final Assault / Hidden Core prep takes place on your own half of the boss field (research 09 §1.2): pieces shown at boss-field positions (row − 7; right-hand player mirrored col → 20 − col, RIGHT ↔ LEFT), drop targets and wheel directions mapped back to board coordinates (`prepCamera` → `bossPrep` in public/js/ui/gameLogic.js; covered by the playtest2 browser tests). A scout of such a board (`m.field` prep of a boss round) comes in boss-field coordinates with the player's `side` and is framed the same way, the leader standing at its spawn (0.2.0, community report item 55). Both players of a pair are shown together, as in the battle (0.2.0, community report item 51): the partner's board stands on the other half (`m.private.bossMate` units, already in boss-field coordinates: mirrored on the right half, RIGHT ↔ LEFT; read-only `m:<uid>` views of `render/app.js syncMates`), the whole boss field is lit, a scout carries both halves (`mate`), and the team panel frames the pair's avatars in the official green (`ui/battle bg_team_border` tinted, a plain ring without the local art; `gameLogic teamFrameIds`) from the round's start through the fight.
|
||||
- Done: Final Assault / Hidden Core prep takes place on your own half of the boss field (research 09 §1.2): pieces shown at boss-field positions (row − 7; right-hand player mirrored col → 20 − col, RIGHT ↔ LEFT), drop targets and wheel directions mapped back to board coordinates (`prepCamera` → `bossPrep` in public/js/ui/gameLogic.js; covered by the playtest2 browser tests). A scout of such a board (`m.field` prep of a boss round) comes in boss-field coordinates with the player's `side` and is framed the same way, the leader standing at its spawn (0.2.0, community report item 55). Both players of a pair are shown together, as in the battle (0.2.0, community report item 51): the partner's board stands on the other half (`m.private.bossMate` units, already in boss-field coordinates: mirrored on the right half, RIGHT ↔ LEFT; read-only `m:<uid>` views of `render/app.js syncReadOnlyPrepUnits`), the whole boss field is lit, a scout carries both halves (`mate`), and the team panel frames the pair's avatars in the official green (`ui/battle bg_team_border` tinted, a plain ring without the local art; `gameLogic teamFrameIds`) from the round's start through the fight. With 外勤医疗 selected, every living player's own prep, paired boss prep and scouted prep shows a read-only 预备干员-医疗 on its stage slot; the strategy's Touch replacement occurs only when combat begins.
|
||||
|
||||
---
|
||||
@@ -18,7 +18,7 @@ Platform additions (implemented): `welcome.{resumed, version}`, `welcome.diyKitt
|
||||
C→S (all carry `rid`): `g.infoReady` · `g.band {bandId}` · `g.bandSkip` · `g.bandFocus {bandId?}` (the strategy highlighted in the draft screen — what a timed-out turn takes; absent / null clears it; `WRONG_PHASE` outside BAND_DRAFT, `ALREADY` after the pick, `BAD_TARGET` for a band not allowed; §18.2) · `g.buy {slot}` · `g.refresh` · `g.freeze` · `g.levelUp` · `g.sell {uid}` · `g.move {uid, to:{area:'board', row, col}|{area:'hand', idx}, dir?}` · `g.equip {itemUid, targetUid, replaceUid?}` (`replaceUid`: which equipped item a third one replaces, §6.2) · `g.art {itemUid, row, col, dir?}` (`dir` ∈ UP|RIGHT|DOWN|LEFT from the deploy wheel, §3; absent ⇒ `to.dir`, then RIGHT; `g.move` onto the piece's own tile re-orients it; board pieces in `m.private` / `m.field {prep:true}` carry `dir`) · `g.destroy {uid}` (hand / temp items only; equipped items are locked, §6.2) · `g.reward {idx}` · `g.choice {idx, choiceId?}` (with an ID: the recipient's PREP personal choice; without one: the public SP_DRAFT; stale or wrong IDs never fall through to the public draft) · `g.ready {ready}` · `g.emote {id}` · `g.watch {fieldId, playerId?}` (`playerId`: the player tapped — a shared 联防 / boss pair field shows two; what an eliminated viewer follows, §25) · `g.autoplay {on}` · `g.pause {on}` (solo battles only, §14 Solo pause) · `g.unitStats {seq?}` (ROUND_START / SP_DRAFT / PREP, else `WRONG_PHASE`; eliminated: `ELIMINATED`) → push `m.unitStats` (§18.5) · `g.leave`
|
||||
S→C:
|
||||
- `m.public` — full public state on every change (throttled ≤ 10/s): `{ phase, round, lastRound, deadline, serverNow, modeId, difficulty, stageId, factions, disabledBonds, bannedChess, bossId, hiddenBossId?, teamLp?, bossHp?:{hp,max}, overtimeAt? /* FA / HC: ms epoch the overtime drain starts, §4 */, paused /* solo pause, §14 */, draft?:{order,turn,picks,skipsLeft,turnDeadline,turnSeconds /* one turn's length, 0 untimed; deadline = turnDeadline (§18.2) */,untimed}, sp?:{family,cards /* bounty cards carry descRaw?, §20.6 */,turn,picks}, players:[{playerId, seat, name, isBot, connected, alive, lp, bandId, shopLevel, boardCount, ready, bonds:[{bondId,count,active,tier,layers,harmony? /* 调和's +1 is in count, §21.26 */}], fieldId, status, pendingLp? /* COMBAT / 联防 of a normal round: the LP the player's own battle will cost at settlement so far, min(lpCapPerRound, counted leaks) — in 联防 min(lpCapPerRound, uniteLeft) — omitted when 0 and in every other phase (§17.5 live LP) */, uniteLeft? /* 联防: the leaker's enemies still standing — not spawned yet + alive + counted leaks, uncapped, live (§20.6) */}] , fields:[{fieldId, kind, players:[playerId...], progress?:{killed, resolved, total, done} /* §14 顶栏胶囊: resolved = the field's own scheduled enemies knocked down or leaked, null while unknown */}] }` · `m.public.uniteResult` — SETTLE after a 联防 only, its outcome as data: `{ through /* the leakers' enemies that still got through, uncapped */, helpers:[playerId...], leakers:[playerId...], losses:{playerId: the LP settlement charged this round — every alive player} }`; absent when no 联防 resolved (the client's result box then reads the round's own battle; GitHub #235, PR #112)
|
||||
- `m.private` — the recipient's full PlayerState view (§6.2 minus server-only data) on every change; in a boss round's prep also `bossMate { playerId, side, units }` — the pair partner's board on its half of the boss field (UnitInfo, mirrored on the right half), re-sent when the partner moves (0.2.0, community report item 51).
|
||||
- `m.private` — the recipient's full PlayerState view (§6.2 minus server-only data) on every change; during prep, `prepMapChars` shows the 外勤医疗 reserve medic on the stage's pre-existing slot without adding a movable board piece; in a boss round's prep also `bossMate { playerId, side, units }` — the pair partner's board and reserve medic on its half of the boss field (UnitInfo, mirrored on the right half), re-sent when the partner moves (0.2.0, community report item 51).
|
||||
- `m.field` — field meta when watching starts: `{ fieldId, kind, rect, stageId, units: [UnitInfo], effects? }` (the units on the field, knocked-out operators waiting to redeploy included — `Battle.fieldMeta`, §18.3). The prep-scouting field (`Match.prepFieldMeta`) puts the scouted player's **hand (整备区) and 临时整备区 into `units` as well**: held pieces as units on the hand row (row 7, col = hand slot) and temp pieces on the temp row (row 8, cols 4..8 = the temp slots; PRTS 帮助 counts the temp area with the hand; a held item is a `kind: 'item'` unit the client draws as its floating plate) — the scout renders exactly like the own prep bench, for every watcher alike, the spectator seat's copy equal to a teammate's (§23.19's "never a private view" stays the no-`m.private` rule); the scout signature `_prepScoutSig` carries the hand / temp slots, so a piece moved to another slot is a change — and carries `effects`: the scouted player's effects view (策略 / 机变 / 悬赏 …), the watched player's effects column ready-made
|
||||
- NOTE: game time in `b.snap`/`b.ev` is carried as **`gt`** (the frame's `t` is the message type).
|
||||
- Active finite zero-SP timed skills display `sp=timeLeft`, `spMax=duration` and `UF.SKILL`; after end they display
|
||||
|
||||
@@ -1578,8 +1578,8 @@ Notes for the integrator
|
||||
|
||||
### 27.64 外勤医疗's pre-battle map character retains its earlier aggro when Touch replaces it — `content/bands/battle.js installMapChars`
|
||||
|
||||
- **Seen**: the user observes 预备干员-医疗 already visible on the field during preparation. With two elite operators,
|
||||
`battleStart` creates Touch only after all board units have deployed. This gives the sim unit the latest `aggroSeq`;
|
||||
- **Seen**: the [official recording](https://b23.tv/JZLqdSO) at about 4:25 shows 预备干员-医疗 on the field during preparation,
|
||||
while this client's preparation view did not show it. With two elite operators, `battleStart` creates Touch only after all board units have deployed. This gives the sim unit the latest `aggroSeq`;
|
||||
a local boss test makes “萨米的意志” select Touch's column first. In the
|
||||
[official recording](https://b23.tv/JZLqdSO) at about 4:57, Touch stands at the right edge while 冰凌 falls on a
|
||||
different column. The recording supports a mismatch, but does not by itself isolate every other target's taunt.
|
||||
@@ -1587,10 +1587,8 @@ Notes for the integrator
|
||||
describes a 预备干员-医疗 appearing on every player's field and being replaced by Touch at combat start if at least two
|
||||
elite operators stand there. PRTS 索敌 places the last deployed target ahead at equal taunt. [ASSUMED]: the
|
||||
replacement retains the map character's pre-deployment aggro position; the sources do not fix its exact numeric rank.
|
||||
- **Now**: only the 外勤医疗 map character (预备干员-医疗 or Touch) gets an early `aggroSeq` after its `battleStart`
|
||||
spawn. Its actual deployment event and `deploySeq` stay unchanged. Operators, ordinary initial summons and
|
||||
mid-battle summons retain their existing priority.
|
||||
- **Now**: in PREP, every living player's own board and scouted board shows a read-only 预备干员-医疗 from the stage's map-character slot when any living player holds 外勤医疗; a boss pair's two medics appear on the respective halves. The prep unit does not become a movable `board` piece. At `battleStart`, only the 外勤医疗 map character (预备干员-医疗 or its Touch replacement) gets an early `aggroSeq`. Its actual deployment event and `deploySeq` stay unchanged. Operators, ordinary initial summons and mid-battle summons retain their existing priority.
|
||||
- **Golden**: one value moves in `test/golden/matches.json`: `coop2-NORMAL-14-standins`, player `h_0`'s
|
||||
`dmgDealt` 3,000,543 → 2,999,081. That match fields 外勤医疗, so its changed initial target order can alter damage;
|
||||
every other recorded result is unchanged. **Tests**: `test/content/bands.test.js` checks both variants' priority and
|
||||
“萨米的意志” 冰凌 selection on solo and two-player boss fields.
|
||||
“萨米的意志” 冰凌 selection on solo and two-player boss fields; `test/match/touch-prep.test.js` checks own, scouted and paired boss preparation views; the render browser test checks that the medic is visible, mirrored and not draggable.
|
||||
+13
-6
@@ -797,8 +797,16 @@ export async function createFieldView(host, options = {}) {
|
||||
if (e.area === 'board' && info.dir && !held.has(e.uid) && typeof v.setDir === 'function') v.setDir(info.dir);
|
||||
}
|
||||
for (const k of [...views.keys()]) if (String(k).startsWith('p:') && !seen.has(Number(String(k).slice(2)))) dropView(k);
|
||||
// the boss round's prep: the pair partner's board on its half of the boss field (item 51; display only)
|
||||
syncMates(prepXf.kind === 'bossPrep' && src.bossMate && Array.isArray(src.bossMate.units) ? src.bossMate.units : []);
|
||||
// The 外勤医疗 reserve medic is visible before battle but not a movable board piece. Map its board-space slot to
|
||||
// the displayed boss half exactly as for the player's own pieces; the partner's units already use field coordinates.
|
||||
const mapChars = Array.isArray(src.prepMapChars) ? src.prepMapChars.map((u) => {
|
||||
if (!u || !Number.isInteger(u.x) || !Number.isInteger(u.y)) return null;
|
||||
const p = prepXf.toDisp(u.y, u.x);
|
||||
const dir = prepXf.dirToDisp(u.dir);
|
||||
return { ...u, x: p.col, y: p.row, dir, facing: dir === 'LEFT' ? -1 : 1 };
|
||||
}).filter(Boolean) : [];
|
||||
const mate = prepXf.kind === 'bossPrep' && src.bossMate && Array.isArray(src.bossMate.units) ? src.bossMate.units : [];
|
||||
syncReadOnlyPrepUnits([...mapChars, ...mate]);
|
||||
prepPieces = list.filter((e) => e.key && views.has(e.key));
|
||||
if (dragState && !views.has(dragState.key)) { drag.reset(); endDragVisual(false); }
|
||||
holdScene(false); // the prep pieces reference their models now (a battle's hold ends here)
|
||||
@@ -806,11 +814,10 @@ export async function createFieldView(host, options = {}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The pair partner's pieces in the Final Assault / Hidden Core prep (m.private bossMate units: UnitInfo in boss-field
|
||||
* coordinates, facing as the battle will place them): read-only 'm:<uid>' views — not prep pieces, so they are never
|
||||
* picked, dragged or swept with the own ones; a view is rebuilt when its body, tile or facing changes.
|
||||
* The reserve medic and a boss pair's partner pieces: read-only 'm:<uid>' views. These are not prep pieces, so they
|
||||
* are never picked, dragged or swept with the own board; a view is rebuilt when its body, tile or facing changes.
|
||||
*/
|
||||
function syncMates(units) {
|
||||
function syncReadOnlyPrepUnits(units) {
|
||||
const keep = new Set();
|
||||
for (const u of units) {
|
||||
const info = u && Number.isInteger(u.uid) ? renderInfo({ ...u, id: `m:${u.uid}` }) : null;
|
||||
|
||||
@@ -236,10 +236,29 @@ export class MatchViews {
|
||||
return p ? { charId: p.charId, skillIndex: p.skillIndex, uniEquipId: p.uniEquipId } : undefined;
|
||||
}
|
||||
|
||||
/** The 外勤医疗 reserve medic already standing on each player's field in preparation. Touch replaces it only at
|
||||
* battleStart, so the prep view always uses the reserve medic, regardless of the current elite count. The stage's
|
||||
* normal-board slot is transformed to the correct half of a boss field by _onBossHalf / the client prep camera. */
|
||||
prepMapChars(ps) {
|
||||
if (!ps?.alive || this.phase !== PHASE.PREP || !this.order.some((p) => p.alive && p.bandId === 'band_amedic')) return [];
|
||||
const id = 'char_605_cmedic';
|
||||
const slot = this.stage?.mapChars?.find((m) => m?.key === id && Array.isArray(m.pos) && m.pos[0] >= 9 && m.pos[0] <= 12
|
||||
&& m.pos[1] >= 0 && m.pos[1] <= 10 && !/multi_only/.test(String(m.alias)));
|
||||
const rec = this.gd.token(id);
|
||||
if (!slot || !rec) return [];
|
||||
const dir = slot.dir || 'RIGHT';
|
||||
return [{
|
||||
id: -ps.seat - 1, uid: -ps.seat - 1, kind: 'token', side: 'ally', ownerId: ps.playerId, defId: id,
|
||||
area: 'board', name: rec.name || id, tier: 1, golden: false,
|
||||
spine: rec.assets?.spine || id, avatar: rec.assets?.avatar || id,
|
||||
x: slot.pos[1], y: slot.pos[0], dir, facing: dir === 'LEFT' ? -1 : 1, maxHp: rec.stats?.maxHp || 1,
|
||||
}];
|
||||
}
|
||||
|
||||
/** UnitInfo of a player's board pieces on their (board) tiles — a prep scout's board, the boss partner's (bossMateView).
|
||||
* `area: 'board'` (a bench unit says 'hand' / 'temp': prepFieldMeta). */
|
||||
_prepBoardUnits(ps) {
|
||||
const units = [];
|
||||
const units = this.prepMapChars(ps);
|
||||
// the player's own view of the data (0.2.0 自选编队: its slotted DIY slots are its operators — player/diy.js)
|
||||
const gd = ps.gd || this.gd;
|
||||
for (const { r, c, piece } of boardOrder(ps.board)) {
|
||||
|
||||
@@ -88,6 +88,8 @@ export class PlayerViews {
|
||||
hand: this.hand.map((p) => (p ? this.pieceView(p) : null)),
|
||||
temp: this.temp.map((p) => (p ? this.pieceView(p) : null)),
|
||||
board,
|
||||
// The stage's 外勤医疗 reserve medic is present during prep, but is not a draggable board piece.
|
||||
prepMapChars: this.m.prepMapChars(this),
|
||||
deployCap: this.deployCap,
|
||||
deployCount: this.deployCount,
|
||||
// + the mode-off bonds it has members of (`off: true`, the strip's grey 本局禁用 discs — bondsMeta.offBondCounts)
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 外勤医疗: the official preparation field already shows 预备干员-医疗; Touch replaces it at battle start.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeMatch } from './harness.js';
|
||||
|
||||
const MEDIC = 'char_605_cmedic';
|
||||
const STAGE = 'act2autochess_m01';
|
||||
|
||||
test('外勤医疗 shows the reserve medic on every prep board and in prep scouting, without adding a movable piece', () => {
|
||||
const h = makeMatch({ mode: 'coop', humans: 2, seed: 6301, fake: true }).start().toPrep();
|
||||
h.setStage(STAGE);
|
||||
const [holder, teammate] = [h.ps('p_0'), h.ps('p_1')];
|
||||
assert.deepEqual(holder.privateView().prepMapChars, [], 'no medic without the strategy');
|
||||
holder.bandId = 'band_amedic';
|
||||
for (const ps of [holder, teammate]) {
|
||||
const priv = ps.privateView();
|
||||
assert.equal(priv.prepMapChars.length, 1);
|
||||
const medic = priv.prepMapChars[0];
|
||||
assert.deepEqual([medic.defId, medic.kind, medic.x, medic.y, medic.dir, medic.ownerId],
|
||||
[MEDIC, 'token', 2, 10, 'RIGHT', ps.playerId]);
|
||||
assert.ok(medic.uid < 0 && !priv.board.some((p) => p.uid === medic.uid), 'display only: no board piece to move or sell');
|
||||
assert.equal(h.m.prepFieldMeta(ps).units.find((u) => u.uid === medic.uid)?.defId, MEDIC);
|
||||
}
|
||||
holder.bandId = null;
|
||||
assert.deepEqual(teammate.privateView().prepMapChars, [], 'the preview goes away when nobody holds the strategy');
|
||||
h.m.dispose();
|
||||
});
|
||||
|
||||
test('外勤医疗 boss preparation shows both reserve medics on their matching halves, mirrored for the right player', () => {
|
||||
const h = makeMatch({ mode: 'coop', humans: 2, seed: 6302, fake: true }).start().toPrep();
|
||||
h.setStage(STAGE);
|
||||
const m = h.m;
|
||||
m.bossWaves = [{ players: ['p_0', 'p_1'], wave: { spawns: [] } }];
|
||||
h.ps('p_0').bandId = 'band_amedic';
|
||||
const [left, right] = [h.ps('p_0'), h.ps('p_1')];
|
||||
const own = left.privateView().prepMapChars[0];
|
||||
const mate = left.privateView().bossMate.units.find((u) => u.defId === MEDIC);
|
||||
assert.deepEqual([own.y, own.x, own.dir], [10, 2, 'RIGHT'], 'own prep uses board coordinates');
|
||||
assert.deepEqual([mate.y, mate.x, mate.dir, mate.ownerId], [3, 18, 'LEFT', 'p_1']);
|
||||
const scout = m.prepFieldMeta(right);
|
||||
assert.equal(scout.kind, 'boss');
|
||||
const medics = scout.units.filter((u) => u.defId === MEDIC).sort((a, b) => a.x - b.x);
|
||||
assert.deepEqual(medics.map((u) => [u.y, u.x, u.dir, u.ownerId]),
|
||||
[[3, 2, 'RIGHT', 'p_0'], [3, 18, 'LEFT', 'p_1']]);
|
||||
assert.notEqual(medics[0].uid, medics[1].uid, 'both model IDs remain distinct on the shared field');
|
||||
m.dispose();
|
||||
});
|
||||
@@ -73,6 +73,46 @@ describe('render engine in headless Chrome', { skip }, () => {
|
||||
});
|
||||
}
|
||||
|
||||
test('外勤医疗 reserve medic is visible but not draggable in normal and right boss preparation', async () => {
|
||||
const { page, problems } = await open('scene=prep&panel=0', 1600, 900);
|
||||
const first = await page.evaluate(() => {
|
||||
const v = window.__demo.view;
|
||||
const st = JSON.parse(JSON.stringify(window.__demo.scene.state));
|
||||
st.prepMapChars = [{ id: -1, uid: -1, kind: 'token', side: 'ally', ownerId: 'p_0',
|
||||
defId: 'char_605_cmedic', name: '预备干员-医疗', spine: 'char_605_cmedic', avatar: 'char_605_cmedic',
|
||||
area: 'board', x: 2, y: 10, dir: 'RIGHT', facing: 1, maxHp: 1202 }];
|
||||
v.setPrep(st, { editable: true });
|
||||
const medic = v.debug.views.get('m:-1');
|
||||
const p = v.tileScreen(10, 2), rect = v.debug.app.view.getBoundingClientRect();
|
||||
const out = { exists: !!medic, id: medic?.info.defId, x: medic?.x, y: medic?.y,
|
||||
draggable: !!v.debug.pick.pieceAt(p.x - rect.left, p.y - rect.top) };
|
||||
return out;
|
||||
});
|
||||
await wait(500);
|
||||
await page.screenshot({ path: path.join(OUT, 'touch-prep-normal.png') });
|
||||
const boss = await page.evaluate(() => {
|
||||
const v = window.__demo.view;
|
||||
v.setCamera('bossPrep', { side: 'R', instant: true });
|
||||
const mirrored = v.debug.views.get('m:-1');
|
||||
return [mirrored?.x, mirrored?.y, mirrored?.dir];
|
||||
});
|
||||
await wait(500);
|
||||
await page.screenshot({ path: path.join(OUT, 'touch-prep-boss-right.png') });
|
||||
const removed = await page.evaluate(() => {
|
||||
const v = window.__demo.view;
|
||||
const st = JSON.parse(JSON.stringify(window.__demo.scene.state));
|
||||
st.prepMapChars = [];
|
||||
v.setPrep(st, { editable: true });
|
||||
return !v.debug.views.has('m:-1');
|
||||
});
|
||||
await page.close();
|
||||
assert.deepEqual(problems, []);
|
||||
assert.deepEqual(first, { exists: true, id: 'char_605_cmedic', x: 2, y: 10,
|
||||
draggable: false });
|
||||
assert.deepEqual(boss, [18, 3, 'LEFT']);
|
||||
assert.equal(removed, true);
|
||||
});
|
||||
|
||||
test('fx gallery: every sim fx kind, projectile and status renders without errors; board art in use', async () => {
|
||||
const { page, problems } = await open('scene=fx&panel=0', 1280, 720);
|
||||
// cycle through the whole FX_KINDS table (one kind every 0.5 game s = 0.25 real s), watching the FX system: shots
|
||||
|
||||
Reference in new issue
Block a user