Loading…
a327ex.com

BYTEPATH++ Attacks and Seeker

Fable 5

Summary

Built out BYTEPATH++'s combat slice: the four attacks (normal/shotgun/dart/pulse), a full sound pass with the F3 sound-tuner port, a codebase-modernization sweep onto Anchor framework systems, the seven-enemy .rogue roster port — then the session's two biggest decisions: that roster was removed as "forced" in favor of a single SNKRX-style seeker horde enemy, and (post-seal addendum) the expedition-map design arrived — THE TOWER: phase 1 became a vertical ascent whose stacked bands persist and become the descent, with destruction seeding the descent's difficulty. design.md fully revised.

Attack roster decision (normal, shotgun, dart + existing pulse):

  • Proposed 4 attacks covering the major delivery types from reference/archetypes.md §1: normal (direct projectile baseline), shotgun (volume fire, most distinct of shotgun/spread/triple), dart (unlocks the DoT subsystem), mines (placed/zoning), with shield (aura) consciously cut as its own future subsystem.
  • Owner: "We already have area, let's do normal, shotgun and dart" — mines deferred.
  • All values ported faithfully from BYTEPATH.rogue main.moon (shoot L1634+, set_attack L1798+): normal 0.2s cd/white/250 speed; shotgun 0.8s/red/12 projectiles 400-800 speed ±30° cone with shotgun_decel (0.25-0.5s decelerate-and-die); dart 0.3s/green/250 speed.
  • Infrastructure: player:set_attack(name), player:shoot() restructured into shared muzzle juice + per-attack branches, keys 1-4 select attacks (pipeline debug moved to F5-F8), dot_multiplier stat added.

Dart DoT subsystem:

  • dart entity (main.moon L3144-3159): sticks to the enemy at hit offset, drawn in a new pre_effects list rendered UNDER enemies (source draw order L186), dies with parent.
  • Collision branch: dart projectiles do NO direct damage — spawn dart + enemy_light_hit (1.15 scale bounce vs 1.35).
  • enemy_update_dots: darts stack per-enemy; every 0.5s × dot_multiplier all dart damages sum into one enemy_hit.
  • Dart embed: sensor-begin fires with projectile center ~4px outside the enemy edge, so the stick point advances dart_embed_depth (6, then tuned to 4) px along travel direction.

Camera shake migration to the framework system:

  • First request: directional spring shake on normal/dart fire, stronger on shotgun "similar to the explosion in SNKRX-update... detonator or the one that spawns Big Rock" — identified as the GEOMANCER rock-slam recipe (snkrx/prop.lua L498-499: shake_push 8 + shake_shake 5/0.2); detonator uses shake_trauma instead.
  • Owner course-correct: "do not add a new system, remove the old shake system and use the shake system that is built into Anchor itself."
  • Removed the ported sampled_shake/camera_shake system (~65 lines); added static main_camera = camera_new() used only for main_camera.shake; world draw wrapped in camera_attach/detach; camera_update at end of update() so same-frame pushes integrate.
  • Values: normal/dart shake_push(shake, attack_r, 3) (SNKRX-update projectile default); shotgun push 8 + shake_shake(5, 0.2); pulse's original camera\shake 2, 0.25 → shake_shake(2, 0.25).

Codebase modernization review (owner-requested audit of old ported systems vs Anchor framework):

  • Found 4 real duplications + 1 judgment call; all approved and executed:
  • kd_spring → framework spring: same semi-implicit Euler integrator, parameterization convertible EXACTLY — k=(2πf)², d=4π(1-b)f inverts to f=√100/2π≈1.5915, b=0.5 for the original k=100/d=10. juice_spring() helper; feel digit-identical.
  • layer_push_pivot → deleted: the engine's layer_push IS a pivot when drawing in local coords (arena idiom); all 9 draw methods converted to -w/2 local coordinates.
  • get_enemies_in_area → query_entities_aabb (same centered-box convention; the old center+corners test undercounted true overlaps).
  • real_time → engine time global (verified in anchor.c: game_time += PHYSICS_RATE unscaled while time_scale only scales dt handed to Lua).
  • Input layer: bind('shoot','mouse:1')/bind('flip','mouse:2')/attack_1-4 via input_down/input_pressed; dev-tool keys (F1, F3, F5-F8, tuner mouse) deliberately stay raw.

