Signature Manual Weapon — Design (Pieces 1+2; Piece 3 → Sovereign)
Signature Manual Weapon — Design (Pieces 1+2; Piece 3 → Sovereign)
Section titled “Signature Manual Weapon — Design (Pieces 1+2; Piece 3 → Sovereign)”Status: design approved (Chris, 2026-07-04). Scope decision (Chris, 2026-07-04): build
Pieces 1 + 2 in Dark Cosmos now; defer Piece 3 (reconfiguration for synergies /
boss-adaptation) to the new Sovereign EVE-lite project — per the co-agent’s
2026-07-04-project-split-design.md, that “adapt-your-fit-to-the-fight” paradigm is a different
game, not a deeper mode of this one. This spec details Pieces 1+2; Piece 3 is described only for
continuity.
The idea (Chris’s words)
Section titled “The idea (Chris’s words)”“start off with the first weapon being the simple bullet shooter rather than the melee style, but make the manual one not exactly the same, give it a bit of an effect and make it do a little more dmg to encourage learning to use it” → then: “let’s do a little of each [effect] and it can have an upgrade tree for all attributes, also let’s allow it to be reconfigured to allow synergies with other weapons and to be more useful against different bosses.”
The manual (aimed) attack becomes the player’s signature weapon: a differentiated bullet shooter that rewards learning to aim, grows on its own upgrade tree, and can be reconfigured to synergise with the rest of the arsenal and counter specific bosses.
Decomposition — 3 pieces (this spec is Piece 1)
Section titled “Decomposition — 3 pieces (this spec is Piece 1)”The full vision spans several subsystems, too big for one spec. Build order (each independently playable + testable, each building on the last):
- Piece 1 — Signature manual weapon (foundation) — THIS SPEC. Swap the auto starter from the melee blade to a new simple auto-shooter (“Blaster”); promote the manual aim attack from a const-tuned mechanic into a first-class weapon object; give it baseline “a little of each” (pierce + impact burst + knockback) and a bit more damage than the Blaster. Re-pin determinism.
- Piece 2 — Manual weapon upgrade tree — THIS SPEC (built with Piece 1). Level-up choices that
raise each
WeaponAimattribute independently (damage, pierce count, burst radius, knockback, fire-rate, projectile speed). In-run build-craft in the same roguelite level-up paradigm every other weapon already uses — the split design keeps this kind of depth in Dark Cosmos. - Piece 3 — Reconfiguration: synergies + boss-adaptation — DEFERRED to Sovereign. Retune the manual weapon’s element / effect-emphasis / firing-mode to trigger reactions with owned weapons and to counter specific bosses. This is the EVE-style “fitting / adapt to the encounter” paradigm that the project-split design assigns to the new Sovereign project, NOT Dark Cosmos. Not built here; revisited when Sovereign’s core loop is validated.
Piece 3 gets its own spec→plan→build cycle inside the Sovereign project.
Goal (Pieces 1+2)
Section titled “Goal (Pieces 1+2)”Replace the melee starter with a plain auto-shooter; turn the always-on aimed attack into a distinct, better-feeling signature shot (Piece 1); and let the player grow that signature shot on its own in-run upgrade path (Piece 2). A clean object foundation Piece 3 (in Sovereign) can later extend.
Architecture
Section titled “Architecture”Two firing systems already exist and are kept separate:
- Arsenal weapons (
PilotArsenal.active_weapon_ids) — auto-targeted, upgradeable/evolvable, one per slot, ticked byfor wid in active_weapon_ids: weapon_by_id[wid].update(sim, pilot, dt). - The manual aim attack — always-on (does NOT consume a slot), fires in the aim/right-stick
direction, currently
Sim._fire_aimreading looseAIM_*consts.
Piece 1 adds one new arsenal weapon (Blaster) and promotes the manual attack to a first-class
object (WeaponAim) so it can hold per-instance, later-upgradeable/reconfigurable state.
Tech stack: Godot 4.6 / typed GDScript; /sim pure RefCounted logic; GUT tests; the
data-oriented ProjPool + SpatialHash; content in data/bible.json (hand-edited).
Global Constraints (bind every task)
Section titled “Global Constraints (bind every task)”/simpurity: every new sim fileextends RefCounted; no Node/Engine/Input/Time/OS/File/JSON APIs. Render/UI only read sim state.- Determinism is re-pinned ONCE for Piece 1. The starter swap (blade→blaster) changes the
600-tick baseline (a different weapon fires → different kills/positions). The manual-weapon
firing never runs in the baseline (it only ever passes a move dir —
InputState.new(dir)— so_fire_aimdoesn’t fire); the only other thing that could nudge the checksum is the newProjPool.burst_radiuscolumn, IF pool columns are hashed intostate_checksum(verify during the plan). Either way Piece 1 re-pins ONCE — pin the empirically-observed new values intotests/test_determinism_checksum.gd+tests/test_determinism_crystals.gdand note them inCLAUDE.md. The sim must stay deterministic (byte-identical for a seed) — re-pinning updates the expected value, it does not relax the guarantee. - New
class_namein/sim→ rungodot --headless --path . --importbefore tests/boot or the stale class cache silently drops the new type. - Damage is float end-to-end; damage routes through
elemental_system.damage_enemy(subtract only); removal is the single end-of-tickSim._sweep_dead. - Names (“Blaster”, the evolution name) and every stat are provisional/tunable — balance is playtest-deferred.
Component 1 — WeaponBlaster (the new auto-shooter starter)
Section titled “Component 1 — WeaponBlaster (the new auto-shooter starter)”File: sim/weapon_blaster.gd (class_name WeaponBlaster extends RefCounted).
Mechanic: on cooldown, find the nearest enemy (same lookup as WeaponPulse.nearest_enemy_index)
and fire ONE projectile toward it via sim.projectiles.add_proj(...). Collisions, element
application and reactions are handled for free by Sim.elemental_system.resolve_collisions — no new
renderer needed beyond a dock glyph (same pattern as weapon_scatter/weapon_turret).
Proposed stats (tunable): base_damage 4, cooldown_s 0.45, projectile_speed 720,
projectile_radius 8, lifetime_s 1.0, element "aether". Divides cooldown by
sim.effective_fire_rate(pilot) like every other weapon.
Wiring (new-weapon checklist — see skill bh-add-content):
data/bible.json: ablasterweapon entry (the stats above) + apowermod + its bespoke mods.PilotArsenal._init: constructWeaponBlaster, register inweapon_by_id, setblaster_element_idx, and change the seed toactive_weapon_ids = ["blaster"](was["blade"]).UpgradeSystem.WEAPON_ORDER: add"blaster"; keep"blade"so the blade stays acquirable at level-up (still evolves to Tempest) — it just stops being the starter.main.gdrender LUT / weapon dock glyph forblaster.- Bespoke mods (modest, following the per-weapon
apply_mod/mod_now_afterpattern):power(generic),fire-rate,multishot(fire 2→3 bullets in a tight spread). Evolution (evolve()): provisional “Auto-Cannon” — higher RoF + innate pierce. Exact mod list finalised in the plan.
Determinism: the baseline run now ticks the Blaster instead of the blade → new checksum → re-pin.
Component 2 — WeaponAim (promote the manual attack to a first-class object)
Section titled “Component 2 — WeaponAim (promote the manual attack to a first-class object)”File: sim/weapon_aim.gd (class_name WeaponAim extends RefCounted).
Holds what were loose AIM_* consts as instance state (consts kept as the defaults, per the
cycle-17 “consts become instance vars, consts stay as defaults” pattern):
| Field | Default (from current const) | Piece-1 change |
|---|---|---|
damage |
AIM_DMG = 6.0 |
keep 6 (≈1.5× the Blaster’s 4 — “a little more”) |
cooldown |
AIM_COOLDOWN = 0.24 |
keep |
proj_speed |
AIM_PROJ_SPEED = 920 |
keep |
proj_radius |
AIM_PROJ_RADIUS = 10 |
keep |
proj_lifetime |
AIM_PROJ_LIFETIME = 1.1 |
keep |
knockback |
AIM_KNOCKBACK = 260 |
keep (a touch punchier is a tuning call) |
deadzone |
AIM_DEADZONE = 0.35 |
keep |
element_idx |
aether |
keep |
pierce |
(new) | 1 |
burst_radius |
(new) | ≈48 |
- Held on
PilotArsenalasarsenal.aim(replaces the looseaim_timer/aim_element_idx; those becomeWeaponAimfields).Sim._fire_aimbecomes a thin caller intoarsenal.aim.update(sim, pilot, input, dt)(or reads the object’s fields) — no behavioural change beyond the new effects. - Stays the always-on aimed attack — NOT added to
active_weapon_ids, does not consume a slot. - This object is the seam Pieces 2 (upgrade tree over these fields) and 3 (reconfigure element / effect emphasis / mode) build on. It carries no upgrade/config logic yet.
Component 3 — the “a little of each” effects
Section titled “Component 3 — the “a little of each” effects”- Pierce (already supported).
ProjPool.add_proj(..., pierce_v, ...)+elemental_systemcollision resolve already decrement pierce and let the shot pass through (elemental_system.gd:78-79). The aim shot passespierce = arsenal.aim.pierce(1) instead of 0. Zero new mechanism. - Impact burst (small AoE). Add a
burst_radius: PackedFloat32Arraycolumn toProjPool(mirroring the existingpierce/split/knockbackcolumns: resize, set inadd_proj, swap-remove in lockstep). On a projectile hit,resolve_collisions— whenburst_radius[pi] > 0— applies a small AoE at the hit point using the existingquery_circlepattern (aselemental_system.reaction_burstdoes): damage each enemy in radius atBURST_FRAC(const, ≈0.5) of the shot’s damage, excluding the primary target (which already took full damage). Available to any projectile; only the aim shot sets it non-zero in Piece 1. - Knockback (already supported). The aim shot already passes
AIM_KNOCKBACK; keep it (nowarsenal.aim.knockback).
Render (neon): the aim shot reads as a brighter lance (existing projectile render is fine; a
distinct tint/size is a small touch); the burst emits an fx_events pop at the hit point (same
mechanism nova/reactions use). FX is render-only and excluded from the checksum.
Component 4 — Piece 2: the manual-weapon upgrade tree (in-run level-up mods)
Section titled “Component 4 — Piece 2: the manual-weapon upgrade tree (in-run level-up mods)”“Upgrade tree” here means the roguelite level-up upgrade pool — each WeaponAim attribute
independently upgradeable via level-up picks, in the exact paradigm every other weapon already uses
(apply_mod + offered through roll_upgrade_choices). It is NOT a separate branching skill-tree UI;
that would be a larger feature to scope on its own — flag if wanted.
WeaponAim.apply_mod(kind, mag) + mod_now_after(kind, mag) (same shape as every other weapon’s
per-weapon mod methods — keeps /sim purity + weapon-local state). Mods (kind → effect,
proposed magnitude/level, all tunable):
Mod (aim:<kind>) |
Effect | Proposed /level |
|---|---|---|
aim:power |
damage ×(1+mag) |
+25% |
aim:pierce |
pierce += 1 |
+1 |
aim:burst |
burst_radius ×(1+mag) |
+20% |
aim:knockback |
knockback ×(1+mag) |
+30% |
aim:firerate |
cooldown ÷(1+mag) (still bounded by effective_fire_rate) |
+15% |
aim:velocity |
proj_speed ×(1+mag) (≈ more range) |
+20% |
Offering rules (Sim.roll_upgrade_choices):
- The manual weapon is always-on = always “owned”, so its mods are always offer-eligible — no acquisition gate (unlike arsenal weapons, which must be granted before their mods appear).
- Cap at most ONE
aim:mod per 3-choice roll, so the always-eligible manual mods can’t flood out arsenal-weapon choices. apply_upgraderoutes theaim:prefix toarsenal.aim.apply_mod(kind, _aim_mod_mag(kind))(mirrors the existingwm:weapon-mod route tow.apply_mod).
Determinism: Piece 2 is fully neutral — no re-pin beyond Piece 1’s starter swap. Mods apply only
through apply_upgrade, which the baseline determinism test never calls (it never levels up). The
baseline value is whatever Piece 1’s starter swap re-pins it to; Piece 2 does not move it.
UI: the level-up choice cards already render weapon mods — the aim: mods appear as choices
(e.g. “Aim · Piercing +1”, “Aim · Power +25%”). The manual weapon also needs a place to show its
current state: add it to the weapon dock as a fixed always-present “Manual/Aim” tile (its own glyph;
shows current mods on hover, like the other weapon tiles). Exact dock treatment finalised in the plan.
Data flow
Section titled “Data flow”InputState.aim_dir → Sim._fire_aim → arsenal.aim.update() → projectiles.add_proj(pierce, burst_radius, knockback) → Sim.elemental_system.resolve_collisions (pierce pass-through, burst AoE, knockback, element/reactions) → Sim._sweep_dead. One-way, deterministic, unchanged in shape. Piece 2 adds one
path: level-up → apply_upgrade("aim:<kind>") → arsenal.aim.apply_mod() (never during the baseline).
Testing (TDD per chunk)
Section titled “Testing (TDD per chunk)”- Blaster: fires at the nearest enemy on cadence; deals
base_damage; respectseffective_fire_rate; a fresh run’sactive_weapon_ids == ["blaster"];bladestill offerable. - WeaponAim: aim past the deadzone fires on cooldown; the shot carries
pierce=1andburst_radius≈48; damage ≈1.5× the Blaster. - Pierce: an aim shot through two lined-up enemies damages both (pierce decrements, shot survives the first).
- Burst: an aim shot hitting one enemy in a cluster also damages neighbours within
burst_radiusatBURST_FRAC. - Aim mods (Piece 2): each
WeaponAim.apply_mod(kind, mag)changes the right field by the right magnitude;mod_now_afterreturns the correct now/after pair;apply_upgrade("aim:pierce")increments the aim weapon’s pierce. - Offer rules (Piece 2):
aim:mods are always offer-eligible; a single 3-choice roll contains at most oneaim:mod. - Determinism: re-pin the two checksum tests to Piece 1’s new baseline; Piece 2 must NOT move it (assert the baseline is unchanged after Piece 2’s mods land). Full suite + count guard green.
Out of scope for this build (Pieces 1+2)
Section titled “Out of scope for this build (Pieces 1+2)”- No reconfiguration / synergy / boss-adaptation — that is Piece 3, deferred to the Sovereign
project (see the scope decision +
2026-07-04-project-split-design.md). - No branching skill-tree UI — the “upgrade tree” is the roguelite level-up mod pool (Component 4), not a bespoke tree screen; a dedicated tree UI would be its own feature (flag if wanted).
- No manual-weapon evolution (attribute mods only in Piece 2).
- No change to the blade beyond de-seating it as the starter (it remains a normal acquirable weapon).
- No rebalance of other weapons.
Tunable values (single source, playtest-deferred)
Section titled “Tunable values (single source, playtest-deferred)”| Value | Proposed | Where |
|---|---|---|
| Blaster damage / cooldown | 4 / 0.45s | data/bible.json blaster |
| Blaster speed / radius / lifetime | 720 / 8 / 1.0 | data/bible.json blaster |
| Blaster element | aether |
data/bible.json blaster |
| Aim damage | 6 (≈1.5× Blaster) | WeaponAim.damage (default AIM_DMG) |
| Aim pierce | 1 | WeaponAim.pierce |
| Aim burst radius | 48 | WeaponAim.burst_radius |
| Burst damage fraction | 0.5 | BURST_FRAC const |
| Aim knockback | 260 | WeaponAim.knockback (default AIM_KNOCKBACK) |
| Piece 2 mod magnitudes | +25% / +1 / +20% / +30% / +15% / +20% | Component 4 table (aim: mods) |
| Max 1 aim mod per roll | 1 | Sim.roll_upgrade_choices cap |