fix: 再部署重置浮游倍率;roster-026、solo-HARD-1、solo-ABYSS-1 不再沿用旧缓存
This commit is contained in:
1 parent
bc50cac01f
commit
7434e9eaf0
8 files changed
+210
-11
No files matched your search
@@ -246,7 +246,7 @@
|
||||
"combat": "client",
|
||||
"boost": null,
|
||||
"setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]},
|
||||
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":341377,"virtualMs":107800},
|
||||
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":341949,"virtualMs":107800},
|
||||
"rng": {"setup":25,"shop":215,"waves":0,"draft":42,"bots":8070,"meta":1},
|
||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||
"rounds": [
|
||||
@@ -299,7 +299,7 @@
|
||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||
"trophies": 0,
|
||||
"reward": 340,
|
||||
"stats": {"activatedLayers":212,"bossDamage":438623,"dmgDealt":2261523,"gold":135,"itemsEquipped":16,"kills":402,"leaks":14,"lpLost":14,"merges":4,"perfectRounds":10,"refreshes":17},
|
||||
"stats": {"activatedLayers":212,"bossDamage":438051,"dmgDealt":2260950,"gold":135,"itemsEquipped":16,"kills":402,"leaks":14,"lpLost":14,"merges":4,"perfectRounds":10,"refreshes":17},
|
||||
"lineup": ["3_13_a@12,5[1_01_e_b+3_08_e_a]","3_20_a@11,8[5_02_e_a]","4_13_a@10,3","6_19_a@10,5[1_02_e_a+3_11_e_a]","6_17_a@10,8[2_01_e_a+5_01_e_a]","2_08_a@9,3[4_02_e_a+3_07_e_b]","1_19_b@9,4","1_12_b@9,5[4_10_e_a]"],
|
||||
"bonds": ["deputShip 0/24","kazimierzShip 1/26","kjeragShip 1/15","sargonShip 1/41","skillfulShip 1/31","soloShip 0/15","steadShip 0/11","suntShip 1/22","swiftShip 1/77"]
|
||||
}
|
||||
@@ -384,7 +384,7 @@
|
||||
"combat": "client",
|
||||
"boost": null,
|
||||
"setup": {"stageId":"act1autochess_m03","bossId":"boss_5","hiddenBossId":"boss_8","factions":["TIMES","DOT","INVISIBLE"]},
|
||||
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":2832671,"virtualMs":107800},
|
||||
"end": {"victory":false,"roundsPassed":13,"hiddenReached":false,"hiddenCleared":false,"reason":"defeat","teamLp":0,"bossPoolLeft":2833084,"virtualMs":107800},
|
||||
"rng": {"setup":25,"shop":215,"waves":0,"draft":42,"bots":8070,"meta":1},
|
||||
"errors": {"engine":0,"logged":0,"sim":0,"dispatcher":0},
|
||||
"rounds": [
|
||||
@@ -437,7 +437,7 @@
|
||||
"title": {"id":"comment_2","name":"不朽盟约","picId":"comment_icon_2","text":"激活的盟约层数总和最高"},
|
||||
"trophies": 0,
|
||||
"reward": 340,
|
||||
"stats": {"activatedLayers":220,"bossDamage":167329,"dmgDealt":3078206,"gold":135,"itemsEquipped":16,"kills":386,"leaks":22,"lpLost":20,"merges":4,"perfectRounds":11,"refreshes":17},
|
||||
"stats": {"activatedLayers":220,"bossDamage":166916,"dmgDealt":3077793,"gold":135,"itemsEquipped":16,"kills":386,"leaks":22,"lpLost":20,"merges":4,"perfectRounds":11,"refreshes":17},
|
||||
"lineup": ["3_20_a@12,5[5_02_e_a]","4_13_a@11,8","3_13_a@10,3[1_01_e_b+3_08_e_a]","2_08_a@10,5[4_02_e_a+3_07_e_b]","6_17_a@10,8","1_12_b@9,4[4_10_e_a]","6_19_a@9,5[1_02_e_a+3_11_e_a]","1_19_b@9,9[2_01_e_a+5_01_e_a]"],
|
||||
"bonds": ["deputShip 0/24","kazimierzShip 1/26","kjeragShip 1/15","sargonShip 1/41","skillfulShip 1/31","soloShip 0/15","steadShip 0/11","suntShip 1/22","swiftShip 1/85"]
|
||||
}
|
||||
|
||||
@@ -1630,7 +1630,7 @@
|
||||
"bossHpLeft": null,
|
||||
"rngDraws": 179,
|
||||
"errors": {"count":0,"kinds":[]},
|
||||
"players": {"p1":{"killed":19,"total":71,"perfect":false,"leaks":54,"leaksUncounted":0,"coins":6,"dmg":212430,"bossDmg":0,"heal":2065,"deaths":22,"layerGains":{}}},
|
||||
"players": {"p1":{"killed":19,"total":71,"perfect":false,"leaks":54,"leaksUncounted":0,"coins":6,"dmg":210891,"bossDmg":0,"heal":2065,"deaths":22,"layerGains":{}}},
|
||||
"units": [
|
||||
["p1",1,"op","chess_char_3_12_a",0,null,0,0,99,2,9831,6171,2065,21,4],
|
||||
["p1",2,"op","chess_char_3_16_a",0,null,0,0,101,0,4246,4781,0,15,1],
|
||||
@@ -1639,7 +1639,7 @@
|
||||
["p1",5,"op","chess_char_3_09_a",0,null,1393.7,1,103,1,16928,1394,0,24,3],
|
||||
["p1",6,"op","chess_char_3_10_a",0,null,0,0,89,1,36230,3630,0,22,5],
|
||||
["p1",7,"op","chess_char_3_11_a",0,null,0,0,7,1,12325,1448,0,6,1],
|
||||
["p1",8,"op","chess_char_3_13_a",0,null,0,0,53,0,3936,5199,0,23,1],
|
||||
["p1",8,"op","chess_char_3_13_a",0,null,0,0,53,0,2397,5199,0,23,1],
|
||||
["p1",9,"op","chess_char_3_14_a",0,null,0,0,79,0,13946,2752,0,15,0],
|
||||
["p1",10,"op","chess_char_3_17_a",0,null,1218.8,1,96,4,42240,3722,0,49,9],
|
||||
["p1",11,"op","chess_char_3_19_a",0,null,0,0,77,1,20696,3166,0,32,0],
|
||||
@@ -1653,7 +1653,7 @@
|
||||
"enemies": {
|
||||
"enemy_10042_prtrop": [12,0,12,7101,28836],
|
||||
"enemy_10042_prtrop_2": [12,0,10,6218,18414],
|
||||
"enemy_10043_sailor": [39,12,27,20980,197254],
|
||||
"enemy_10043_sailor": [39,12,27,20980,195715],
|
||||
"enemy_1005_yokai_2": [1,1,0,68,2678],
|
||||
"enemy_10156_mncrer": [1,1,0,140,6912],
|
||||
"enemy_1116_liprr_2": [1,1,0,247,12960],
|
||||
@@ -1667,7 +1667,7 @@
|
||||
"hooks": [1,102,4200,412,412,1525,1585,10,42,95,33,33,0,10321,22,47,73,52,1,46,0,0,0],
|
||||
"events": {"atk":478,"bounty":3,"deploy":26,"die":43,"dmg":1585,"engage":14,"fx":26,"heal":10,"leak":52,"skill":66,"spawn":90,"status":54},
|
||||
"fx": {"dp":7,"explode":3,"revive":4,"sp":4,"strike":1,"summon":2,"telegraph":2,"wolfShadow":1,"wolfShadowLost":2},
|
||||
"snaps": "226d72ef 4944a14f b2954de1 522e52cb dd46cce1 b1febb8b ac7e7cf4 bb6af571 080c3ba3 fc1505bd c0528264 4a64f7c2 beffba7c a423b42e"
|
||||
"snaps": "226d72ef 4944a14f b2954de1 3ffcf693 a30dc502 d84524d6 4ead34e9 4b042eb0 8e0173d6 43359664 a5bda569 a184d2eb 172129c9 aa1a4797"
|
||||
},
|
||||
"roster-027": {
|
||||
"about": "pass 1 band 4: chess_char_3_06_b chess_char_3_12_b chess_char_3_02_b chess_char_3_03_b chess_char_3_04_b chess_char_3_05_b chess_char_3_21_a chess_char_3_01_b chess_char_3_08_b chess_char_3_09_b chess_char_3_10_b chess_char_3_11_b",
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// Real production Match + Battle, legal normal intents from an empty board. Seed 8 fixes the shop/wave route.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { Battle } from '../../server/sim/Battle.js';
|
||||
import { makeMatch } from './harness.js';
|
||||
|
||||
test('Rockrock killed by a real enemy and automatically revived by Ermengard starts the same target at init', () => {
|
||||
const observed = [];
|
||||
class ObservedBattle extends Battle {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
const log = { round: opts.round, deaths: [], deploys: [], firstHits: [] };
|
||||
observed.push(log);
|
||||
const seen = new Set(), launches = new Map();
|
||||
this.on('death', c => {
|
||||
if (c.unit.defId === 'chess_char_2_10_a') log.deaths.push({ t: this.time, seq: c.unit.deploySeq,
|
||||
target: c.unit.trait.funnelTarget, scale: c.unit.trait.funnelScale, reason: c.reason });
|
||||
}, { priority: 1000 });
|
||||
this.on('death', c => {
|
||||
if (c.unit.defId === 'chess_char_2_10_a') log.deaths.at(-1).revivedBy = c.revivedBy;
|
||||
}, { priority: -1000 });
|
||||
this.on('deploy', c => {
|
||||
if (c.unit.defId === 'chess_char_2_10_a') log.deploys.push({ seq: c.unit.deploySeq,
|
||||
target: c.unit.trait.funnelTarget, scale: c.unit.trait.funnelScale });
|
||||
}, { priority: -1000 });
|
||||
this.on('attack', c => {
|
||||
if (c.attacker.defId !== 'chess_char_2_10_a') return;
|
||||
// This unmodified Rockrock route has no nested attack callback. The attack hook follows its shot creation.
|
||||
launches.set(this._attackSeq, { t: this.time, seq: c.attacker.deploySeq });
|
||||
});
|
||||
this.on('damaged', c => {
|
||||
const u = c.source;
|
||||
const launch = launches.get(c.dmg?.attackId);
|
||||
if (u?.defId !== 'chess_char_2_10_a' || seen.has(u.deploySeq) || launch?.seq !== u.deploySeq) return;
|
||||
seen.add(u.deploySeq);
|
||||
log.firstHits.push({ t: this.time, launchAt: launch.t, seq: u.deploySeq, target: c.target.id,
|
||||
scale: u.trait.funnelScale, hp: c.target.hp });
|
||||
});
|
||||
}
|
||||
}
|
||||
const h = makeMatch({ mode: 'solo', seed: 8 });
|
||||
h.m.BattleClass = ObservedBattle; // read-only observers, no combat state writes
|
||||
const p = h.ps('p_0'), act = msg => assert.deepEqual(h.m.handle('p_0', msg), { ok: true });
|
||||
try {
|
||||
h.start().toPrep(1, { band: 'band_ermengard' });
|
||||
act({ t: 'g.buy', slot: 0 });
|
||||
act({ t: 'g.move', uid: p.hand.find(x => x?.id === 'chess_char_1_09_a').uid,
|
||||
to: { area: 'board', row: 12, col: 6 }, dir: 'RIGHT' });
|
||||
h.toPrep(2, { band: 'band_ermengard' });
|
||||
act({ t: 'g.levelUp' });
|
||||
h.toPrep(5, { band: 'band_ermengard' });
|
||||
assert.equal(p.shop.slots[3].id, 'chess_char_2_10_a');
|
||||
act({ t: 'g.buy', slot: 3 });
|
||||
act({ t: 'g.move', uid: p.hand.find(x => x?.id === 'chess_char_2_10_a').uid,
|
||||
to: { area: 'board', row: 12, col: 8 }, dir: 'RIGHT' });
|
||||
h.toPrep(7, { band: 'band_ermengard' });
|
||||
const log = observed.find(x => x.round === 6);
|
||||
assert.ok(log?.deaths.length > 0);
|
||||
const paired = log.deaths.map(d => ({ death: d, hit: log.firstHits.find(x => x.seq > d.seq) }))
|
||||
.filter(x => x.hit && x.death.target === x.hit.target && x.death.scale > 0.2);
|
||||
assert.ok(paired.length > 0, JSON.stringify(log));
|
||||
for (const { death, hit } of paired) {
|
||||
assert.equal(death.reason, 'killed');
|
||||
assert.equal(death.revivedBy, 'band');
|
||||
assert.ok(hit.launchAt >= death.t, 'fresh attack after the knockout and revival');
|
||||
assert.ok(hit.hp > 0, 'original target still lives');
|
||||
const deploy = log.deploys.find(x => x.seq === hit.seq);
|
||||
assert.equal(deploy.target, null);
|
||||
assert.ok(Math.abs(deploy.scale - 0.2) < 1e-8);
|
||||
assert.ok(Math.abs(hit.scale - 0.2) < 1e-8, JSON.stringify({ death, hit }));
|
||||
}
|
||||
assert.deepEqual(h.logs.error, []);
|
||||
h.invariants();
|
||||
} finally { h.m.dispose(); }
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
// PRTS 分支特性信息/data §驭械术师: changing targets / redeploying resets the drone ramp.
|
||||
// Controlled battle inputs here; the real Match route is covered in test/match/funnel-redeploy.test.js.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { makeBattle, enemyRec, checkInvariants } from '../helpers/battleHarness.js';
|
||||
|
||||
const make = (id = 'chess_char_2_10_a', extra = []) => makeBattle({
|
||||
units: [{ chessId: id, row: 10, col: 4 }, ...extra],
|
||||
defs: { enemies: { dummy: enemyRec({ key: 'dummy', hp: 1e7, speed: 0 }) } },
|
||||
enemies: [{ key: 'dummy', pos: [10, 6] }], timeLimit: 300, autoFinish: false,
|
||||
hooks: ['damaged'], captureNoisy: true,
|
||||
});
|
||||
const hits = (h, u) => h.hooksOf('damaged').filter(c => c.source === u && c.target.side === 'enemy');
|
||||
const near = (a, b) => assert.ok(Math.abs(a - b) < 1e-8, `${a} != ${b}`);
|
||||
const ramp = (h, u) => {
|
||||
h.runUntil(() => hits(h, u).length >= 8, 20);
|
||||
assert.ok(hits(h, u).length >= 8);
|
||||
};
|
||||
|
||||
for (const id of ['chess_char_2_10_a', 'chess_char_2_10_b', 'chess_char_4_03_a', 'chess_char_4_03_b']) {
|
||||
test(`${id}: same living target starts at the configured initial scale after automatic redeployment`, () => {
|
||||
const h = make(id), u = h.unit(id);
|
||||
ramp(h, u);
|
||||
const e = h.enemies()[0];
|
||||
assert.ok(u.trait.funnelScale > u.profile.funnel.init);
|
||||
const before = hits(h, u).length;
|
||||
h.b.dealDamage(e, u, { amount: 1e7, type: 'true' });
|
||||
assert.equal(u.alive, false);
|
||||
h.runUntil(() => u.alive, 100);
|
||||
assert.equal(u.alive, true);
|
||||
assert.equal(e.alive, true);
|
||||
assert.equal(u.trait.funnelTarget, null);
|
||||
near(u.trait.funnelScale, u.profile.funnel.init);
|
||||
assert.equal(h.runUntil(() => hits(h, u).length > before, 5), true);
|
||||
assert.ok(hits(h, u).length > before);
|
||||
assert.equal(hits(h, u).at(-1).target, e);
|
||||
near(u.trait.funnelScale, u.profile.funnel.init);
|
||||
checkInvariants(h.b);
|
||||
assert.deepEqual(h.b.errors, []);
|
||||
});
|
||||
}
|
||||
|
||||
for (const id of ['chess_char_3_13_a', 'chess_char_3_13_b', 'chess_char_3_20_a', 'chess_char_3_20_b']) {
|
||||
test(`${id}: per-target funnel map restarts on redeployment`, () => {
|
||||
const h = make(id), u = h.unit(id);
|
||||
ramp(h, u);
|
||||
const e = h.enemies()[0];
|
||||
assert.ok(u.trait.funnelMap.get(e.id).s > u.profile.funnel.init);
|
||||
h.b.retreat(u);
|
||||
assert.equal(h.b.redeploy(u), true);
|
||||
assert.equal(u.trait.funnelMap.size, 0);
|
||||
const before = hits(h, u).length;
|
||||
assert.equal(h.runUntil(() => hits(h, u).length > before, 5), true);
|
||||
assert.ok(hits(h, u).length > before);
|
||||
near(u.trait.funnelMap.get(e.id).s, u.profile.funnel.init);
|
||||
checkInvariants(h.b);
|
||||
});
|
||||
}
|
||||
|
||||
test('continuous attacks ramp and cap; changing the living target resets normally', () => {
|
||||
const h = make(), u = h.unit('chess_char_2_10_a');
|
||||
ramp(h, u);
|
||||
near(u.trait.funnelScale, u.profile.funnel.max);
|
||||
const replacement = h.spawn('dummy', { pos: [10, 5] });
|
||||
const before = hits(h, u).length;
|
||||
assert.equal(h.runUntil(() => hits(h, u).length > before, 5), true);
|
||||
assert.ok(hits(h, u).length > before);
|
||||
assert.equal(hits(h, u).at(-1).target, replacement);
|
||||
near(u.trait.funnelScale, u.profile.funnel.init);
|
||||
});
|
||||
|
||||
test('another unit deployment and a free move keep this ramp; unrelated trait state survives redeployment', () => {
|
||||
const h = make('chess_char_2_10_a', [{ chessId: 'chess_char_1_02_a', row: 12, col: 3 }]);
|
||||
const u = h.unit('chess_char_2_10_a'), other = h.unit('chess_char_1_02_a');
|
||||
ramp(h, u);
|
||||
const scale = u.trait.funnelScale, target = u.trait.funnelTarget;
|
||||
u.trait.testUnrelated = { value: 7 }; // controlled sentinel: no production trait object may be replaced
|
||||
const sentinel = u.trait.testUnrelated;
|
||||
h.b.retreat(other);
|
||||
assert.equal(h.b.redeploy(other), true);
|
||||
assert.equal(u.trait.funnelTarget, target);
|
||||
near(u.trait.funnelScale, scale);
|
||||
assert.equal(h.b.moveRedeploy(u, 10, 3), true); // owner-approved move carry, DESIGN §22.3
|
||||
assert.equal(u.trait.funnelTarget, target);
|
||||
near(u.trait.funnelScale, scale);
|
||||
h.b.retreat(u);
|
||||
assert.equal(h.b.redeploy(u), true);
|
||||
assert.equal(u.trait.testUnrelated, sentinel);
|
||||
near(u.trait.funnelScale, u.profile.funnel.init);
|
||||
});
|
||||
Reference in new issue
Block a user