Imported from Caleb-Guyer/BirdFight3 (
AGENTS.md). Install upstream withnpx skills add Caleb-Guyer/BirdFight3. Copyright stays with the author.
BirdFight3 — AI Assistant Context
Read this before making changes. It captures architecture, hard-won invariants, and project state that are not obvious from the code.
What this is
A feature-complete JavaFX 2D platform fighter (Smash-style) with 22 playable
birds, built by a solo developer (Caleb) with AI pair-programming. Java 21,
Maven, single module.
Everything is code-drawn vector art on Canvas unless sprite sheets are provided
(see Sprite pipeline). ~40k-line BirdGame3.java god class + 1.1MB Bird.java
are known debt — work within them; don't attempt a grand refactor.
Build / test / run
- Tests:
.\mvnw.cmd test(CI runs them on every push — see CI section) - Run from source:
.\mvnw.cmd javafx:run - Package for players:
.\build-installer.ps1 [-AppVersion X.Y.Z] [-Type msi]→ jpackage app-image + zip intarget\. MSI needs the WiX Toolset. - Balance lab (headless AI-vs-AI, ~7m):
.\mvnw.cmd test -Dtest=BalanceLabRun→ writesaudit/balance-report.md - Classic encounter lab (headless, ~12s for one route):
.\mvnw.cmd test -Dtest=ClassicBalanceLabRun -DclassicBird=ROADRUNNER -DclassicMatches=64→ writesaudit/classic-balance-report.md; use-DclassicBird=ALLfor every route - Adventure mission lab (headless, all 40 Still Sky missions × Easy/Normal/Hard):
.\mvnw.cmd test -Dtest=AdventureBalanceLabRun -DadventureMatches=24→ writesaudit/adventure-balance-report.md; narrow with-DadventureMission=dead_airor-DadventureDifficulty=HARD; add-DadventureEnforceTargets=trueto fail on target-band misses
THE DETERMINISM CONTRACT (most important thing in this file)
The simulation is a pure function of (match seed, per-tick inputs). Replays, LAN lockstep netcode, and the balance lab all depend on this. Breaking it causes silent desyncs. Rules:
- Sim randomness only via
SimRng(single seeded stream, reseeded per match instartMatch).game.randomandBird.randomalias it. - Render/audio/UI randomness must NEVER touch SimRng — use
Math.random(),renderRandom, oraudioRandom. Draw-path code consuming SimRng desyncs at high refresh rates. - No wall-clock reads in sim logic — use
simTick(60Hz fixed tick counter).System.nanoTime()is allowed only for render/perf/UI. - Setup randomness (random bird picks, map generation) uses seed-derived
Randominstances, NOT SimRng, so draw counts can't shift the stream. - Fixed timestep 60Hz with render interpolation on top; hitstop consumes
ticks without advancing
simTick; slow-mo scales wall-clock only. - Never stack time-scaling mechanisms (hitstop × slow-mo = perceived hang).
Key systems and where they live
- Game loop:
BirdGame3.gameTick()— accumulator, hitstop, lockstep gate, replay capture/inject, then per-tick sim body. Render interpolation:snapshotRenderPositions/applyRenderInterpolation(prev/curr lerp with teleport snap). - Replays:
MatchReplay(seed + config + per-tick input masks + dash taps),ReplayStore(gzip binary inreplays/, keeps 30), browser via MATCH HISTORY → REPLAYS. Playback is self-contained (restores roster/map) and suppresses all progression side effects. - Lockstep netcode:
LockstepSession+LanProtocolv65. All machines run the full sim; host relays per-tick input bundles; 4-tick LAN input delay; state hashes exchanged every 120 ticks, desync → kill feed warning. During lockstep the sim reads ONLYlanActionPressed(bundle-applied) — live local arrays are gated out ofisActionPressed. Legacy snapshot path is dormant behindlockstepSession == null. Direction edges register dash taps only fromapplyLockstepBundle; applying live host edges bypasses input delay and desynchronizes the host. Client keyboard and controller each own a mask; the sent mask is their union (a sync clobbering the other source froze P2 once — commit 7274ebf). Network matches reuse the standard cinematic victory/results screen; only the host-controlled lobby/exit actions differ. - Internet multiplayer: direct TCP host/join reuses lockstep. Internet uses
an 8-tick input buffer negotiated in
MSG_START. Hosts choose a port (default 28999) and must forward it through their router; joins accept DNS names, IPv4, and IPv6 endpoints. There is no central matchmaking, NAT traversal, or relay service. Internet hosting disables the companion feed. The handshake must complete before a player slot is exposed.MSG_STARTincludes a host-authoritativeNetworkSimulationConfigsnapshot because updater-preserved tuning files can differ between PCs; clients restore their local tuning when the network session ends. - Tuning file:
bird-stats.properties(working dir). Per-bird power/jump/ speed/flyUpForce + damageDealtMult/damageTakenMult/cooldownRate/ultimateRate- global.gravity/startingHealth. Loaded at startup and by the balance lab;
F12 in Training writes the template and hot-reloads. Multipliers apply at
choke points:
receiveScaledDamage(ALL damage), the single cooldown decrement site,gainUltimate. Owned Vulture crow damage and launch inherit the owner's damageDealtMult, while Null Rock's boss flock remains independent. Reload upgrades the exact legacy shipped Goose/Bat/Vulture multiplier presets in memory; if any value in one bird's preset differs, the entire preset is treated as customized and left alone.
- global.gravity/startingHealth. Loaded at startup and by the balance lab;
F12 in Training writes the template and hot-reloads. Multipliers apply at
choke points:
- Developer profiles: the
FEATHERDEVsettings code is a permanent all-content entitlement. Profile load reappliesunlockEverythingForDeveloperProfileso developer saves created on older versions automatically receive newly added birds, skins, maps, modes, progression unlocks, and the complete Still Sky mission/cutscene gallery without opening packs. - Save safety: trailer launches use isolated Java Preferences and must never read or write the live profile. Normal shutdown persistence is gated until a profile has loaded successfully. A rolling backup is created before each normal startup load; preserve all three safeguards when adding headless/export modes.
- Sprite pipeline:
sprites/<bird>.png+.propertiesreplaces a bird's vector body (effects still draw). Per-skin variants:<bird>-<suffix>where suffix matches the skin key case/underscore-insensitively. One animation per row, art faces right, onlyidlerequired, fallback chains. Frame clocks on sim ticks. F12 hot-reloads. No real art exists yet — only the template. - Auto-update:
GameUpdaterchecks GitHub releases/latest on launch of packaged builds, downloads the-win.zipasset, swaps via a DETACHED helper (cmd /c start— the jpackage launcher's job object kills normal children) using ROBOCOPY (Copy-Item can't merge into populated trees), then relaunches./XF bird-stats.properties /XD spritesprotects player files. - Desktop shortcut: created on first packaged launch via the shell's real Desktop path ([Environment]::GetFolderPath — Caleb's desktop is OneDrive-redirected; never use user.home/Desktop).
- Balance lab:
BalanceLab+ harness bridge (harnessPrepareMatch/harnessTickin BirdGame3,headlessHarnessModegate in MatchController.triggerMatchEnd). No JavaFX toolkit, no UI, no progression. - Classic balance lab:
ClassicBalanceLabruns authored route encounters headlessly with their real teams, map variants, mutators, difficulty, stocks, timers, and route mechanics. It reports each combat round independently; objective rounds are excluded by default because their result primarily measures AI navigation. Later Roadrunner rounds preload prior Redline Bolts. - Adventure balance lab:
AdventureBalanceLabruns all 40 Still Sky missions headlessly on Easy, Normal, and Hard through the real campaign objective controller. The player pilot stays fixed at CPU 5 while enemy AI, health, objective windows, Easy assists, teams, variants, phases, and bosses use the selected difficulty. Mid-campaign targets are 50% on Easy, 35% on Normal, and 15% on Hard, each with a ±10-point band. Mission targets rise by up to 10 points early, fall by up to 10 late, and fall another 5 points for bosses (8 for the final boss). Objective results intentionally measure routing reliability; headless cutoffs fail the manual audit. - Null Rock cooldowns: player-controlled Null Rock uses four independent directional reuse timers; CPU Null Rock deliberately retains one shared, difficulty-scaled boss timer so higher difficulties increase cadence safely.
Releases
Tag v<version> on GitHub with asset BirdFight3-<version>-win.zip — every
installed copy offers the update on next launch. Flow: update the Maven version
and docs/releases/vX.Y.Z.md, run .\build-installer.ps1 -RunTests, push the
release commit, then create and push the annotated tag vX.Y.Z. The Release
workflow validates that the tag, Maven version, and notes agree; reruns checks;
builds the portable package; and publishes the GitHub release with three assets.
Latest shipped: v1.5.0.
CI (already exists — don't recreate)
.github/workflows/ci.yml (push/PR) and release.yml (tags): tests, package,
dist verification, and an AUDIO AUDIT (scripts/audit-sounds.ps1) that FAILS
the build if bundled audio contains embedded hostname strings. Gotcha: mp3s
from the wild carry Adobe XMP boilerplate (ns.adobe.com/w3.org) in ID3 tags —
STRIP ID3v2/v1 headers from any new audio before committing.
Audio
All music is public domain (FreePD via archive.org mirror
allfreepdmusicbykuronekony4n); all SFX are procedurally synthesized
originals. Mapping in CREDITS-AUDIO.md. The owner rejected cute/silly
tracks — keep music serious/intense. SFX get pitch/volume variation via
playManagedSfxVaried (presentation-only audioRandom); match music ducks
under KO slow-mo.
Balance state (2026-08-29, see audit/balance-report.md)
AI-vs-AI results — treat as "where to look," not verdicts (the AI can't pilot technical kits like Razorbill/Charles):
- The post defense/ledge/recovery audit completed 19,404 matches across 21 maps with 6 draws/timeouts. Results range from Tufted Titmouse at 26.2% to Raven at 71.2%; the 45.0-point spread is wider than the preceding 40.0-point audit. The new systems reduced Raven's former 74.3% lead while materially improving recovery-dependent Falcon, Phoenix, and Vulture results. Combined with the owner's hands-on fighter passes, the roster remains an accepted playable baseline, though not a claim of tournament-perfect balance.
- Titmouse's campaign-era 1.45 dealt / 0.68 taken preset produced an 84.6% outlier. Restoring 1.10 dealt / 0.92 taken while preserving its 1.40 cooldown and 1.25 ultimate cadence brought the earlier full audit to 64.0%. Exact old shipped presets migrate in memory; any customized value preserves the whole preset. The new stale-move system exposes the CPU's repetitive Titmouse plan and its latest result is 26.2%; treat that as an AI/human-playtest lead before applying another global stat buff.
- Goose scored 46.4% in the latest full audit; its honk launch-hierarchy focused run previously scored 51.2%. Honk has charge commitment, sharp distance falloff, velocity caps, and shared damage-scaled launch; legacy whole-kit penalties were eased to 0.68 dealt / 1.35 taken / 0.70 cooldown / 0.62 ultimate.
- Roadrunner's obsolete 4 power / 0.78 dealt / 1.45 taken / 0.65 cooldown / 0.50 ultimate preset contradicted its momentum payoff. After owner feedback that the first pass was still weak, it moved to 7 / 1.00 / 1.08 / 1.05 / 1.00; its full-audit result is now 43.6% without becoming a roster outlier. Its Classic Round 1 rose from 12.5% to 67.2% by becoming a three-wave miniature gauntlet with checkpoint repairs and a second opening stock. Aggressive CPU navigation changes previously made it worse, so keep AI stage-routing separate.
- Phoenix's normal-attack correction and recovery pass now score 53.7% in the full audit. Bat's eased legacy penalties score 47.8%; its strong map variance remains a useful owner-playtest target rather than an automatic tuning order.
- Vulture's owned crow damage/launch already inherit his outgoing multiplier. Its focused pass eased obsolete whole-kit penalties to 0.84 dealt / 1.26 taken / 0.84 cooldown / 0.78 ultimate, raising its focused result from 25.7% to 45.4% and its latest full-audit result is 47.0%; the owner approved the feel pass.
- The all-route Classic audit completed 9,856 attempts: 107 of 154 scored combat encounters landed in the target band, 19 were possibly easy, 28 were flagged hard/weak-matchup, and objective rounds remained excluded. These are AI pilot leads, especially for technical kits and unique bosses, not automatic tuning orders; every route completed without a harness hang. Razorbill's Worldseam final boss recovered from a routing regression to a 54.7% completion rate.
- The Adventure audit completed 2,880 attempts across all 40 missions and three difficulties with zero harness cutoffs. It recorded 78 of 120 samples in the target band, 27 possibly easy, and 15 possibly hard. The owner's human finale playtest accepts The Null Rock despite the AI pilot's 0% sampled clear rate.
- Global multipliers do NOT fix structural outliers.
Working with the owner
- He playtests every feel change and reports back plainly ("feels like lagging", "still off"). Ship feel changes small and flag them for playtest.
- Keep dramatic effects rare (match-end only); never stack time-freezes.
- One feature per commit; he says "ship vX.Y.Z" when he wants a release.
- Known bug pattern to watch:
Math.clamp(value, variableMin, constMax)throws when variableMin > constMax — one such crash shipped for months in shield-block code (fixed b50a149). Use max(current, min(cap, v)) instead.
Maintenance candidates (not release blockers)
- Competitive balance polish — Raven remains AI-high at 71.2%, while Titmouse (26.2%) and Pelican (42.2%) are the main AI-low human-playtest leads. Roadrunner is 43.6% in the full audit. Pelican's 6.0% Worldseam result and Titmouse's repetitive CPU plan should be verified by human play before any global tuning; neither produced a harness hang or structural test failure.
- Real sprite art — pipeline complete and waiting; owner draws.
- Rollback, matchmaking, and relay infrastructure — direct internet lockstep now exists, but no-setup play through CGNAT still needs a hosted relay and high-latency competitive play still needs rollback.
- Per-skin sprite variants are unit-tested but not yet visually verified.