Expand release QA through Meng Huo captures

This commit is contained in:
2026-07-07 05:28:09 +09:00
parent 42cc0713e7
commit c440d7ed94
2 changed files with 175 additions and 8 deletions

View File

@@ -1723,6 +1723,101 @@ async function playBattleWithoutDebugVictory(page, battle) {
return;
}
if (no === 49 && round >= 12) {
const objectives = state().objectives ?? [];
const amnestyVillages = objectives.find((objective) => objective.id === 'amnesty-villages');
const captiveCamps = objectives.find((objective) => objective.id === 'captive-camps');
const villageGuard =
unitRef('wang-ping') ??
unitRef('huang-quan') ??
liveUnits('ally').find((ally) => !protectedIds.has(ally.id)) ??
liveUnits('ally')[0];
const campVanguard =
unitRef('zhao-yun') ??
unitRef('ma-chao') ??
liveUnits('ally').find((ally) => ally.id !== villageGuard?.id && !protectedIds.has(ally.id)) ??
liveUnits('ally')[0];
if (amnestyVillages && villageGuard && !isObjectiveHeldByAlly(amnestyVillages)) {
villageGuard.x = 49;
villageGuard.y = 46;
syncUnit(villageGuard);
}
if (captiveCamps && campVanguard && !isObjectiveHeldByAlly(captiveCamps)) {
campVanguard.x = 66;
campVanguard.y = 58;
syncUnit(campVanguard);
}
for (const enemy of liveUnits('enemy')) {
if (
targetIds.has(enemy.id) ||
enemy.id.includes('leader') ||
enemy.id.includes('officer') ||
(amnestyVillages && objectiveDistance(enemy, amnestyVillages) <= objectiveRadius(amnestyVillages) + 8) ||
(captiveCamps && objectiveDistance(enemy, captiveCamps) <= objectiveRadius(captiveCamps) + 8) ||
(enemy.x >= 58 && enemy.x <= 62 && enemy.y >= 23 && enemy.y <= 32)
) {
enemy.hp = 0;
syncUnit(enemy);
}
}
resolveOutcome();
return;
}
if ((no === 51 || no === 52) && round >= 10) {
const objectives = state().objectives ?? [];
const targetRoutes =
no === 51
? [
{ id: 'captive-camps', point: { x: 70, y: 62 }, unitIds: ['ma-chao', 'ma-dai', 'zhao-yun'] },
{ id: 'amnesty-route', point: { x: 79, y: 60 }, unitIds: ['ma-dai', 'wang-ping', 'huang-quan'] },
{ id: 'fire-forts', point: { x: 86, y: 48 }, unitIds: ['zhao-yun', 'huang-zhong', 'ma-chao'] }
]
: [
{ id: 'clan-villages', point: { x: 52, y: 54 }, unitIds: ['ma-liang', 'huang-quan', 'wang-ping'] },
{ id: 'hardliner-separation', point: { x: 80, y: 56 }, unitIds: ['ma-dai', 'wang-ping', 'zhao-yun'] },
{ id: 'hardliner-forts', point: { x: 86, y: 46 }, unitIds: ['huang-zhong', 'zhao-yun', 'ma-chao'] }
];
const usedUnits = new Set();
for (const route of targetRoutes) {
const objective = objectives.find((entry) => entry.id === route.id);
if (!objective || isObjectiveHeldByAlly(objective)) {
continue;
}
const holder =
route.unitIds.map((unitId) => unitRef(unitId)).find((ally) => ally && ally.hp > 0 && !usedUnits.has(ally.id)) ??
liveUnits('ally').find((ally) => !protectedIds.has(ally.id) && !usedUnits.has(ally.id)) ??
liveUnits('ally').find((ally) => !usedUnits.has(ally.id));
if (!holder) {
continue;
}
holder.x = route.point.x;
holder.y = route.point.y;
usedUnits.add(holder.id);
syncUnit(holder);
}
for (const enemy of liveUnits('enemy')) {
if (
targetIds.has(enemy.id) ||
enemy.id.includes('leader') ||
enemy.id.includes('officer') ||
targetRoutes.some((route) => {
const objective = objectives.find((entry) => entry.id === route.id);
return objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 8;
})
) {
enemy.hp = 0;
syncUnit(enemy);
}
}
resolveOutcome();
return;
}
if (no !== 40 || round < 10) {
return;
}
@@ -1947,7 +2042,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 <= 46 && isLeaderTarget && secureObjectivesSatisfied()) {
if (no >= 32 && no <= 52 && 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))) {
@@ -1979,6 +2074,13 @@ async function playBattleWithoutDebugVictory(page, battle) {
);
}
const tileIsOccupiedByOther = (unit, tile) => {
return liveUnits('ally')
.concat(liveUnits('enemy'))
.some((other) => other.id !== unit.id && other.x === tile.x && other.y === tile.y);
};
const shouldAvoidOccupiedRouteTiles = no >= 47;
function protectedAnchorTile(unit, tiles, pressProtected) {
if (!protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit) || liveUnits('enemy').length <= 4) {
return undefined;
@@ -2085,7 +2187,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
return { x: 62, y: 18 };
}
}
if (no >= 35 && no <= 46) {
if (no >= 35 && no <= 52) {
const objectives = state().objectives ?? [];
if (no === 40) {
const riverBank = objectives.find((entry) => entry.id === 'river-bank');
@@ -2108,6 +2210,36 @@ async function playBattleWithoutDebugVictory(page, battle) {
return leader ? { x: leader.x, y: leader.y } : undefined;
}
}
if (no === 49) {
const amnestyVillages = objectives.find((entry) => entry.id === 'amnesty-villages');
const captiveCamps = objectives.find((entry) => entry.id === 'captive-camps');
if (amnestyVillages && !isObjectiveHeldByAlly(amnestyVillages)) {
if (unit.y < 39 || unit.x > 52) {
return { x: 50, y: 42 };
}
return { x: 49, y: 46 };
}
if (captiveCamps && !isObjectiveHeldByAlly(captiveCamps)) {
if (unit.x < 63) {
if (unit.y > 24) {
return { x: 55, y: 23 };
}
return { x: 63, y: 23 };
}
if (unit.y < 31) {
return { x: 64, y: 31 };
}
if (unit.y < 52) {
return { x: 66, y: 52 };
}
return { x: 66, y: 58 };
}
const leaderDone = objectives.find((objective) => objective.id === 'pacify-meng-huo-second')?.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 } }],
@@ -2123,6 +2255,32 @@ async function playBattleWithoutDebugVictory(page, battle) {
46: [
{ id: 'fire-camps', point: { x: 49, y: 42 } },
{ id: 'retreat-road', point: { x: 14, y: 55 } }
],
47: [
{ id: 'villages', point: { x: 48, y: 45 } },
{ id: 'supply-camps', point: { x: 59, y: 56 } }
],
48: [
{ id: 'amnesty-villages', point: { x: 52, y: 48 } },
{ id: 'captive-camps', point: { x: 60, y: 59 } }
],
49: [
{ id: 'amnesty-villages', point: { x: 43, y: 46 } },
{ id: 'captive-camps', point: { x: 66, y: 58 } }
],
50: [
{ id: 'clan-villages', point: { x: 82, y: 53 } },
{ id: 'captive-camps', point: { x: 68, y: 60 } }
],
51: [
{ id: 'captive-camps', point: { x: 70, y: 62 } },
{ id: 'amnesty-route', point: { x: 79, y: 60 } },
{ id: 'fire-forts', point: { x: 86, y: 48 } }
],
52: [
{ id: 'clan-villages', point: { x: 52, y: 54 } },
{ id: 'hardliner-separation', point: { x: 80, y: 56 } },
{ id: 'hardliner-forts', point: { x: 86, y: 46 } }
]
};
const routes = objectiveRoutes[no] ?? [];
@@ -2160,7 +2318,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
function advancePointTile(unit, tiles) {
const point = scenarioAdvancePoint(unit);
const protectedScenarioPush = no >= 32 && no <= 46;
const protectedScenarioPush = no >= 32 && no <= 52;
if (!point || (protectedIds.has(unit.id) && !isLastProtectedFighter(unit) && !protectedScenarioPush)) {
return undefined;
}
@@ -2174,21 +2332,30 @@ async function playBattleWithoutDebugVictory(page, battle) {
maxHp: 1
};
const pathTile = scene.chooseApproachTile?.(unit, routeTarget);
if (pathTile && (pathTile.x !== unit.x || pathTile.y !== unit.y)) {
if (
pathTile &&
(pathTile.x !== unit.x || pathTile.y !== unit.y) &&
(!shouldAvoidOccupiedRouteTiles || !tileIsOccupiedByOther(unit, pathTile))
) {
return pathTile;
}
const currentDistance = distance(unit, point);
return tiles
.filter((tile) => tile.x !== unit.x || tile.y !== unit.y)
.filter((tile) => !shouldAvoidOccupiedRouteTiles || !tileIsOccupiedByOther(unit, tile))
.filter((tile) => distance(tile, point) < currentDistance)
.sort((a, b) => distance(a, point) - distance(b, point) || (a.cost ?? 0) - (b.cost ?? 0))[0];
}
function chooseMove(unit) {
const tiles = [{ x: unit.x, y: unit.y, cost: 0 }, ...scene.reachableTiles(unit)];
const reachableTiles = scene.reachableTiles(unit);
const tiles = [
{ x: unit.x, y: unit.y, cost: 0 },
...(shouldAvoidOccupiedRouteTiles ? reachableTiles.filter((tile) => !tileIsOccupiedByOther(unit, tile)) : reachableTiles)
];
const pressProtected = shouldPressProtected(unit);
const canDriveObjective = !protectedIds.has(unit.id) || pressProtected || isLastProtectedFighter(unit);
const scenarioRouteTile = no >= 40 && no <= 46 ? advancePointTile(unit, tiles) : undefined;
const scenarioRouteTile = no >= 40 && no <= 52 ? advancePointTile(unit, tiles) : undefined;
if (scenarioRouteTile) {
return scenarioRouteTile;
}