Add rules-based squad tactics checks

This commit is contained in:
2026-06-29 14:00:19 +02:00
parent 3aebcf8f0e
commit b6cff53b8e
5 changed files with 152 additions and 18 deletions
@@ -0,0 +1,57 @@
const { missionHelpers, pool } = require('../mariadb');
const SQUAD_TACTICS_CHECK = {
name: 'Squad Tactics',
skill: 'Tactics',
characteristic: 'Int',
target: 45,
modifier: 0,
rulesNote: 'Core: Tactics is an Advanced Intelligence Skill with groups such as Assault Doctrine, Defensive Doctrine, Orbital Drop Procedures, and Recon and Stealth. It gives tactical reading/positioning; it does not grant free Cohesion or Squad Mode abilities.',
reward: 'On success, the Kill-team identifies the best tactical approach for this scene; extra Degrees of Success may reveal a safer route, priority threat, ambush vector, or better position.',
};
function hasSquadTactics(scene) {
return (scene.checks || []).some(check => String(check.name || '').toLowerCase() === 'squad tactics');
}
function addSquadTactics(scene) {
if (hasSquadTactics(scene)) return scene;
return {
...scene,
checks: [SQUAD_TACTICS_CHECK, ...(Array.isArray(scene.checks) ? scene.checks : [])],
};
}
(async function main() {
const missions = await missionHelpers.getAll();
const targets = missions.filter(mission => mission.name === 'Vesalius Echo: Theta-92' || mission.is_active);
const results = [];
for (const mission of targets) {
const scenes = Array.isArray(mission.scenes) ? mission.scenes : [];
const nextScenes = scenes.map(addSquadTactics);
const changed = JSON.stringify(scenes) !== JSON.stringify(nextScenes);
if (!changed) {
results.push({ id: mission.id, name: mission.name, changed: false });
continue;
}
const ok = await missionHelpers.update(mission.id, {
name: mission.name,
theme: mission.theme,
sceneCount: nextScenes.length,
enemyCount: mission.enemy_count || 0,
threatLevel: mission.threat_level || 'Medium',
playerCount: mission.player_count || 4,
scenes: nextScenes,
});
results.push({ id: mission.id, name: mission.name, changed: ok });
}
console.log(JSON.stringify({ updated: results }, null, 2));
await pool.end();
})().catch(async error => {
console.error(error);
try { await pool.end(); } catch {}
process.exit(1);
});
@@ -1,14 +1,30 @@
const { missionHelpers, pool } = require('../mariadb');
const squadTacticsCheck = {
name: 'Squad Tactics',
skill: 'Tactics',
characteristic: 'Int',
target: 45,
modifier: 0,
rulesNote: 'Core: Tactics is an Advanced Intelligence Skill with groups such as Assault Doctrine, Defensive Doctrine, Orbital Drop Procedures, and Recon and Stealth. It gives tactical reading/positioning; it does not grant free Cohesion or Squad Mode abilities.',
reward: 'On success, the Kill-team identifies the best tactical approach for this scene; extra Degrees of Success may reveal a safer route, priority threat, ambush vector, or better position.',
};
function withSquadTactics(checks) {
const list = Array.isArray(checks) ? checks : [];
if (list.some(check => String(check.name || '').toLowerCase() === 'squad tactics')) return list;
return [squadTacticsCheck, ...list];
}
function rebalanceScene(scene) {
if (scene.title === 'The Living Vault') {
return {
...scene,
checks: [
checks: withSquadTactics([
{ name: 'Anchor the psychic lock', skill: 'Psyniscience', characteristic: 'Wp', target: 45, modifier: 10, reward: 'Malachiel steadies the vault before the signal tears free.' },
{ name: 'Read psychic carrier wave', skill: 'Psyniscience', characteristic: 'Per', target: 45, modifier: 10, reward: 'Confirm the signal is warp-amplified.' },
{ name: 'Copy datapulse checksum', skill: 'Tech-Use', characteristic: 'Int', target: 45, modifier: 0, reward: 'Recover THETA-92 coordinates without needing a perfect decode.' }
],
]),
complications: [
'2 successes gives full coordinates; 1 success gives partial coordinates with a bad omen.',
'On 2+ failures, the vault tags one marine with a bio-psychic echo.',
@@ -20,11 +36,11 @@ function rebalanceScene(scene) {
if (scene.title === 'Prisoner Signal') {
return {
...scene,
checks: [
checks: withSquadTactics([
{ name: 'Open stasis cages safely', skill: 'Tech-Use', characteristic: 'Int', target: 45, modifier: 10, reward: 'Release prisoners without killing support systems.' },
{ name: 'Triage movable prisoners', skill: 'Medicae', characteristic: 'Int', target: 45, modifier: 10, reward: 'Save the prisoners who can survive extraction.' },
{ name: 'Spot biomorphic trap', skill: 'Awareness', characteristic: 'Per', target: 45, modifier: 10, reward: 'Detect the altered prisoner before it wakes.' }
],
]),
enemies: [
{ name: 'Gene-Warped Prisoner', type: 'mutant trap', count: 1, wounds: 12, ws: 35, bs: 0, ap: 2, damage: '1d10+4', agBonus: 4 }
]
@@ -34,12 +50,12 @@ function rebalanceScene(scene) {
if (scene.title === 'Engine Shrine Sabotage') {
return {
...scene,
checks: [
checks: withSquadTactics([
{ name: 'Place demolition charges', skill: 'Demolitions', characteristic: 'Int', target: 45, modifier: 10, reward: 'Arm the charges in the right sequence.' },
{ name: 'Sense the living machine-spirit', skill: 'Psyniscience', characteristic: 'Wp', target: 45, modifier: 0, reward: 'Malachiel predicts the reactor pulse before it vents.' },
{ name: 'Rip open armour panels', skill: 'Strength', characteristic: 'S', target: 45, modifier: 10, reward: 'Expose the core without losing time.' },
{ name: 'Avoid plasma venting', skill: 'Dodge', characteristic: 'Ag', target: 45, modifier: 10, reward: 'Avoid 1d10+4 Energy Pen 3.' }
],
]),
enemies: [
{ name: 'Gland Hound', type: 'bio-hound', count: 1, wounds: 14, ws: 40, bs: 0, ap: 2, damage: '1d10+5', agBonus: 5 }
]
@@ -50,11 +66,11 @@ function rebalanceScene(scene) {
return {
...scene,
type: 'briefing',
checks: [
checks: withSquadTactics([
{ name: 'Hold the transmission in memory', skill: 'Willpower', characteristic: 'Wp', target: 45, modifier: 10, reward: 'Preserve the coordinates despite Bile’s psychic static.' },
{ name: 'Lock coordinates', skill: 'Navigation', characteristic: 'Int', target: 45, modifier: 10, reward: '+10 to first infiltration/navigation test next mission.' },
{ name: 'Frame strategic report', skill: 'Command', characteristic: 'Fel', target: 45, modifier: 10, reward: 'Turn the discovery into actionable Deathwatch orders.' }
],
]),
complications: [
'Intel priority choices: enemy intel, facility map, sample data, or prisoner testimony.',
'If Sample extracted, quarantine becomes a next-session pressure.',
@@ -64,7 +80,7 @@ function rebalanceScene(scene) {
};
}
return scene;
return { ...scene, checks: withSquadTactics(scene.checks) };
}
(async function main() {
@@ -34,6 +34,22 @@ const canon = {
}
};
const squadTacticsCheck = {
name: 'Squad Tactics',
skill: 'Tactics',
characteristic: 'Int',
target: 45,
modifier: 0,
rulesNote: 'Core: Tactics is an Advanced Intelligence Skill with groups such as Assault Doctrine, Defensive Doctrine, Orbital Drop Procedures, and Recon and Stealth. It gives tactical reading/positioning; it does not grant free Cohesion or Squad Mode abilities.',
reward: 'On success, the Kill-team identifies the best tactical approach for this scene; extra Degrees of Success may reveal a safer route, priority threat, ambush vector, or better position.',
};
function withSquadTactics(scene) {
const checks = Array.isArray(scene.checks) ? scene.checks : [];
if (checks.some(check => String(check.name || '').toLowerCase() === 'squad tactics')) return scene;
return { ...scene, checks: [squadTacticsCheck, ...checks] };
}
const scenes = [
{
title: 'The Living Vault',
@@ -417,7 +433,7 @@ const scenes = [
enemyCount: 4,
threatLevel: 'High',
playerCount: existing.player_count || 4,
scenes: scenes.map(scene => ({ ...scene, missionCanon: canon, missionStoryFlags: storyFlags }))
scenes: scenes.map(withSquadTactics).map(scene => ({ ...scene, missionCanon: canon, missionStoryFlags: storyFlags }))
});
if (!ok) throw new Error(`Failed to update mission ${existing.id}`);