Skip to content

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.

“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):

  1. 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.
  2. Piece 2 — Manual weapon upgrade tree — THIS SPEC (built with Piece 1). Level-up choices that raise each WeaponAim attribute 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.
  3. 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.

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.

Two firing systems already exist and are kept separate:

  • Arsenal weapons (PilotArsenal.active_weapon_ids) — auto-targeted, upgradeable/evolvable, one per slot, ticked by for 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_aim reading loose AIM_* 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).

  • /sim purity: every new sim file extends 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_aim doesn’t fire); the only other thing that could nudge the checksum is the new ProjPool.burst_radius column, IF pool columns are hashed into state_checksum (verify during the plan). Either way Piece 1 re-pins ONCE — pin the empirically-observed new values into tests/test_determinism_checksum.gd + tests/test_determinism_crystals.gd and note them in CLAUDE.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_name in /sim → run godot --headless --path . --import before 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-tick Sim._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: a blaster weapon entry (the stats above) + a power mod + its bespoke mods.
  • PilotArsenal._init: construct WeaponBlaster, register in weapon_by_id, set blaster_element_idx, and change the seed to active_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.gd render LUT / weapon dock glyph for blaster.
  • Bespoke mods (modest, following the per-weapon apply_mod/mod_now_after pattern): 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 PilotArsenal as arsenal.aim (replaces the loose aim_timer/aim_element_idx; those become WeaponAim fields). Sim._fire_aim becomes a thin caller into arsenal.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”
  1. Pierce (already supported). ProjPool.add_proj(..., pierce_v, ...) + elemental_system collision resolve already decrement pierce and let the shot pass through (elemental_system.gd:78-79). The aim shot passes pierce = arsenal.aim.pierce (1) instead of 0. Zero new mechanism.
  2. Impact burst (small AoE). Add a burst_radius: PackedFloat32Array column to ProjPool (mirroring the existing pierce/split/knockback columns: resize, set in add_proj, swap-remove in lockstep). On a projectile hit, resolve_collisions — when burst_radius[pi] > 0 — applies a small AoE at the hit point using the existing query_circle pattern (as elemental_system.reaction_burst does): damage each enemy in radius at BURST_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.
  3. Knockback (already supported). The aim shot already passes AIM_KNOCKBACK; keep it (now arsenal.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_upgrade routes the aim: prefix to arsenal.aim.apply_mod(kind, _aim_mod_mag(kind)) (mirrors the existing wm: weapon-mod route to w.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.

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).

  • Blaster: fires at the nearest enemy on cadence; deals base_damage; respects effective_fire_rate; a fresh run’s active_weapon_ids == ["blaster"]; blade still offerable.
  • WeaponAim: aim past the deadzone fires on cooldown; the shot carries pierce=1 and burst_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_radius at BURST_FRAC.
  • Aim mods (Piece 2): each WeaponAim.apply_mod(kind, mag) changes the right field by the right magnitude; mod_now_after returns 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 one aim: 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.
  • 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