Ship Classes (EVE-style) — design
Ship Classes (EVE-style) — design
Section titled “Ship Classes (EVE-style) — design”Date: 2026-07-04
Status: approved (design), pre-implementation
Determinism baseline at design time: snapshot_string().hash()=2730172591, state_checksum()=4075578713 → read the literal pinned assertion in tests/test_determinism_checksum.gd / test_determinism_crystals.gd, not this note.
Naming is provisional. Chris wants a later pass on overall themes/names for everything, and there is still a large amount of balancing to do. Every name, number, and gold price below is a placeholder to try out and tune, not a final decision. The architecture is the durable part; the content (the “Obsidian” identity, the exact stat numbers) is expected to change.
1. Concept & scope
Section titled “1. Concept & scope”Introduce an EVE-style class dimension to hulls. The 6 existing hulls
(manta, cobalt, aurum, prism, amethyst, fuchsia) become frigate-class:
3 weapon slots, 1 drone slot, at today’s stats (100 HP / 260 speed / 16px hitbox).
Add one cruiser hull with a full stat block — more slots, tankier, slower, bigger — unlocked with gold in the meta-shop. A cruiser is a genuine trade (bigger guns, harder to dodge), not a free upgrade.
In scope
Section titled “In scope”- Retag the existing 6 hulls as
frigateclass withweapon_slots=3, drone_slots=1. - One cruiser hull with a full stat block + a real baked sprite.
Sim.max_weapon_slotsas per-run state (currently a hardcoded const).- Gold unlock for the cruiser via the existing meta-shop unlock pattern.
- UI wiring so slot counts, the picker, and the config panel reflect the hull.
- TDD for every unit; determinism re-verified.
Out of scope (future iterations)
Section titled “Out of scope (future iterations)”- More cruisers, or destroyer / battlecruiser / battleship tiers.
- Per-pilot hulls in co-op —
max_weapon_slotsstays Sim-global this pass, matching the current P1-centric reality (P2 progression is already gated/incomplete). Flagged inline where it would need to become per-pilot. - EVE high/mid/low-slot distinctions — we model only weapon slots and drone slots.
- A weapon-slot meta-shop upgrade (drones already have one; weapons stay hull-fixed for now).
2. Data model — enrich sim/ship_bonuses.gd
Section titled “2. Data model — enrich sim/ship_bonuses.gd”Each TABLE entry grows from a bare bonus into a full hull spec. Keep the ShipBonuses
class_name — renaming a class referenced in 5 files (meta_state, start_menu,
ship_config_panel, player_renderer, main) invites the stale-class-cache trap for no real
gain. The name is now a light misnomer (it’s a hull registry, not just bonuses); a comment notes it.
Frigate entries keep their existing bonus, gaining class + slots + explicit base stats equal to today’s defaults (a behavioural no-op except for the weapon-slot cap):
"cobalt": { class="frigate", weapon_slots=3, drone_slots=1, base_hp=100, base_speed=260, base_radius=16, stat_effect="move_speed", magnitude=1.15, name="Cobalt", label="+15% speed" },# manta / aurum / prism / amethyst / fuchsia identically retagged, each keeping its own bonusThe cruiser (provisional identity — “Obsidian”, heavy dark hull, fits Dark Cosmos; frigates are gem/metal names, this is heavier stone):
"obsidian": { class="cruiser", weapon_slots=5, drone_slots=2, base_hp=170, base_speed=210, base_radius=24, stat_effect="armor", magnitude=10.0, name="Obsidian", label="Cruiser · +10 armor" },New static accessors (all pure, unknown id → frigate defaults for stale saves):
class_of(id), weapon_slots_for(id), drone_slots_for(id), base_stats_for(id).
3. Sim change — one new field
Section titled “3. Sim change — one new field”var max_weapon_slots: int = UpgradeSystem.MAX_WEAPONS # per-run active cap; hull sets it at run startUpgradeSystem.MAX_WEAPONS(6) stays as the absolute ceiling: weapon-instance count, UI slot-array size, hard cap. Frigate (3) and cruiser (5) are both<= 6.max_weapon_slotsis the per-run soft cap.UpgradeSystem.roll_upgrade_choicesandgrant_weapongate weapon grants onsim.max_weapon_slotsinstead of the const.- Default equals the const so any
Simbuilt without a hull (every headless test, the determinism baseline) behaves exactly as today. - This mirrors the existing
Sim.max_drone_slotsfield one-for-one — the pattern already exists.
4. Run-start application (render-side, main.gd:474-475)
Section titled “4. Run-start application (render-side, main.gd:474-475)”Today: meta.apply_to(...) then ShipBonuses.apply_to(selected_ship, sim.player, sim.mods),
and sim.max_drone_slots = meta.drone_slots() at main.gd:485.
Replace with a single ShipBonuses.apply_hull(selected_ship, sim, meta) that:
sim.max_weapon_slots = weapon_slots_for(id)sim.max_drone_slots = drone_slots_for(id) + meta.level_of("drone-slots")— hull base plus the existing drone-slots shop upgrade (additive). Frigate base 1 makes this a perfect no-op versus today’s1 + level_of("drone-slots").- overrides
player.max_hp/player.hp/player.speed/player.radiusfrom the base block - applies the headline bonus via the existing
StatEffects/SimModspath (unchanged mechanism)
apply_to() is kept as a thin wrapper (bonus-only) if any other caller needs it, or folded in —
decided during implementation after grepping callers.
Determinism boundary: this is run config, applied once before tick 0 — the same boundary
max_drone_slots already uses. Both co-op peers must agree on the hull (as they already agree on
the seed and run config). max_weapon_slots being sim state read by the deterministic upgrade
roller is fine because it is fixed before ticking and identical across peers.
5. Meta-shop unlock
Section titled “5. Meta-shop unlock”Add a bible entry unlock-hull-obsidian (type:"unlock", target:"hull:obsidian", a gold cost)
— mirrors the existing unlock-drone-<class> / unlock-decoy-<type> pattern the shop already
renders (ui/shop_categories.gd, ui/shop_icons.gd already special-case unlock-* ids; the new
id needs minor additions to those maps).
# sim/meta_state.gd — mirrors owns_decoy / owns_classfunc owns_ship(ship_id: String, defs: Array) -> bool: if ShipBonuses.class_of(ship_id) == "frigate": return true # all frigates always owned return level_of("unlock-hull-" + ship_id) >= 16. UI touch points
Section titled “6. UI touch points”| File | Line | Change |
|---|---|---|
ui/start_menu.gd |
~129 | List the cruiser too; render it locked with its gold price until owns_ship; selecting it (when owned) sets selected_ship. |
ui/ship_config_panel.gd |
~95 | Draw weapon_slots_for(id) cards, not MAX_WEAPONS; show class name + stat block (slots / HP / speed). |
ui/weapon_panel.gd |
~176 | Draw sim.max_weapon_slots slots, not UpgradeSystem.MAX_WEAPONS. |
ui/drone_dock.gd |
— | Already reads sim.max_drone_slots. No change. |
render/player_renderer.gd |
731 | Already data-driven on selected_ship. Just needs the sprite asset to exist. |
7. The cruiser sprite
Section titled “7. The cruiser sprite”Bake a distinct, chunkier silhouette (EVE cruiser = more mass than a frigate) via
tools/ship_preview (SHIP_VARIANT=bake godot --path . res://tools/ship_preview/preview.tscn --rendering-method forward_plus) → render/ship_sprites/ship3d_obsidian.png (+ any hull texture
the renderer expects, matching the existing ship3d_* assets).
Per memory bullet-heaven-ui-look: a capture that “looks plausible” is not proof — diff against
the frigate baseline and get Chris’s on-device eye before calling the art done. This is its own
iteration sub-task, sequenced after the mechanical system works with a placeholder.
8. Determinism & testing (TDD)
Section titled “8. Determinism & testing (TDD)”Unit tests:
ShipBonuses:class_of/weapon_slots_for/drone_slots_for/base_stats_forfor a frigate, the cruiser, and an unknown id (→ frigate defaults).apply_hull: frigate setsmax_weapon_slots=3,max_drone_slots=1(+shop), base stats unchanged; cruiser sets5/2/170/210/24++10 armor.UpgradeSystem: weapon grants stop atmax_weapon_slots(a frigate run cannot exceed 3 weapons;roll_upgrade_choicesoffers no weapon grant when full).MetaState.owns_ship: frigate always true; cruiser false untilunlock-hull-obsidian >= 1; purchase flow flips it.
Determinism: the baseline test builds Sim with no hull → default max_weapon_slots=6, and
frigate numbers equal today’s, so expect 2730172591 / 4075578713 unchanged (read the literal
pinned assertion, not this note). Re-verify after the change. If it moves — it should not — treat
it as a real break and investigate, do not blindly re-pin.
Ritual: every chunk goes through bh-dev-chunk (build → TDD → import → boot-check →
test-count → determinism → commit → tvOS-sync). New class/dir additions run
godot --headless --import to dodge the stale-class-cache parse error.
9. Balance flags (for Chris, all tunable)
Section titled “9. Balance flags (for Chris, all tunable)”- Frigate 6→3 weapon slots is a real shift to the only shipping mode: builds go deep (more mods/evolutions on fewer weapons) instead of wide. Intended and arguably better build-craft, but changes late-run upgrade pacing (once 3 slots fill, offers are mods/evos/stats only). Worth a playtest.
- All cruiser numbers (
5W/2D,170/210/24,+10 armor, gold price) are consts in theTABLE. - Cruiser must stay a genuine trade: the bigger hitbox + lower speed are the cost of the extra slots + HP. If it reads as strictly-better in playtest, widen the downside, don’t shrink the slots.
10. Build sequence (feeds writing-plans)
Section titled “10. Build sequence (feeds writing-plans)”- Data model — enrich
ShipBonuses.TABLE+ accessors + tests (no behaviour change yet). - Sim field —
Sim.max_weapon_slots; re-pointUpgradeSystemgates; determinism re-verify. - Run-start apply —
apply_hullwiring inmain.gd; frigate proves the no-op path. - UI slot counts —
weapon_panel/ship_config_panelread the hull’s counts. - Cruiser content + unlock —
obsidianentry,owns_ship, shop unlock def, start-menu lock/price. - Cruiser sprite — bake, diff vs baseline, on-device review (art sub-task, last).