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 check: a scripted "player" steers to the target and releases on the CCIP pipper.
import { newCampaign } from '../src/game/state';
import { makeFlight, readyAt, applyMission, type MissionPlan } from '../src/game/campaign';
import { createWorld, step, ccip, finishWorld } from '../src/game/sim';
import { warOutput } from '../src/game/city';
const s = newCampaign(11);
const take = (t: any, n: number) => readyAt(s, t, s.hour).slice(0, n).map((a) => a.id);
const target = process.argv[2] ?? 'power';
const role = process.argv[3] ?? 'strike';
const flights = role === 'weasel'
? [makeFlight(0, 'Yak-28', 'weasel', take('Yak-28', 2), 'KSR-11', target), makeFlight(1, 'Tu-16', 'strike', take('Tu-16', 4), 'FAB-500', target)]
: role === 'full' ? [makeFlight(0, 'Tu-16', 'strike', take('Tu-16', 6), (process.argv[4] ?? 'FAB-500') as any, target), makeFlight(2, 'MiG-21', 'escort', take('MiG-21', 4), null, target), makeFlight(3, 'Yak-28', 'weasel', take('Yak-28', 2), 'KSR-11', target, -24), makeFlight(4, 'Tu-16PP', 'jammer', take('Tu-16PP', 2), null, target, -12)]
: [makeFlight(0, 'Tu-16', 'strike', take('Tu-16', 4), (process.argv[4] ?? 'FAB-500') as any, target), makeFlight(2, 'MiG-21', 'escort', take('MiG-21', 4), null, target)];
const plan: MissionPlan = { flights, primary: target, alternates: [], route: 'center', ingressAlt: 'med', attackAlt: (process.argv[5] ?? 'med') as any, launchHour: s.hour, chaffCorridor: role === 'full', drones: role === 'full' ? 4 : 0, tanker: false, playerFlight: flights[0].id, ignoreROE: false };
const w = createWorld(s, plan);
const p = w.player!;
const fr = p.flight!;
let released = 0;
const ev: Record<string, number> = {};
for (let i = 0; i < 9000 && !w.ended; i++) {
if (i === 1) w.ctl.doors = true;
if (p.alive && !p.done) {
const want = Math.atan2(fr.aimY - p.y, fr.aimX - p.x);
let d = want - p.hdg; while (d > Math.PI) d -= 2 * Math.PI; while (d < -Math.PI) d += 2 * Math.PI;
const c = ccip(w, p);
const dd = Math.hypot(c.x - fr.aimX, c.y - fr.aimY);
if (p.sticks === 0 || Math.hypot(p.x - fr.aimX, p.y - fr.aimY) < 1.5) (p as any).home = true;
let dh = Math.atan2(4 - p.y, 60 - p.x) - p.hdg; while (dh > Math.PI) dh -= 2 * Math.PI; while (dh < -Math.PI) dh += 2 * Math.PI;
w.ctl.turn = (p as any).home ? Math.max(-1, Math.min(1, dh * 3)) : Math.max(-1, Math.min(1, d * 3));
const threat = process.env.EVADE && w.missiles.find((m) => m.side === 'blue' && m.targetId === p.id && Math.hypot(m.x - p.x, m.y - p.y) < 7);
if (threat) { let rel = Math.atan2(threat.y - p.y, threat.x - p.x) - p.hdg; while (rel > Math.PI) rel -= 2 * Math.PI; while (rel < -Math.PI) rel += 2 * Math.PI; w.ctl.turn = rel > 0 ? -1 : 1; w.ctl.climb = -1; w.ctl.chaff = Math.random() < 0.05; }
w.ctl.climb = 0;
w.ctl.fire = role === 'weasel' ? w.sites.some((q) => q.emitting && Math.hypot(q.st.x - p.x, q.st.y - p.y) < 30) : dd < 0.8 && p.sticks > 0;
if (w.ctl.fire && p.sticks > 0) released++;
}
step(w, 0.05);
for (const e of w.events) ev[e.t] = (ev[e.t] ?? 0) + 1;
w.events.length = 0;
}
const ob = warOutput(s).output;
const r = finishWorld(w);
const mine = r.impacts.filter((i) => i.player);
console.log('player alive', p.alive, 'hp', Math.round(p.hp), 'released frames', released, 'player impacts', mine.length, 'on target', mine.filter((i) => i.hit === `d:${target}`).length, 'hits', [...new Set(mine.map((i) => i.hit))].join(','));
console.log('events', JSON.stringify(ev));
const rep = applyMission(s, plan, r, ob, s.political);
console.log('out', ob.toFixed(2), '->', rep.outputAfter.toFixed(2), 'integrity', s.districts[target]?.integrity.toFixed(2), 'lost', rep.lost.length, 't', rep.tonnage.toFixed(1), 'dur', Math.round(r.durationSec));
import { DAMAGE_LOG } from '../src/game/sim';
console.log('damage', JSON.stringify(Object.fromEntries(Object.entries(DAMAGE_LOG).map(([k, v]) => [k, Math.round(v)]))), 'player last hit by', p.lastSrc, 'at t', Math.round(p.lastHit), 'x', p.x.toFixed(1));
