SOURCE / PINNED RELEASE
Made of little things.
City Breaker
- Release
- 94d2b8664b10…
- Author-recorded commit
- df23103cbcc1…
- License
- LICENSE
- Author’s source reference
- nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-cf8b858653e
Archive hash verified: eeeed725668c1ece…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
// Headless balance harness: node scripts/balance (bundled with esbuild).
import { newCampaign } from '../src/game/state';
import { applyMission, consumeForMission, makeFlight, readyAt, advanceTo, earliestLaunch, type MissionPlan } from '../src/game/campaign';
import { createWorld, autoResolve, finishWorld } from '../src/game/sim';
import { warOutput } from '../src/game/city';
import type { AircraftType, MunitionId } from '../src/game/config';
const strategy = process.argv[2] ?? 'bottleneck';
const s = newCampaign(Number(process.argv[3] ?? 7));
const targets = strategy === 'bottleneck' ? ['power', 'yards', 'bridge', 'fueldepot', 'subN', 'subE', 'railE'] : strategy === 'area' ? ['housingN', 'altstadt', 'housingS', 'housingW'] : ['steel', 'machine', 'chem'];
let i = 0;
while (!s.ended) {
const launch = earliestLaunch(s) + 6;
advanceTo(s, launch);
if (s.ended) break;
const target = targets[i++ % targets.length];
if (process.env.MAXDAY && s.hour > +process.env.MAXDAY * 24) break;
const flights = [];
const take = (t: AircraftType, n: number) => readyAt(s, t, s.hour).slice(0, n).map((a) => a.id);
const mun: MunitionId = strategy === 'area' ? 'ZAB-500' : 'FAB-500';
const b = take('Tu-16', 8);
if (b.length) flights.push(makeFlight(0, 'Tu-16', 'strike', b, mun, target));
const il = take('Il-28', 6);
if (il.length) flights.push(makeFlight(1, 'Il-28', 'strike', il, 'FAB-250', target));
const e = take('MiG-21', 4);
if (e.length) flights.push(makeFlight(2, 'MiG-21', 'escort', e, null, target));
const wz = take('Yak-28', 2);
if (wz.length) flights.push(makeFlight(3, 'Yak-28', 'weasel', wz, 'KSR-11', target, -24));
const j = take('Tu-16PP', 1);
if (j.length) flights.push(makeFlight(4, 'Tu-16PP', 'jammer', j, null, target, -12));
if (!flights.length) { advanceTo(s, s.hour + 24); continue; }
const plan: MissionPlan = { flights, primary: target, alternates: [], route: (['center', 'north', 'south'] as const)[i % 3], ingressAlt: 'med', attackAlt: i % 2 ? 'med' : 'high', launchHour: s.hour, chaffCorridor: !!j.length, drones: 2, tanker: true, playerFlight: flights[0].id, ignoreROE: false };
const ob = warOutput(s).output; const pb = s.political;
consumeForMission(s, plan);
const w = createWorld(s, plan);
autoResolve(w);
const r = finishWorld(w);
const rep = applyMission(s, plan, r, ob, pb);
console.log(`D${Math.floor(s.hour/24)+1} ${target.padEnd(9)} out ${(ob*100).toFixed(0)}→${(rep.outputAfter*100).toFixed(0)} lost ${rep.lost.length} t=${Math.round(rep.tonnage)} decoy=${Math.round(rep.decoyTonnage)} civ=${rep.civilians} pol=${s.political.toFixed(0)} sam=${r.samLaunches} int=${r.interceptorsKilled} dur=${Math.round(r.durationSec)}`);
}
const o = warOutput(s);
console.log(s.ended?.outcome, JSON.stringify({ ind: o.industry.toFixed(2), L: o.logistics.toFixed(2), P: o.power.toFixed(2), W: o.workforce.toFixed(2) }), s.stats.aircraftLost);
import { DAMAGE_LOG } from '../src/game/sim';
console.log(JSON.stringify(Object.fromEntries(Object.entries(DAMAGE_LOG).map(([k, v]) => [k, Math.round(v)]))), JSON.stringify(s.city.adaptLevels), 'morale', s.city.morale.toFixed(2), 'workers', s.city.workers, 'resolve', s.city.resolve.toFixed(2), 'casualties', s.city.casualties, 'firestorms', s.stats.firestorms, 'evac', s.city.evacuationTriggered);
import { afterAction } from '../src/game/campaign';
if (process.env.AAR) { const a = afterAction(s); console.log(a.stats.map((x) => x.join(': ')).join('\n')); console.log(a.paragraphs.join('\n\n')); }
import { CASUALTY_LOG } from '../src/game/city';
console.log('casualties by cause', JSON.stringify(CASUALTY_LOG));
