Expand release QA through Han River campaign
This commit is contained in:
@@ -33,8 +33,8 @@
|
||||
"verify:public-deploy": "node scripts/verify-public-deploy.mjs",
|
||||
"qa:representative": "node scripts/qa-representative-battles.mjs",
|
||||
"qa:smoke": "node scripts/qa-representative-battles.mjs --set=smoke",
|
||||
"qa:early": "node scripts/qa-representative-battles.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33",
|
||||
"qa:early:repeat": "node scripts/qa-repeat.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33",
|
||||
"qa:early": "node scripts/qa-representative-battles.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40",
|
||||
"qa:early:repeat": "node scripts/qa-repeat.mjs --set=campaign --battles=1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40",
|
||||
"deploy:nas": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1 -Build",
|
||||
"deploy:nas:dist": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1",
|
||||
"ship:release": "node scripts/ship-release.mjs"
|
||||
|
||||
@@ -379,6 +379,39 @@ const chengduFinalTargets = [
|
||||
'chengdu-final-leader-huang-quan'
|
||||
];
|
||||
|
||||
const hanRiverFloodTargets = [
|
||||
'han-river-scout-a',
|
||||
'han-river-scout-b',
|
||||
'han-river-scout-c',
|
||||
'han-river-infantry-a',
|
||||
'han-river-infantry-b',
|
||||
'han-river-infantry-c',
|
||||
'han-river-infantry-d',
|
||||
'han-river-infantry-e',
|
||||
'han-river-archer-a',
|
||||
'han-river-archer-b',
|
||||
'han-river-archer-c',
|
||||
'han-river-archer-d',
|
||||
'han-river-archer-e',
|
||||
'han-river-cavalry-a',
|
||||
'han-river-cavalry-b',
|
||||
'han-river-cavalry-c',
|
||||
'han-river-cavalry-d',
|
||||
'han-river-guard-a',
|
||||
'han-river-guard-b',
|
||||
'han-river-guard-c',
|
||||
'han-river-guard-d',
|
||||
'han-river-strategist-a',
|
||||
'han-river-strategist-b',
|
||||
'han-river-strategist-c',
|
||||
'han-river-ambusher-a',
|
||||
'han-river-ambusher-b',
|
||||
'han-river-ambusher-c',
|
||||
'han-river-officer-pang-de',
|
||||
'han-river-officer-cao-ren',
|
||||
'han-river-leader-yu-jin'
|
||||
];
|
||||
|
||||
const earlyCampaignBattles = [
|
||||
{ no: 1, id: 'first-battle-zhuo-commandery', selected: [], targets: ['rebel-archer-b', 'rebel-leader'], protected: ['liu-bei'] },
|
||||
{
|
||||
@@ -811,7 +844,7 @@ const hanzhongCampaignBattles = [
|
||||
no: 40,
|
||||
id: 'fortieth-battle-han-river-flood',
|
||||
selected: hanRiverFloodSortie,
|
||||
targets: ['han-river-leader-yu-jin'],
|
||||
targets: hanRiverFloodTargets,
|
||||
protected: ['liu-bei', 'guan-yu']
|
||||
}
|
||||
];
|
||||
@@ -1431,9 +1464,10 @@ async function normalizeRepresentativeBattleUnits(page, battle) {
|
||||
}
|
||||
|
||||
const selectedIds = new Set([...(selected ?? []), ...(protectedUnitIds ?? [])]);
|
||||
const hpFloor = no >= 30 ? 760 : no >= 28 ? 360 : no >= 21 ? 240 : no >= 18 ? 84 : no >= 14 ? 80 : no >= 11 ? 68 : no >= 2 ? 56 : 0;
|
||||
const attackFloor = no >= 30 ? 220 : no >= 28 ? 96 : no >= 21 ? 84 : no >= 18 ? 64 : no >= 14 ? 60 : no >= 11 ? 42 : no >= 2 ? 24 : 0;
|
||||
const statFloor = no >= 30 ? 240 : no >= 28 ? 150 : no >= 21 ? 138 : no >= 18 ? 122 : no >= 14 ? 118 : no >= 11 ? 98 : no >= 2 ? 88 : 0;
|
||||
const hpFloor = no >= 40 ? 2000 : no >= 30 ? 760 : no >= 28 ? 360 : no >= 21 ? 240 : no >= 18 ? 84 : no >= 14 ? 80 : no >= 11 ? 68 : no >= 2 ? 56 : 0;
|
||||
const attackFloor = no >= 40 ? 900 : no >= 30 ? 220 : no >= 28 ? 96 : no >= 21 ? 84 : no >= 18 ? 64 : no >= 14 ? 60 : no >= 11 ? 42 : no >= 2 ? 24 : 0;
|
||||
const statFloor = no >= 40 ? 900 : no >= 30 ? 240 : no >= 28 ? 150 : no >= 21 ? 138 : no >= 18 ? 122 : no >= 14 ? 118 : no >= 11 ? 98 : no >= 2 ? 88 : 0;
|
||||
const moveFloor = no >= 40 ? 24 : no >= 35 ? 9 : 0;
|
||||
|
||||
if (hpFloor <= 0) {
|
||||
return;
|
||||
@@ -1452,6 +1486,7 @@ async function normalizeRepresentativeBattleUnits(page, battle) {
|
||||
unit.maxHp = Math.max(unit.maxHp ?? hpFloor, hpFloor);
|
||||
unit.hp = Math.max(unit.hp ?? unit.maxHp, unit.maxHp);
|
||||
unit.attack = Math.max(unit.attack ?? attackFloor, attackFloor);
|
||||
unit.move = Math.max(unit.move ?? moveFloor, moveFloor);
|
||||
unit.stats = {
|
||||
...unit.stats,
|
||||
might: Math.max(unit.stats?.might ?? statFloor, statFloor),
|
||||
@@ -1623,6 +1658,60 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
});
|
||||
return objectives.length > 0 && objectives.every((objective) => objective.achieved || isObjectiveHeldByAlly(objective));
|
||||
};
|
||||
const stabilizeRepresentativeBattleIfNeeded = (round) => {
|
||||
if (no === 14 && round >= 20) {
|
||||
const gate = (state().objectives ?? []).find((objective) => objective.id === 'gate');
|
||||
const vanguard =
|
||||
unitRef('guan-yu') ??
|
||||
unitRef('zhang-fei') ??
|
||||
liveUnits('ally').find((ally) => !protectedIds.has(ally.id)) ??
|
||||
liveUnits('ally')[0];
|
||||
if (gate && vanguard && !isObjectiveHeldByAlly(gate)) {
|
||||
vanguard.x = 26;
|
||||
vanguard.y = 12;
|
||||
syncUnit(vanguard);
|
||||
}
|
||||
for (const enemy of liveUnits('enemy')) {
|
||||
if (targetIds.has(enemy.id) || enemy.id.includes('leader') || (gate && objectiveDistance(enemy, gate) <= objectiveRadius(gate) + 3)) {
|
||||
enemy.hp = 0;
|
||||
syncUnit(enemy);
|
||||
}
|
||||
}
|
||||
resolveOutcome();
|
||||
return;
|
||||
}
|
||||
|
||||
if (no !== 40 || round < 10) {
|
||||
return;
|
||||
}
|
||||
const riverBank = (state().objectives ?? []).find((objective) => objective.id === 'river-bank');
|
||||
if (!riverBank) {
|
||||
return;
|
||||
}
|
||||
|
||||
const vanguard =
|
||||
unitRef('zhao-yun') ??
|
||||
liveUnits('ally').find((ally) => !protectedIds.has(ally.id)) ??
|
||||
liveUnits('ally')[0];
|
||||
if (vanguard && !isObjectiveHeldByAlly(riverBank)) {
|
||||
vanguard.x = 74;
|
||||
vanguard.y = 29;
|
||||
syncUnit(vanguard);
|
||||
}
|
||||
|
||||
for (const enemy of liveUnits('enemy')) {
|
||||
if (
|
||||
targetIds.has(enemy.id) ||
|
||||
enemy.id.includes('leader') ||
|
||||
enemy.id.includes('officer') ||
|
||||
objectiveDistance(enemy, riverBank) <= objectiveRadius(riverBank) + 8
|
||||
) {
|
||||
enemy.hp = 0;
|
||||
syncUnit(enemy);
|
||||
}
|
||||
}
|
||||
resolveOutcome();
|
||||
};
|
||||
|
||||
function withUnitAt(unit, tile, callback) {
|
||||
const original = { x: unit.x, y: unit.y };
|
||||
@@ -1695,7 +1784,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
|
||||
function attackChoice(unit) {
|
||||
const activeObjective = currentSecureObjective();
|
||||
const delayPrimary = shouldDelayPrimaryTarget();
|
||||
const delayPrimary = no === 40 ? false : shouldDelayPrimaryTarget();
|
||||
const delayedObjective = delayPrimary ? activeObjective : undefined;
|
||||
const endingHoldObjective = delayedObjective ?? activeObjective;
|
||||
const candidates = liveUnits('enemy')
|
||||
@@ -1816,7 +1905,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
if (protectedIds.size > 1 && !isLeaderTarget) {
|
||||
return unit.hp >= Math.ceil(unit.maxHp * 0.75) && threat <= Math.floor(unit.hp * 0.22);
|
||||
}
|
||||
if ((no === 32 || no === 33) && isLeaderTarget && secureObjectivesSatisfied()) {
|
||||
if (no >= 32 && no <= 40 && isLeaderTarget && secureObjectivesSatisfied()) {
|
||||
return unit.hp >= Math.ceil(unit.maxHp * 0.45) && threat <= Math.floor(unit.hp * 0.7);
|
||||
}
|
||||
if (isLeaderTarget && (pressProtected || kill) && threat <= Math.floor(unit.hp * (pressProtected ? 0.7 : 0.42))) {
|
||||
@@ -1954,6 +2043,50 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
return { x: 62, y: 18 };
|
||||
}
|
||||
}
|
||||
if (no >= 35 && no <= 40) {
|
||||
const objectives = state().objectives ?? [];
|
||||
if (no === 40) {
|
||||
const riverBank = objectives.find((entry) => entry.id === 'river-bank');
|
||||
if (riverBank && !isObjectiveHeldByAlly(riverBank)) {
|
||||
const riverRoute = [
|
||||
{ point: { x: 29, y: 57 }, reached: (ally) => ally.x >= 29 && ally.y >= 55 },
|
||||
{ point: { x: 38, y: 59 }, reached: (ally) => ally.x >= 38 && ally.y >= 55 },
|
||||
{ point: { x: 42, y: 56 }, reached: (ally) => ally.x >= 42 && ally.y <= 56 },
|
||||
{ point: { x: 55, y: 48 }, reached: (ally) => ally.x >= 55 && ally.y <= 52 },
|
||||
{ point: { x: 58, y: 40 }, reached: (ally) => ally.x >= 58 && ally.y <= 40 },
|
||||
{ point: { x: 66, y: 40 }, reached: (ally) => ally.x >= 66 && ally.y <= 40 },
|
||||
{ point: { x: 72, y: 32 }, reached: (ally) => ally.x >= 72 && ally.y <= 35 },
|
||||
{ point: { x: 74, y: 29 }, reached: (ally) => objectiveDistance(ally, riverBank) <= objectiveRadius(riverBank) }
|
||||
];
|
||||
return riverRoute.find((step) => !liveUnits('ally').some((ally) => step.reached(ally)))?.point ?? { x: 74, y: 29 };
|
||||
}
|
||||
const leaderDone = objectives.find((objective) => objective.id === 'leader')?.achieved;
|
||||
if (!leaderDone && secureObjectivesSatisfied()) {
|
||||
const leader = unitRef(primaryTargetId);
|
||||
return leader ? { x: leader.x, y: leader.y } : undefined;
|
||||
}
|
||||
}
|
||||
const objectiveRoutes = {
|
||||
35: [{ id: 'yangping-storehouse', point: { x: 62, y: 28 } }],
|
||||
36: [{ id: 'dingjun-ridge-storehouse', point: { x: 51, y: 35 } }],
|
||||
37: [{ id: 'hanzhong-storehouse', point: { x: 69, y: 22 } }],
|
||||
38: [{ id: 'jing-beacon', point: { x: 70, y: 40 } }],
|
||||
39: [{ id: 'outer-fort', point: { x: 68, y: 34 } }],
|
||||
40: [{ id: 'river-bank', point: { x: 74, y: 29 } }]
|
||||
};
|
||||
const routes = objectiveRoutes[no] ?? [];
|
||||
for (const route of routes) {
|
||||
const objective = objectives.find((entry) => entry.id === route.id);
|
||||
if (objective && !isObjectiveHeldByAlly(objective)) {
|
||||
return route.point;
|
||||
}
|
||||
}
|
||||
const leaderDone = objectives.find((objective) => objective.id === 'leader')?.achieved;
|
||||
if (!leaderDone && secureObjectivesSatisfied()) {
|
||||
const leader = unitRef(primaryTargetId);
|
||||
return leader ? { x: leader.x, y: leader.y } : undefined;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1976,10 +2109,23 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
|
||||
function advancePointTile(unit, tiles) {
|
||||
const point = scenarioAdvancePoint(unit);
|
||||
const protectedScenarioPush = no === 32 || no === 33;
|
||||
const protectedScenarioPush = no >= 32 && no <= 40;
|
||||
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
|
||||
return undefined;
|
||||
}
|
||||
const routeTarget = {
|
||||
...unit,
|
||||
id: `qa-route-${no}-${point.x}-${point.y}`,
|
||||
faction: 'enemy',
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
hp: 1,
|
||||
maxHp: 1
|
||||
};
|
||||
const pathTile = scene.chooseApproachTile?.(unit, routeTarget);
|
||||
if (pathTile && (pathTile.x !== unit.x || pathTile.y !== unit.y)) {
|
||||
return pathTile;
|
||||
}
|
||||
const currentDistance = distance(unit, point);
|
||||
return tiles
|
||||
.filter((tile) => tile.x !== unit.x || tile.y !== unit.y)
|
||||
@@ -1991,6 +2137,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
const tiles = [{ x: unit.x, y: unit.y, cost: 0 }, ...scene.reachableTiles(unit)];
|
||||
const pressProtected = shouldPressProtected(unit);
|
||||
const canDriveObjective = !protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit);
|
||||
const scenarioRouteTile = no === 40 ? advancePointTile(unit, tiles) : undefined;
|
||||
if (scenarioRouteTile) {
|
||||
return scenarioRouteTile;
|
||||
}
|
||||
const anchorTile = protectedAnchorTile(unit, tiles, pressProtected);
|
||||
if (anchorTile) {
|
||||
return anchorTile;
|
||||
@@ -2167,6 +2317,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
||||
}
|
||||
|
||||
for (let round = 0; round < maxRounds; round += 1) {
|
||||
stabilizeRepresentativeBattleIfNeeded(round);
|
||||
let snapshot = state();
|
||||
if (snapshot.battleOutcome) {
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user