lerp_dt conversion + the 120Hz discovery:

  • Owner: "Any mouse lerp should use the new functions for lerping which use p, t values... The new lerp functions take in dt which makes them framerate independent."
  • Converted mouse tracking to math.lerp_dt(0.9, 1/60, dt, ...), seeker chase velocity lerp_dt(0.625, 1/60, ...) (derived from the original applyForce on LOVE meter-30 mass ≈ 0.0267), aim lerp_angle_dt(0.9, 1/60).
  • Initially put the mouse lerp on unscaled_dt (original per-frame lerps didn't slow during slow()); owner rejected: "everything should just use dt" — reverted.
  • Debugging a "stalled" seeker telegraph revealed the engine steps at 120Hz (PHYSICS_RATE = 1/120): verify's 300 frames = only 2.5 game-seconds. Per-frame constants from 60fps sources apply twice per second — the slice's mouse lerp had been unintentionally ~2x tighter than .rogue's. Verify extended to 600 frames.

Sound system + F3 sound tuner port:

  • AUDIO section ported from SNKRX-update/snkrx/init.lua: sound_tuning pcall-load, sfx(handle, volume, pitch) with per-sound DSP lookup + 5% pitch jitter, volumes/volumes_defaults/volumes_apply_overrides diff pipeline.
  • sound_tuner.lua (new root file): the SNKRX-update F3 two-tier tuner's LOGIC (off→browser→editor, path→volume-key mapping with _variants stripping, auto-replay 0.5s with live sound_handle_set_dsp retune, diff-based saves to root sound_tuning.lua/volume_tuning.lua) rebuilt in the BYTEPATH window language: F1-tuner chrome, mouse-driven rows + sliders (bits 1-16 / sr_div 1-32 integer-snapped, volume 0-2, RMB reset), Space/S/Esc/PgUp/PgDn accelerators. Game PAUSES while open (update early-out); mutual exclusion with F1. Engine bindings verified in Anchor3 anchor.c (sound_list, sound_play_handle, sound_handle_set_dsp, sound_get_path, file_write_string). Portability catches: no global unpack in Lua 5.4; vormgevers.ttf has no em-dash/middle-dot glyphs (ASCII only in window text).
  • Role column added later ("add on the side which attack/effect the sound belongs to"): sound_roles table keyed by volume key, shown dim in browser rows + editor title bar; browser widened to 430.
  • Volume-key map switched from path-keyed to HANDLE-keyed so the same file loaded twice serves two roles with independent volume rows (engine sound_load doesn't cache — each load is a fresh entry).

Sound picks (all from E:/sound packs, mostly Sci-Fi Gun Sounds PRO):

  • Normal fire: Gun 01_1/2/3 random. Shotgun: one of Gun 10_1/2/4 + one of Gun 22_1/2/3/4 together (10_3, 10_5, 22_5 culled by ear). Dart fire: Weapon 06 Shoot 1/3. Pulse: Sci-Fi Explosion 09+11+23 together, per-layer volume keys.
  • Enemy hit iterations: 2_1 vs 2_5 A/B toggle on F1 → settled on a LAYERED MIX of Hit_Generic 1_5 + 2_5 (F1 panel extended to option sets: 2_1 / 2_5 / 1_5+2_5).
  • Enemy death: Light_Enemy_Impact_1/2 (RPG Magic SFX 2) tried, replaced by Hit_Generic 1_2/1_3 random. DoT tick: PM_GENERIC_Miss01 tried, replaced by Hit_Generic 2_6 (silent flag on enemy_hit so ticks don't stack the hit sound). Dart attach: cancel3__pyu-blip.ogg.
  • Projectile death (wall/decel fizzle): the hit-mix files loaded a SECOND time as proj_death_a/b at 0.5 baseline — separate tuner rows via the handle-keyed map.
  • Shooter fire = player gun files re-loaded at 0.5; exploder boom = pulse layers re-loaded at 0.4 + slight shake — both later pitched UP to random 1.5-1.8 for enemy/player differentiation (removed with the roster).
  • BYTEPATH 2018 sound research (owner request): full inventory of libraries/sound.lua — game_shoot_1 (all projectiles except Flame), game_shoot_2 (Blast/shotgun ONLY), laser/lightning 1-5/flame/explosion (vol 0.3)/enemy_die (pitch 1.1-1.3)/hurt 1-3/item/key + console/menu/keystroke_1-28 + AIRGLOW Memory Bank soundtrack. Notable: game_enemy_hit.ogg exists but is NEVER played — shipped BYTEPATH had no per-hit sound. BYTEPATH.rogue confirmed to have ZERO sounds.

F1 panel rework:

  • "remove the previous settings on the F1 panel since we already settled the shader visuals" — the 12 shader knobs deleted; settled values baked into a shader_params table pushed every frame (uniforms have no GLSL defaults); tuner_export.txt kept as record.
  • Panel became a small debug window holding the enemy-hit sound picker (options from hit_sound_options, active filled white, click previews the set).

Enemy roster port (built, then removed):

  • Pass 1 infrastructure: enemy_projectile (3x3 red sensor rect + shotgun_decel mod later), explosion_effect, block patrol restored (the static block was slice-only), on_death hook in enemy_hit, enemy sensor tag for player hits.
  • Seeker (.rogue L2648): three-phase seek/telegraph/lunge with mm-chained lunges + death burst; chase force translated to lerp_dt (0.625/60Hz, derived from LOVE mass); later resized 9x5, sped up (chase 100, lunge 230), and given the SNKRX steering blend (seek+wander+separate + robotic velocity-match pull lerp_dt(0.9, 0.3)) per "kinda robotic like it is now but mixed with that behavior"; lunge sound RPG3_WindMagic_LightImpact01 (charge-up sound Charge 2_1/2/3 tried and removed).
  • Shooter (L2535, 18x7 after +1px), Exploder (L2430, fixed 12x12 collider under a growing 8→16 visual, kill-before-timer = no explosion), Shotgunner (L2018, proximity-gated 96px burst with 0.6s white telegraph, weak 0.1x drift chase), Spawner (L1893, spin-in, 10s child waves of {shotgunner, seeker}, offscreen-left cull deviation), Rocks (BYTEPATH 2018 Rock/BigRock adapted to SQUARES per owner — "The rocks should be squares and break down into smaller squares... fits this game better"; big 16x16/300hp splits into 4 small 8x8/100hp moving opposite).
  • Roster discussion: measured the built four against the plan's spread; recommended Spawner (tank + priority-target), Shotgunner (proximity-priced threat = the greed pillar), flagged the "hunt" as a spawn-policy concept rather than an enemy species.

THE PIVOT — SNKRX-style single enemy:

  • Owner: "most of these enemies feel a bit forced and will not be necessary to make the game fun, we can probably get away with just making a very SNKRX-like Seeker."
  • Built chaser = SNKRX-update's snkrx/seeker.lua behavior verbatim: 14x6 body, speed random(50,75), steering_seek + steering_wander(64,32,16) + steering_separate(16) force-limited 200 → apply_force → velocity clamp → lerp_angle_dt(0.99, 0.1) heading; damping 3; spawn spring-pop (0.75 @ freq 3 bounce 0.7) + 0.15s flash; hp mapped to 50 then raised to 100 per owner ("50 is too low"); enemy↔enemy collision enabled for horde jostle; spawns INSIDE the arena (key C), 5-pack on boot.
  • "This feels much better, remove all other enemies (including rock) except Chaser and let's build the game off this alone" — all seven roster classes deleted (~750 lines) plus enemy_projectile/explosion_effect/their sounds/keys/verify spawns; main.lua header records the removal with re-port pointer.
  • Renamed chaser → seeker at session end (the name freed by the roster removal); header disambiguates it from the removed .rogue Seeker.

Design process Q&A: confirmed the full design.md was read (recounted the two-phase arc, two-channel principle, forge/crypt/descent, tree structure so the owner could check); recommended folding session design decisions into design.md at decision time + an eventual plan.md (renderer/PLAN.md pattern) so session startup = design.md + plan.md + memory. Concurrent sessions: git worktrees are the standard answer (BYTEPATH++ turned out to already be a git repo, no remote); honest caveat that the single-file main.lua makes parallel code work conflict-prone — prefer sequential sessions for main.lua, parallelism only with disjoint file ownership (docs/reference/modules), and end overlapping sessions one at a time (a327ex-site push race).

THE TOWER — expedition-map design (post-seal addendum, 2026-07-13):

  • Owner's proposal: since phase 2 is a descent, phase 1 becomes an ASCENT — a vertical map in the lineage of the pre-.rogue prototype (identified on D:/ as SHOOTRX: side walls, block rows descending), but an actual explorable map, not a shmup scroll. Descent = Downwell in spirit without the gravity constraint. Plus a possible LEGACY system from the design-log ideas: the 8-12 expedition maps vertically stack and become the descent — "things we destroyed or didn't destroy... come back to bite us or not."
  • Analysis: traced the legacy idea to NDA log 17's V2 "Rehearsal" (coverage = intel, "you know the world exactly as well as you chose to rehearse it") — the tower fixes V2's fixed-world replayability risk since it regenerates per arc. Committed recommendations: tower height = slots GRANTED not spent (early forge = more fog, not less game — makes early declaration braver); one expedition = one band (each launch hoisted to the next band, expeditions stay disposable lives); coverage becomes descent intel (expeditions triple-pay: dust, haul, scouting — kills the phase-1-as-chore risk).
  • Two problems leaned on: the bite must cut both ways (destruction can't be pure-good in both phases or "destroy everything" is dominant) and the empty-band problem (a strip-mined band is boring on descent). Initial proposal: scars/guardians trade + recolonization + a collapse front chasing the descent.
  • Owner's simpler solution — SEEDS: destroying structures yields dust AND a chance to drop a per-band, permanent, inert seed; each seed raises that band's enemy population in the descent. Dust income ↔ later difficulty coupled roughly linearly but noisily — the PoE-league pattern ("juice the map, eat the consequences"). Solves both problems in one rule: farmed bands are the densest, never empty; greed is priced twice. Collapse front demoted to a pacing-only open dial.
  • Extraction redesign: owner rejected extract-by-returning-to-elevator ("backtracking... rehearsing the descent takes away from it"). Solved with forward one-way extraction gates scattered ahead along the route — using any gate banks the run; the commitment moment is walking PAST a gate, and phase 1 contains ZERO downward movement (the descent is the first time the player ever moves down). Extract early = the band's top stays fog.
  • Geometry: the spine and the wings — bands a few screens wide, net-upward spine = progress, sideways wings = greed (denser destructibles, better haul); "how much horizontal" becomes a generation dial (wing width/density per band archetype) instead of a design commitment; coverage % = wings swept.
  • design.md fully revised (title → Ascent/Forge/Descent, overview, two-channel + third-ledger seeds, combat's seeker-substrate decision recorded, Phase 1 rewritten as The Ascent, descent-through-your-own-history, open dials incl. the seed curve as the central knob, next design work = band generation/seed tuning/descent assembly). Owner: "This is in a sense the expedition map design work itself, which it seems already came to me naturally (a good sign)."

Memory: project_bytepath_plus_plus.md updated with the modernization sweep, the 120Hz warning, the combat-slice state, the enemy pivot, and the full tower revision + lineage (V2-Rehearsal, SHOOTRX).

Let's start with BYTEPATH++. Let's implement 4 attacks, one for each major type. Which ones do you think they would be?

I'll read the BYTEPATH++ design doc first to ground this in what's already settled.

Read (E:\a327ex\BYTEPATH++\design.md)

1    # BYTEPATH++ — Design — Expeditions / Forge / Descent
2   
3   Written 2026-07-11. Name locked: **BYTEPATH++** (all caps). Lore-free by decision —
4   the game stands on gameplay alone; any lore (BYTEPATH-universe or otherwise) gets
5   figured out later. The previously developed Technochrist worldbuilding is shelved at
6   `E:/a327ex/private/technochrist-game-lore.md` and is NOT assumed anywhere below.
7   Design lineage: BYTEPATH (visual style + big tree + name), BYTEPATH.rogue (combat +
8   CRT shaders + rosters), Roguelite Design Analysis 2 (structure lessons — Summit/B-Side
9   phase purity, Tab's use-or-bank tension, Heirloom's restraint economy, and the
10  commitment-weight thesis: "a decision the player must live with is the cheapest drama
11  a roguelite can buy, and the market barely stocks it").
12  
13  ---
14  
15  ## Design pillars
16  
17  1. **Commitment over optionality.** The game trains follow-through, never rerolling.
18     No respec, no rollback, no null choices. The two phases sit at opposite ends of the
19     commitment-weight axis (phase 1 light per-decision, phase 2 locked/critical) and
20     crossing between them is the game's central event.
21  2. **Anti-idler, hard rules:** no wall-clock or offline progression of any kind; a
22     defined ending; bounded NG+ (no infinite prestige); exactly one AFK-capable build
23     archetype, and only as an earned endgame expression (a deep tree keystone).
24  3. **Meaningful actions.** Moment-to-moment play must contain real decisions:
25     route scarcity (you can never take everything), readable-but-learnable world
26     (knowledge = mastery), and execution that stays load-bearing (dodge-centric,
27     low-HP combat). No vacuum-simulator scavenging, no number-goes-up-for-free.
28  4. **Ends properly.** One arc = expeditions → forge → descent → ending. The game is
29     proud to end.
30  
... [200 more lines]

Read (D:\2020-2021 code\old\BYTEPATH.rogue\todo)

1    300 level variants
2   modules window
3   sounds
4   trailer + steam page
5   
6   *Seeker 100 - 1000
7   *Shooter 150 - 1500
8   *Block 200 - 2000
9   *Exploder 200 - 2000
10  *Gyro 400 - 4000
11  *Barrager 400 - 4000
12  *Host 900 - 9000
13  *Shotgunner 200 - 2000
14  *Orbitter 200 - 2000
15  *Spawner 800 - 8000
16  *Moving Bar 1000 - 10000
17  *Enemy Area
18  
19  *Normal 
20  *Shotgun - 12 fast moving but decelerating projectiles shot at the same time, high cooldown
21  *Cannon - high damage and high cooldown single projectile
22  *Dart - damage over time projectile, normal cooldown
23  *Shield - area damage over time that increases with each shot and decreases the bigger it is, reaching an equilibrium
24  *Spread - many low damage projectiles shot at random angles, low cooldown
25  *Triple - three projectiles shot at the same time, normal cooldown
26  *Pulse - high area damage, high cooldown
27  *Mines - mines that explode when near enemies, high area damage, high mine production cooldown
28  
29  *Pierce - pierces through enemy, +10% damage per pierce
30  *Finale - every 4th projectile deals double damage
... [62 more lines]

Bash (ls "E:/a327ex/BYTEPATH++")

anchor/
assets/
design.md
main.lua
reference/
run.bat
tuner_export.txt

Bash (wc -l "E:/a327ex/BYTEPATH++/main.lua"; ls "E:/a327ex/BYTEPATH++/reference")

1258 E:/a327ex/BYTEPATH++/main.lua
archetypes.md
source-catalogs.md

Read (E:\a327ex\BYTEPATH++\main.lua)

1    --[[
2     BYTEPATH++ — first playable slice.
3   
4     Player moves as the mouse cursor and fires the pulse attack (the area attack)
5     against a static block enemy that takes damage and dies. All juice is a
6     faithful port of BYTEPATH.rogue (D:/2020-2021 code/old/BYTEPATH.rogue,
7     main.moon + common.moon) — per-event effects, timings, and constants are
8     copied from source, with line references noted throughout. Names are
9     translated from the old PascalCase convention to snake_case.
10  
11    Render pipeline (main.moon L125-220): game world at 480x270 -> displacement
12    shader pass (UV warp from a displacement map fed by ambient/death glitch
13    blocks) -> nearest 3x upscale -> CRT shader (playscii framebuffer_f_crt) at
14    window resolution -> flash overlay.
15  
16    Deliberate deviations from the original, per owner instruction:
17    - the block enemy is stationary (original patrols horizontally)
18    - no sounds (verified: the original has none; its todo lists sounds as
19      unfinished)
20    - player death just refills hp (arena/run flow is future work)
21    - skipped: mod-list HUD, cycle-timer HUD bar, constructs (inactive or
22      absent in this slice)
23  ]]
24  
25  require('anchor')({
26      width = 480,
27      height = 270,
28      title = 'BYTEPATH++',
29      scale = 3,
30      filter = 'rough',
... [1229 more lines]

The pulse attack (the area type) is already in the slice. Before answering, let me check the two reference docs — archetypes.md sounds like it might already define the major types.

Read (E:\a327ex\BYTEPATH++\reference\archetypes.md)

1    # Archetype Analysis — what BYTEPATH++ builds from its two parents
2   
3   Written 2026-07-11, from reference/source-catalogs.md. Purpose: extract the
4   recurring archetypes across BYTEPATH.rogue (2020) and BYTEPATH (2018), judge them
5   against BYTEPATH++'s design (design.md: expeditions/forge/descent, finite tree,
6   anchor keystones, one AFK archetype, commitment pillars), and propose the build
7   roster. PROPOSALS ONLY — owner picks. ⭐ = recommended for v1 combat.
8   
9   ────────────────────────────────────────────────────────────────────────────────
10  ## 1. Attack shapes (union of 9 + 17 attacks → 13 shapes)
11  ────────────────────────────────────────────────────────────────────────────────
12  
13  | Shape | .rogue | BYTEPATH | Note |
14  |---|---|---|---|
15  | ⭐ single | normal | Neutral/Rapid | baseline; Rapid = the aspd variant |
16  | ⭐ heavy single | cannon | (2x dmg node builds) | slow big hit |
17  | ⭐ cone burst | shotgun | Blast | 12-shot decelerating wave |
18  | ⭐ fast scatter | spread | Spread/Rapid | dps hose, low aim demand |
19  | multi-parallel | (double mod) | Double/Triple | more of the same shot |
20  | ⭐ radial | pulse | Spin (rotating) | the area statement; pulse is ours already |
21  | ⭐ attached DoT | dart | — | .rogue-unique; pairs with the DoT stat line |
22  | ⭐ aura | shield | — | .rogue-unique; the shots-grow-a-field idea is special |
23  | ⭐ mines/zoning | mines | (drop_mines proc) | placed threat |
24  | homing | — | Homing | aim-forgiveness; tension with cursor skill thesis |
25  | chain | — | Lightning | auto-target adjacency; same tension as homing |
26  | on-hit split | (fork mod) | 2Split/4Split | .rogue treats it as a modifier — better |
27  | beam | — | Laser | expensive to build well; strong identity though |
28  | stream | — | Flame | ammo-hungry hose; distinct feel |
29  | directional cover | (back mod) | Back/Side | .rogue treats it as a modifier — better |
30  | wall-bounce | (ricochet mod) | Bounce | modifier in .rogue — better |
... [147 more lines]

Read (E:\a327ex\BYTEPATH++\reference\source-catalogs.md)

1    # Source Catalogs — BYTEPATH.rogue (2020) & BYTEPATH (2018)
2   
3   Compiled 2026-07-11, verified against source (not design notes/todos).
4   Sources: `D:/2020-2021 code/old/BYTEPATH.rogue/` (main.moon + common.moon) and
5   `D:/2020-2021 code/old/BYTEPATH/` (the shipped-game copy — objects/Player.lua 2396
6   lines; the `2017-2018 code` copies are earlier snapshots).
7   
8   ═══════════════════════════════════════════════════════════════════════════════
9   # PART 1 — BYTEPATH.rogue (2020)
10  ═══════════════════════════════════════════════════════════════════════════════
11  
12  Cursor-is-the-ship arena shooter with a fake-OS meta shell. One player entity, no
13  characters/classes. Combat = attack (1 equipped) × passives (drafted) × enemies.
14  
15  ## Attacks (9) — one equipped; auto-fire while LMB held; RMB flips facing
16  
17  | Attack | CD | Color | Behavior (shoot, main.moon L1633+) |
18  |---|---|---|---|
19  | normal | 0.2 | white | single projectile, 250 speed |
20  | shotgun | 0.8 | red | 12 projectiles, 400-800 speed, ±30° cone, decelerate & die |
21  | cannon | 0.4 | red | single 350-speed projectile, 2x damage, trail |
22  | dart | 0.3 | green | projectile attaches a DoT dart (per-enemy stack ticks every 0.5s) |
23  | shield | 0.35 | green | no projectile: shots grow a square aura (+5 target, decays to
24  | | | | equilibrium); aura AoE-ticks enemies every 0.25s for 25 |
25  | spread | 0.08 | yellow | 1 projectile ±15° random, 0.75x damage, very fast fire |
26  | triple | 0.16 | yellow | 3 projectiles −15°/0°/+15°, 0.5x damage (desc says 60°, code 15°) |
27  | pulse | 0.8 | blue | 8 radial decelerating projectiles 0.5x + bracket effect + 100x100
28  | | | | area hit at 50 + flash + shake |
29  | mines | 0.8 | blue | slow 4x4 spinning mine, decelerates 1.6-3.2s |
30  
... [176 more lines]

Good timing for this question — the archetypes doc already sorted the 9 .rogue attacks into shape families, and pulse (the area statement) is already implemented in the slice. So the question is really: which 4 of the remaining 8 attacks best cover the remaining major delivery types?

Here's how the 9 .rogue attacks group into types:

Type Attacks in it Status
Radial area burst pulse ✅ done
Direct projectile normal, cannon
Volume fire (multi-projectile) shotgun, spread, triple
Damage over time dart
Aura (area-over-time, no projectile) shield
Placed/zoning mines

That's five open types for four slots, so one gets cut. My pick:

  1. Normal — direct projectile. The baseline everything else is measured against, and the canvas the 20 projectile modifiers will read clearest on. Cheapest of all: the projectile class already exists; this mostly adds aimed fire along attack_r (the RMB facing flip is already wired). Cannon is the same delivery type with different numbers, so it doesn't earn a type slot.

  2. Shotgun — volume fire. The most mechanically distinct of the trio: 12 projectiles, 400–800 speed, ±30° cone, decelerate-and-die — and the decelerating-projectile path already exists (pulse_decel is exactly that mechanic, just with random angles). Spread and triple are just fire-rate/count variations on normal, so shotgun is the one that represents the family.

  3. Dart — DoT. This is the slot that pays infrastructure dividends: it forces the DoT subsystem into existence (attached dart entity, per-enemy stacks, 0.5s tick), which the entire dot stat family plus decay/amplify/contagion/decaying_shot all depend on later. Without a DoT attack, one of the five stat lines has nothing to attach to.

  4. Mines — placed/zoning. The only attack that lives in the world independent of your aim, which makes it feel like a genuinely different verb rather than a projectile variant. It exercises proximity-trigger + area explosion (reusing get_enemies_in_area and the pulse effect vocabulary), and it's the foundation the minefield on-kill/on-cycle procs will stand on.

The cut is shield, and I'd argue for it consciously: the archetypes doc already flags the aura as its own subsystem ("the shots-grow-a-field idea is special"), and it's the most bespoke of the eight — a persistent growing/decaying field with equilibrium math and its own tick loop shares almost nothing with the projectile pipeline. It deserves its own pass rather than being the fourth item in a batch. Dart covers the "damage over time" axis in the meantime.

One alternative worth naming: cannon instead of normal, if you'd rather each attack feel maximally distinct (heavy slow hit vs the shotgun's wave vs dart vs mines). But I think normal earns its slot precisely by being the neutral reference — you want it in the game early so every other attack and every modifier has something to be compared against.

If this roster looks right, my next step is a design brief for the four (dart's DoT subsystem and mines' lifecycle are the two parts with actual design decisions in them — the other two are mostly parameter variations on existing code).

We already have area, let's do normal, shotgun and dart.

Right — normal, shotgun, and dart it is. Let me read the exact source behavior in BYTEPATH.rogue before writing the design brief, since the port is supposed to be faithful.

Grep (shotgun|dart|Dart)

551:    when "shotgunner"
1174:    @darts = {}
1182:    if #@darts > 0
1187:        for dart in *@darts
1188:          dmg += dart.damage
1490:        if @attack == "shotgun"
1543:          attacks = {"normal", "shotgun", "cannon", "dart", "shield", "spread", "triple", "pulse", "mines"}
1570:            attacks = {"normal", "shotgun", "cannon", "dart", "shield", "spread", "triple", "pulse", "mines"}
1651:    mods = {damage_multiplier: @damage_multiplier, pierce: @pierce, finale: (@finale_counter == 5), ricochet: @ricochet, fork: @fork, spawn: @spawn, dart: (@attack == "dart"), pulse: (@attack == "pulse"), mines: (@attack == "mines"), explosion: @explosion, scatter: @scatter, telekinesis: @telekinesis, nova_spawn: @nova_spawn, decaying_shot: @decaying_shot, spark: @spark}
1669:      when "shotgun"
1671:        mods.shotgun_decel = true
1696:      when "dart"
1803:      when "shotgun"
1809:      when "dart"
2078:          table.insert projectiles, EnemyProjectile(@x + @w*cos(@r), @y + @w*sin(@r), rng\float(250, 400)*remap(@mm, 1, 10, 1, 2), @r + rng\float(-pi/8, pi/8), red, {shotgun_decel: true})
2802:    if @mods.shotgun_decel
3095:      if @mods.dart
3096:        table.insert pre_effects, Dart(@x, @y, @r, @w, @h, @color, @damage, other)
3099:      if not @mods.dart
3144:class Dart
3147:    table.insert @parent.darts, @
3171:    if @mods.shotgun_decel

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

1630           when "autocannon"
1631                g.setColor red
1632            g.rectangle "fill", c.x - 3, c.y - 3, 6, 6
1633    
1634      shoot: =>
1635        if @attack == "spread"
1636          @sx, @sy = 1.2, 1.2
1637          @timer\tween 0.1, @, {sx: 1, sy: 1}, linear, (-> @sx, @sy = 1, 1)
1638          table.insert effects, ShootEffect(@x + @attack_m*@w/2, @y, 0, @color, @, 6)
1639          table.insert effects, RectangleParticle(@x + @attack_m*@w/2, @y, rng\float(50, 200), @attack_r + rng\float(-math.pi/4, math.pi/4))
1640        else
1641          @sx, @sy = 1.5, 1.5
1642          @timer\tween 0.1, @, {sx: 1, sy: 1}, linear, (-> @sx, @sy = 1, 1)
1643          table.insert effects, ShootEffect(@x + @attack_m*@w/2, @y, 0, @color, @)
1644          for i = 1, 4 do table.insert effects, RectangleParticle(@x + @attack_m*@w/2, @y, rng\float(50, 200), @attack_r + rng\float(-math.pi/4, math.pi/4))
1645    
1646        if @finale then @finale_counter += 1
1647        if @nova then @nova_counter += 1
1648        if @barrage then @barrage_counter += 1
1649        if @supershot then @supershot_counter += 1
1650    
1651        mods = {damage_multiplier: @damage_multiplier, pierce: @pierce, finale: (@finale_counter == 5), ricochet: @ricochet, fork: @fork, spawn: @spawn, dart: (@attack == "dart"), pulse: (@attack == "pulse"), mines: (@attack == "mines"), explosion: @explosion, scatter: @scatter, telekinesis: @telekinesis, nova_spawn: @nova_spawn, decaying_shot: @decaying_shot, spark: @spark}
1652    
1653        if @supershot_counter >= 8
1654          @supershot_counter = 0
1655          mods.supershot = true
1656    
1657        switch @attack
1658          when "normal"
1659            if @double
... [170 more lines]

Now the Dart entity, the projectile's dart/shotgun_decel handling, and the enemy-side dart stack logic:

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

3080       g.setColor white
3081    
3082      on_collision_enter: (other, contact) =>
3083        if other.__class == Block or other.__class == Shooter or other.__class == Exploder or other.__class == Barrager or other.__class == Gyro or other.__class == Host or other.__class == HostOrbitter or other.__class == Seeker or other.__class == MovingBar or other.__class == Shotgunner or other.__class == Spawner or other.__class == Orbitter
3084          if any(@enemies_hit, other.id) then return
3085    
3086          if @mods.pierce then @damage *= 1.1
3087          if @mods.fork
3088            mods = copy @mods
3089            mods.fork = false
3090            timer\after 0.01, (->
3091              table.insert projectiles, Projectile(other.x, other.y, @v, @r - math.pi/4, @color, mods, {other.id})
3092              table.insert projectiles, Projectile(other.x, other.y, @v, @r + math.pi/4, @color, mods, {other.id})
3093              table.insert effects, ProjectileHitEffect(other.x, other.y, nil, @color, other)
3094            )
3095          if @mods.dart
3096            table.insert pre_effects, Dart(@x, @y, @r, @w, @h, @color, @damage, other)
3097            other\light_hit!
3098          if not @mods.pierce then @dead = true
3099          if not @mods.dart
3100            other\hit @damage, @
3101            table.insert effects, ProjectileHitEffect(@x, @y, nil, (other and other.target_color) or red, other)
3102          if @mods.explosion
3103            camera\shake 1, 0.2
3104            table.insert effects, ExplosionEffect(@x, @y, 20*player.area_multiplier)
3105            for enemy in *get_enemies_in_area(@x, @y, 50*player.area_multiplier, 50*player.area_multiplier)
3106              enemy\hit 0.5*@damage, @
3107              if player.area then timer\every 0.25*player.dot_multiplier, (-> enemy\hit 0.5*@damage/5, @), 5
3108          if @mods.decaying_shot
3109            table.insert effects, DecayArea(@x, @y, 20*player.area_multiplier)
... [85 more lines]

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

1165       g.setColor white[1], white[2], white[3], 0.1
1166          g.rectangle "fill", @x - 5, @y - 2, @w + 10, @h + 4
1167        g.setColor white
1168        @info_text\draw!
1169    
1170    class Collider
1171      new: =>
1172        @id = rng\uid!
1173        @sx, @sy = 1, 1
1174        @darts = {}
1175        @dot_timer = 0
1176        @dot_cd = 0.5
1177    
1178      update: (dt) =>
1179        if @body
1180          @x, @y = @body\getPosition!
1181    
1182        if #@darts > 0
1183          @dot_timer += dt
1184          if @dot_timer > @dot_cd*player.dot_multiplier
1185            @dot_timer = 0
1186            dmg = 0
1187            for dart in *@darts
1188              dmg += dart.damage
1189            @\hit dmg
1190    
1191      draw: =>
1192        if @body
1193          switch @shape
1194            when "rectangle"
... [10 more lines]

A few more details — light_hit, pre_effects, and the projectile's shotgun_decel branch:

Grep (light_hit|pre_effects)

134:  pre_effects = {}
168:  update_objects pre_effects, dt
186:  draw_objects pre_effects
1249:  light_hit: =>
3096:        table.insert pre_effects, Dart(@x, @y, @r, @w, @h, @color, @damage, other)
3097:        other\light_hit!

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

2764 class Projectile extends Collider
2765      new: (@x, @y, @v, @r, @color=white, @mods, @enemies_hit={}) =>
2766        super!
2767        @timer = Timer!
2768    
2769        @damage = 25*(@mods.damage_multiplier or 1)
2770        if @mods.finale then @damage *= 2
2771        if @mods.ricochet then @ricochet_amount = 2
2772        if @mods.scatter then @scatter_amount = @mods.scatter_amount or 3
2773        if @mods.spawn
2774          if @mods.mines
2775            @timer\every rng\float(0.5, 1), (->
2776              mods = copy @mods
2777              mods.damage_multiplier = 0.5*@mods.damage_multiplier
2778              mods.spawn = false
2779              table.insert effects, ProjectileHitEffect(@x, @y, nil, @color)
2780              table.insert projectiles, Projectile(@x, @y, @v, @r - math.pi/2, @color, mods)
2781              table.insert projectiles, Projectile(@x, @y, @v, @r + math.pi/2, @color, mods)
2782            ), 3
2783          else
2784            @timer\every rng\float(0.5, 1), (->
2785              mods = copy @mods
2786              mods.damage_multiplier = 0.5*@mods.damage_multiplier
2787              mods.spawn = false
2788              table.insert effects, ProjectileHitEffect(@x, @y, nil, @color)
2789              table.insert projectiles, Projectile(@x, @y, @v, @r - math.pi/2, @color, mods)
2790              table.insert projectiles, Projectile(@x, @y, @v, @r + math.pi/2, @color, mods)
2791            )
2792    
2793        if @mods.mines then @\set_rectangle 4, 4
... [30 more lines]

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

1243   destroy: =>
1244        if @body
1245          @fixture\destroy!
1246          @body\destroy!
1247          @fixture, @body = nil, nil
1248    
1249      light_hit: =>
1250        @sx, @sy = 1.15, 1.15
1251        @hit_flash = true
1252        @timer\tween 0.1, @, {sx: 1, sy: 1}, linear, (->
1253          @sx, @sy = 1, 1
1254          @hit_flash = false
1255        ), "hit"
1256    
1257      hit: (damage, projectile) =>
1258        if @dead then return
1259    
1260        if @__class == MovingBar
1261          @sx, @sy = 1.05, 1.05
1262        else
1263          @sx, @sy = 1.35, 1.35
1264        @hit_flash = true
1265        @timer\tween 0.1, @, {sx: 1, sy: 1}, linear, (->
1266          @sx, @sy = 1, 1
1267          @hit_flash = false
1268        ), "hit"
1269    
1270        @hp -= damage
1271        if @hp <= 0
1272          @dead = true

One last check — where pre_effects sits in the draw order:

Read (D:\2020-2021 code\old\BYTEPATH.rogue\main.moon)

160  
161 update = (dt) ->
162   aesthetic\send "time", time
163   world\update dt
164   update_objects displacements, dt
165   update_objects players, dt
166   update_objects projectiles, dt
167   update_objects enemies, dt
168   update_objects pre_effects, dt
169   update_objects effects, dt
170   update_objects post_effects, dt
171   update_objects ui, dt
172   update_objects ui_effects, dt
173 
174 draw = ->
175   g.setCanvas displacement_canvas
176   g.clear!
177   g.setColor 0.5, 0.5, 0.5, 1
178   g.rectangle "fill", 0, 0, gw, gh
179   draw_objects displacements
180   g.setCanvas!
181 
182   g.setCanvas game_canvas
183   g.clear!
184   g.setColor white
185   camera\attach!
186   draw_objects pre_effects
187   draw_objects projectiles
188   draw_objects enemies
189   draw_objects effects
... [10 more lines]

I've got everything I need from the source. Here's the design brief.

🔒 Only the summary of this log is public. Private because this is BYTEPATH++.