Skip to content

cs-util-com/MathMarauders

 
 

Repository files navigation

1) Goal & Core Loop

  • Target session length: 90–180s.

  • Loop:

    1. Forward run → choose math gates (+ / − / × / ÷).
    2. Skirmish vs enemy squad (quick volley exchange, deterministic).
    3. Reverse chase back toward start; survive to finish.
    4. End card with star rating & restart.
  • Pace: Instant restarts, no loading hitches, minimal UI friction.

2) Platforms & Performance

  • Web (mobile-first), responsive desktop support.
  • Min perf targets: 60 FPS on mid-tier mobile; stable 30 FPS on low-end.
  • Draw calls: < 150 during peak. Active particles: ≤ ~250.
  • Fallbacks: If avg FPS < 50 for 2s → auto degrade (see §8 Perf Guards).

3) Visual Direction

  • Style: Low-poly “Candy Arcade”; bright, juicy, extremely readable.

  • Palette (Mobile-First Snap):

    • Primary set: #ff5fa2 (pink), #33d6a6 (teal), #ffd166 (yellow), background gradient #12151a → #1e2633.
    • Unit colors: Blue vs Orange — Player #00d1ff, Enemy #ff7a59.
  • Lighting: Use MeshMatcapMaterial for units/props (lighting-independent). Simple hemi+dir light for environment props (no real shadows).

  • Post-FX: FXAA; Selective Bloom only on gate numerals/symbols and arrow trails (intensity ~0.6, threshold ~0.85, smoothing ~0.1). No OutlinePass.

4) Camera, Feel & Timing

  • Rig: Rail-Follow (Catmull-Rom) with 3 segments: Forward → Skirmish → Reverse. Prebake ~120 samples/segment.

  • Defaults:

    • Forward: height 8, behind 10, FOV 60, easeInOutQuad.
    • Skirmish: height 7, behind 12, tiny ±6° yaw sway.
    • Reverse: height 6.5, behind 9, snap-zoom +1.5% FOV at start.
    • Look target: player centroid with lerp 0.12.
    • Shake: 0.25 amp, 90 ms on big hits (≤1/500 ms).
  • Clip: near/far = 0.1 / 200. Motion blur: none.

5) UI & HUD (Minimal Arcade)

  • Top-center: score + wave timer (tabular-nums).
  • Bottom-left: steering slider (thumb enlarges while dragging, hit-area ≥44×44px).
  • Bottom: big Start/Restart pill.
  • Top-right: pause menu (resume/restart/mute).
  • Gate labels (in-scene): giant operator + number, color-coded (+ green, − red, × yellow, ÷ blue); the numeral card is in the bloom include list.
  • Number formatting: compact (1.2k); deltas flash 250 ms (green gain / red loss).
  • Transitions: 120–160 ms fades/slides; no bounces.

6) Particles & VFX (Ultra-Lean Clarity)

  • Arrow trails: billboard quads (instanced), 6 segments, lifetime 220 ms, additive blend, 64×64 soft-glow texture. Max concurrent emitters: 12.
  • Hit sparks: 8 sprite particles on impact, 160 ms, size 6→2 px.
  • Damage flash: enemy emissiveIntensity 0→0.8 over 60 ms, back to 0 in 80 ms.
  • Gate glow: via selective bloom only on numerals/symbols.
  • Camera juice: as §4.
  • Auto-degrade: halve trail segments (3), cap emitters 6, disable sparks when <50 FPS (see §8).

7) World, Assets & Props

7.1 Terrain — Candy Speedway

  • Lane: 10 m wide strip with two pastel edge lines + dashed center (vertex colors; no textures).
  • Gradient sky/backdrop: #12151a → #1e2633.
  • Fog: linear start 25 m, end 60 m (masks strip pooling).

7.2 Gates — Pillar Arch + Floating Numeral

  • Mesh: Two chunky pillars + shallow arch (≤200 tris).
  • Numeral card: separate emissive quad above arch; in bloom include list.
  • Dims: W 3.2 m, H 2.8 m, D 0.4 m.

7.3 Units & Arrows — Ultra-Light kit

  • Scale: 1 unit = 1 meter; Y-up.
  • Tris budgets: Soldier 350–450, Enemy 400–500, Arrow ≤20.
  • Instancing: players, enemies, arrows, sparks, props.
  • Pivots: characters at foot center (0,0,0); gate ground center; arrow pivot at tail.

7.4 Props — MEDIUM density (~1 per 10 m) with Arcade Flair kit

  • Baseline:

    • Flag post ≤120 tris, H≈1.6 m.
    • Cone ≤80 tris, H≈0.45 m (placed as pairs 0.6 m apart).
  • Flair:

    • Track marker ≤100 tris, H≈0.35 m; two markers 2 m before every other gate.
  • Placement rules: Per 10 m segment place either 1 cone pair or 1 flag (70/30). Keep ≥0.6 m off lane edge; ≥1.5 m clear of gate pillars. Markers desaturated so numerals remain brightest.

  • Culling: frustum + CPU early-out > 65 m behind camera.

  • Spawn: deterministic from run seed.

7.5 Obstacles & Straggler Culling

  • Rocks: Low-poly gumdrop rocks (≤150 tris) intermittently hug the divider with a 0.5 m buffer; they appear on seeded intervals independent of props.
  • Avoidance: Player flock pathing treats obstacles as soft-collide volumes—agents slide along them but remain within lane bounds.
  • Straggler rule: Any unit that drifts outside the lane or stays beyond the buffer for >2 s is despawned with a subtle dissolve so the formation stays tight.

8) Performance Guards & Feature Flags

  • FPS monitor: rolling avg over 2 s.
  • Degrade step 1 (auto): trails 6→3 segments, emitters 12→6, disable sparks, tighten bloom resolution.
  • Upgrade (auto): if ≥58 FPS for 4 s, revert to full Ultra-Lean.
  • Hard-safe mode: manual setting “Low” = No-Bloom Fallback (no composer bloom; baked trail glow texture).

9) Systems & Mechanics

9.1 Gate Generation & Math

  • Operators: Base operations +a, −b, ×c, ÷d (a,b,c,d are per-gate values in ranges tuned per wave).

  • Rounding:

    • After +/: clamp ≥0.
    • After ×/÷: round to nearest integer, min 1; clamp to [1, MAX_ARMY].
  • Balance rules: never generate that would kill all units; ÷ never below 1.

  • Operation tiers: Waves 1–5 use single-step expressions; waves 6–10 unlock two-step combos (e.g. ×4−2); waves 11+ may introduce short parenthetical or exponent variants. Every composite gate resolves to the same clamp/round pipeline above.

  • Evaluation pipeline: Composite gates are generated from a vetted template set (mul-add, add-mul, pow-div, etc.) and evaluate deterministically left-to-right unless parentheses are present. Apply rounding/clamping only after the full expression resolves; intermediate steps must stay ≥0 (designers drop any template that would violate this with configured ranges).

  • Two-gate choice: place two gates per decision point; values drawn to create meaningful deltas (≥15% difference at early waves, ≥25% later).

  • Color coding: + green, − red, × yellow, ÷ blue.

9.2 Forward Run

  • Speed: base lane speed v0, ramps up slightly each wave.
  • Steer input: horizontal factor ∈ [−1, +1] from slider.
  • Flock simulation: Use a lightweight GPU boids pass (inspired by three.js GPGPU birds) to keep large formations cohesive while responding to steering and obstacle avoidance. For low-spec fallback, degrade to CPU formation offsets.

9.3 Skirmish Resolution (deterministic & fast)

  • Tick: every 150 ms both sides exchange damage.
  • Damage model: damage = base * min(attackerCount, defenderCount) ^ 0.85.
  • Casualty calc: casualties per tick = ceil(damage / HP_PER_UNIT); clamp ≤ current count.
  • Enemy sizing: Enemy squads spawn at ~80% of the optimal player count projected for that decision, keeping pressure while preserving a winnable path.
  • Volleys: spawn arrow particles proportional to casualties (capped) for visual feedback.
  • End of skirmish: side reaching 0 loses; survivor proceeds with remaining units. Time to kill must fit the snackable pace (2–4 ticks typical).
  • Determinism: seeded RNG for slight spread; same seed → same result.

9.4 Reverse Chase

  • Setup: spawn a chasing enemy horde at distance D0; speed slightly higher than player (vChase = v0*1.05).
  • Gate mirror: Reverse phase reuses the forward-run gate count for the current wave, with the same math rules applied to shrinking army sizes.
  • Volley pressure: Fire an automatic arrow volley every 0.8 s sized to ~10% of the current player army; arrows target and remove chasers on hit using the skirmish arrow FX/pools.
  • Speed profile: Baseline forward/reverse travel speed is 6 m/s; clearing a reverse gate triggers a 1 s chase surge where the horde spikes to 8 m/s before easing back to baseline.
  • Win/Lose: reach finish line with ≥1 unit → win; if caught or unit count hits 0 → fail; a failed chase resets progression to wave 1 before the next attempt.
  • Difficulty envelope: Tune surge distance and volley effectiveness so a player who maintains ≥70% of the optimal count survives with a small buffer, while dropping below ~50% creates a credible fail risk without feeling impossible.

9.5 Scoring, Stars, & Persistence

  • Score: gated on efficiency and remaining units. Example:

    • Gate choice bonus (+perfect bonus if >90% of theoretical optimum across decisions).
    • Skirmish speed bonus (fewer ticks).
    • Survival multiplier for reverse chase.
  • Star bands: 1★ / 2★ / 3★ at ~40% / 70% / 90% of level’s theoretical max.

  • Persistence: LocalStorage stores { highScore, bestStars, lastSeed } plus a per-wave map of best star ratings to drive progression UI.

  • Wave flow: After each wave, show a minimalist "Wave X Complete" popup with the current 1–3★ result (optionally show a 5★ breakdown for deeper post-run insights) and Next / Retry options; the global Play button advances to the next unfinished wave by default.

  • Seeded runs: shareable seed param (?seed=XXXX).

9.6 Wave Structure & Progression

  • Gate counts: Wave 1 features 5 forward-run gates; each new wave adds +1 gate (tunable cap) before transitioning to the skirmish beat and mirrored reverse run.
  • Deterministic pairing: Forward and reverse gate sets derive from the same seeded generator so that a given seed+wave produces identical layouts across sessions.

10) Architecture & Code Structure

  • Stack: three.js + pmndrs postprocessing (FXAA, Selective Bloom). Optional troika-three-text for desktop counters (mobile uses DOM).

  • Renderer: antialias:false (AA via composer), powerPreference "high-performance".

  • Core modules:

    • Game.ts (state machine: PreRun → Running → Skirmish → Reverse → EndCard).
    • World.ts (lane pooling, fog, gradient backdrop).
    • Gates.ts (spawn, math values, color, bloom list control).
    • Units.ts (instancing, counts, simple formation layout).
    • Combat.ts (skirmish ticks, damage model, arrow spark emit).
    • VFX.ts (trails, sparks, flashes; performance guards).
    • CameraRig.ts (rail samples, beat transitions, shake).
    • UI.tsx or ui.ts (DOM HUD & slider; pause; end card).
    • Flock.ts (GPU boids update step + CPU fallback, straggler cleanup hooks).
    • SeedRng.ts (seedable PRNG).
    • Perf.ts (FPS monitor, degrade/upgrade).
    • Telemetry.ts (abstract event interface with trackEvent(name, payload); default console logger; ready for external analytics).
  • Object pooling: arrows, sparks, props are pooled; InstancedMesh per type; per-instance attributes for color/scale/opacity.

  • Selective bloom list: numeral cards, trail material. Everything else excluded.

11) Data & Config

{
  "VFX": {
    "TRAIL_SEGMENTS": 6,
    "TRAIL_LIFETIME_MS": 220,
    "SPARK_COUNT": 8,
    "BLOOM_INTENSITY": 0.6,
    "BLOOM_THRESHOLD": 0.85,
    "BLOOM_SMOOTHING": 0.1,
    "SHAKE_AMP": 0.25,
    "SHAKE_MS": 90
  },
  "CAMERA": {
    "FOV": 60,
    "FORWARD": { "height": 8, "behind": 10 },
    "SKIRMISH": { "height": 7, "behind": 12, "yawSwayDeg": 6 },
    "REVERSE": { "height": 6.5, "behind": 9 },
    "LOOK_LERP": 0.12
  },
  "TERRAIN": { "FOG_START": 25, "FOG_END": 60 },
  "GATES": { "WIDTH": 3.2, "HEIGHT": 2.8, "DEPTH": 0.4 },
  "PROPS": { "DENSITY": "MEDIUM" }
}

12) Controls & Accessibility

  • Controls: one-hand slider, tap buttons; Arrow keys/A/D on desktop as mirror input (optional).
  • Readability: high contrast HUD; color-coding supplemented by symbols/operators (color-blind friendly).
  • Haptics (optional mobile): short vibration on perfect gate and win.

13) Error Handling & Edge Cases

  • WebGL unavailable: show lightweight fallback screen with instructions to enable hardware acceleration.
  • Lost context / tab hidden: pause and show resume.
  • Bad seed / params: validate and clamp to defaults.
  • Division gates: enforce min result 1 after rounding; never generate ÷0 or ÷ that yields <1.

14) Testing Plan (high level)

  • Unit (Jest):

    • Gate math & rounding rules (Given/When/Then).
    • Gate generator never emits invalid combos.
    • Combat tick determinism for a fixed seed.
    • Performance guard thresholds (simulate FPS series).
    • Score & star band calculations.
    • Telemetry adapter routes events to console without throwing.
  • Integration (Playwright):

    • Start → finish happy path; restart is instant.
    • Two known seeds produce identical runs and scores.
    • UI responsiveness: slider drag latency under threshold.
  • Visual checks: screenshot diff of HUD & gate legibility across DPRs (1.0/2.0/3.0).

15) Build & Delivery

  • Project shape: single-page app; ES modules; no mandatory build step (can add bundler later).
  • Assets: glTF (embedded) or inline BufferGeometry for ultra-light meshes; matcap PNGs (sRGB).
  • Hosting: static hosting (GitHub Pages/Netlify/etc.).
  • Shareable seed: via querystring; copy-to-clipboard button on end card (optional later).
  • Documentation: Inline JSDoc on public APIs; keep architecture and tooling notes current in README.md/docs/.

16) Non-Goals (v1)

  • Multiplayer, accounts, cloud saves.
  • Heavy post-processing (DOF, motion blur, OutlinePass).
  • Complex physics or per-soldier IK.

Repo layout & tooling (once, before Iteration 1)

/src
  /core        // pure logic (rng, math, scores, perf, state)
  /world       // lane, props, gates (data + spawn policies)
  /units       // formations, combat, pooling
  /render      // three.js glue (composer, matcaps, instancing, trails)
  /ui          // DOM UI (slider, HUD, dialogs)
  /game        // state machine, wiring
/tests
  /unit
  /integration
/docs
  implementation-progress.md
public/index.html   // ESM entry, no bundler required (import maps optional)

Tooling & scripts (package.json)

  • "test": "jest --runInBand"
  • "test:watch": "jest --watch"
  • "lint": "eslint . --max-warnings=0"
  • "e2e": "playwright test --reporter=line"
  • "check": "npm run lint && npm run test && npm run e2e"

CI-friendly, non-interactive reporters; Node 20+, Jest + JSDOM, Playwright for e2e.


Iteration plan (TDD, one task each)

The following is an iteration-by-iteration TDD build plan that is bottom-up, one focused task per iteration, CI-friendly, and aligned with the locked spec. Each iteration lists goal, prep, tests-first items (with Given/When/Then + short “why this test matters”), implementation notes, and the exact commands to run. After every iteration, append a short note to docs/implementation-progress.md.

Iteration 1 — Seedable RNG

Goal: Deterministic seeds for gates/props/skirmish spread. Prep: ripgrep to ensure no prior RNG. Tests (unit):

  • Given seed 1234, When generating 5 numbers, Then the sequence equals a stored snapshot. why this test matters: locks determinism across machines.
  • Given two RNGs with the same seed, When advanced in different batch sizes but same total draws, Then final value matches. why: prevents subtle order bugs. Impl notes: xorshift32 or mulberry32; exposes nextFloat(), nextInt(min,max). Run: npm run test && npm run lint Doc: Add decisions & sequence snippet to docs/implementation-progress.md.

Iteration 2 — Gate Math & Rounding

Goal: Central evaluator for +/-/×/÷ with clamping/rounding per spec. Tests (unit):

  • applyGate(10, "+5") → 15, ("-20") clamps to 0. why: correctness of additive rules.
  • applyGate(10, "×1.5") → 15 (nearest), ("÷3.2") → 3 (nearest, min 1). why: rounding consistency.
  • Never returns < 1 after ×/÷. why: gameplay safety. Impl notes: pure function; no side effects. Run: npm run test

Iteration 3 — Gate Generator

Goal: Produce two valid gate choices with meaningful deltas per wave. Tests (unit):

  • Never yields ÷0 or a that kills all units. why: fail-safe content.
  • Delta between options ≥15% (early waves) / ≥25% (later). why: decision salience.
  • Deterministic for a given seed+wave. why: shareable seeds. Run: npm run test

Iteration 4 — Score & Star Bands

Goal: Score formula + 1★/2★/3★ thresholds. Tests (unit):

  • Perfect decisions + fast skirmishes reach ≥3★; sloppy reaches <2★ on same seed. why: curve feels right.
  • Band values serialize and re-load correctly. why: stable end cards. Run: npm run test

Iteration 5 — FPS Monitor & Perf Guards (logic only)

Goal: Rolling FPS average + degrade/upgrade signals. Tests (unit):

  • Given series below 50 FPS for 2s, Then emits DEGRADE_STEP1. why: protects mobile perf.
  • Given ≥58 FPS for 4s, Then emits UPGRADE. why: recovers visuals. Run: npm run test

Iteration 6 — Object Pool (generic)

Goal: Reusable pool for arrows/sparks. Tests (unit):

  • acquire returns recycled instances after release. why: GC stability.
  • Pool caps prevent growth beyond limit. why: predictable memory. Run: npm run test

Iteration 7 — Unit Formation & Counts

Goal: Transform count → formation slots (grid arc) with pivot at (0,0,0). Tests (unit):

  • 1, 10, 100 units produce non-overlapping positions. why: visual clarity.
  • Formation width/height scales smoothly with count. why: camera framing. Run: npm run test

Iteration 8 — Combat Tick Simulator (deterministic)

Goal: 150 ms volleys; casualties per spec; seedable spread. Tests (unit):

  • Fixed attacker/defender counts + seed → deterministic time-to-kill. why: repeatable runs.
  • Casualties never exceed current counts. why: integrity.
  • “Fast win” vs “near parity” produce different tick lengths. why: pacing. Run: npm run test

Iteration 9 — World Lane Pooling (logic)

Goal: Segment recycling, fog window 25→60 m. Tests (unit):

  • Camera moving forward reuses segments without gaps/overlap. why: endless lane.
  • Reverse direction mirrors reuse. why: chase beat support. Run: npm run test

Iteration 10 — Gate Placement Policy

Goal: Place two gates per decision, safe distances from pillars/centerline. Tests (unit):

  • Gates never overlap; spacing before/after respects min distance. why: fairness & readability.
  • Operator→color mapping correct. why: accessibility/consistency. Run: npm run test

Iteration 11 — Props Generator (MEDIUM density)

Goal: Deterministic flags/cones/markers per 10 m; culling >65 m behind. Tests (unit):

  • Seeded prop positions are reproducible; never intersect gates or lane center. why: stable scenery.
  • Density ≈ 1 per 10 m over long run. why: visual rhythm. Run: npm run test

Iteration 12 — Camera Rail Sampler

Goal: Prebaked Catmull-Rom samples for Forward/Skirmish/Reverse. Tests (unit):

  • Sampling at t∈[0..1] returns continuous, monotonic path; lookAt lerp stable. why: jitter-free.
  • Beat transitions respect durations (200–220 ms). why: timing. Run: npm run test

Iteration 13 — UI Slider (DOM) + Input Pipe

Goal: Accessible slider → normalized steer ∈ [−1, +1]. Tests (integration, Playwright):

  • Drag/Touch adjusts value smoothly; keyboard A/D mirrors on desktop. why: control parity.
  • Hit area ≥44 px; thumb enlarges while active. why: mobile ergonomics. Run: npm run e2e

Iteration 14 — HUD Numbers & Delta Flashes

Goal: Top-center score/timer; compact format; ±delta flash 250 ms. Tests (integration):

  • Numbers align (tabular-nums); deltas animate and auto-clear. why: glanceable feedback.
  • Pause hides timer, resume restores. why: state integrity. Run: npm run e2e

Iteration 15 — Game State Machine

Goal: PreRun → Running → Skirmish → Reverse → EndCard transitions. Tests (unit):

  • Given seed X, driving inputs across beats reaches EndCard without illegal transitions. why: flow safety.
  • Restart returns to PreRun with clean state. why: instant retries. Run: npm run test

Iteration 16 — Three.js Boot & Composer (FXAA only)

Goal: Renderer (antialias:false), FXAA in composer, gradient backdrop, fog. Tests (integration/smoke):

  • Canvas mounts; frame count > 0; gradient & fog uniforms applied. why: render pipeline sanity.
  • Toggling low-perf flag disables bloom path (stub for now). why: future guard. Run: npm run e2e

Iteration 17 — Instanced Units & Formation Render

Goal: Single InstancedMesh for player/enemy; matcap material. Tests (integration/visual diff):

  • Counts 1→100 render within formation bounds (screenshots diff threshold). why: layout fidelity.
  • Material sRGB output toggled correctly. why: color correctness. Run: npm run e2e

Iteration 18 — Gates Render: Pillar Arch + Floating Numeral

Goal: Gate mesh (≤200 tris) + emissive numeral quad in bloom-include list. Tests (integration):

  • Gate symbols color-coded; two choices visible and non-overlapping. why: legibility.
  • Numeral cards flagged for bloom list (data check now; visual in next iteration). why: post-FX hookup. Run: npm run e2e

Iteration 19 — Selective Bloom (numerals + arrow trails)

Goal: Add Bloom effect; include only numeral/arrow materials. Tests (integration/visual):

  • Numeral quads glow; pillars do not; FXAA retained. why: attention focus.
  • Fallback flag disables bloom cleanly. why: low-end mode. Run: npm run e2e

Iteration 20 — Arrow Trails (instanced billboards) + Hit Sparks

Goal: Ultra-Lean: 6 segments, 220 ms lifetime; sparks burst on casualties. Tests (integration):

  • Max emitters capped; lifetime respected; object pool reuse verified (counter). why: perf ceiling.
  • Trail materials on bloom include list only. why: effect budget. Run: npm run e2e

Iteration 21 — Skirmish Wiring (logic→VFX)

Goal: Drive volleys from combat ticks; damage flash (emissive pop). Tests (integration):

  • Known seed results in expected volley count & duration; end state matches unit tests. why: logic/render parity.
  • Flash intensity animates 0→0.8→0 over 140 ms. why: feedback timing. Run: npm run e2e

Iteration 22 — Reverse Chase

Goal: Spawn chase horde at D0; speed vChase=1.05×; win/lose. Tests (integration):

  • With low player count, fail condition triggers before finish; with high, succeed. why: balance envelope.
  • Transition adds snap-zoom +1.5% FOV. why: beat emphasis. Run: npm run e2e

Iteration 23 — Props (MEDIUM) & Culling

Goal: Flags, cones, track markers with placement rules & CPU early-out. Tests (integration):

  • Density ~1/10 m over 300 m lane; no intersections with gates/lane center. why: spatial rules.
  • Frustum + “>65 m behind” culling active (counter stats). why: perf. Run: npm run e2e

Iteration 24 — Scoring, Stars, End Card, Restart

Goal: Show score, 1–3★ bands; Restart resets instantly; share seed link. Tests (integration):

  • Perfect path seed ≥3★; sloppy ≤2★ (using fixed run). why: reward curve.
  • Restart clears transient state (pools, counts, timers). why: replay loop. Run: npm run e2e

Iteration 25 — Perf Guards Hookup (runtime)

Goal: Wire FPS monitor to degrade/upgrade VFX. Tests (integration):

  • Simulated low FPS → trails segments halve, sparks off, bloom res reduced; recovery restores. why: mobile resilience. Run: npm run e2e

Iteration 26 — Accessibility & UX Polishing

Goal: Color contrast ≥ 4.5:1 HUD; operator symbols alongside colors; pause/resume clarity. Tests (integration):

  • Automated contrast check for HUD foreground vs gradient (threshold). why: readability.
  • Gate readability snapshot tests across DPR 1.0/2.0/3.0. why: device coverage. Run: npm run e2e

Developer workflow per iteration (repeatable)

  1. Pre-check: rg "<keyword>" -n to avoid re-implementing existing logic; refactor if present.

  2. Write tests first (unit or e2e). Include a brief // why this test matters: ... comment.

  3. Implement minimal code to pass tests.

  4. Run checks: npm run check (lint + unit + e2e).

  5. Docs update: append to docs/implementation-progress.md:

    • What changed, why it matters, decisions, open questions, next iteration.
  6. Commit message: feat(core|world|ui|render): <iteration title> [#iteration-XX].


Example Given/When/Then snippets (ready to paste)

Unit (Jest):

// tests/unit/gates.math.test.ts
// why this test matters: rounding and clamps underpin all counts; one mismatch cascades into wrong difficulty.
test("× and ÷ rounding & clamps", () => {
  expect(applyGate(10, {op:"mul", val:1.5})).toBe(15);
  expect(applyGate(10, {op:"div", val:3.2})).toBe(3);
  expect(applyGate(1,  {op:"div", val:3.2})).toBe(1);
});

Integration (Playwright):

// tests/integration/hud.delta.spec.ts
// why this test matters: players must read gains/losses instantly; animation regressions are common.
test("delta flashes for 250ms then clears", async ({ page }) => {
  await page.goto("/public/index.html?seed=test-seed");
  await startRun(page);
  await chooseGate(page, "mul", 1.5);
  const delta = page.getByTestId("hud-delta");
  await expect(delta).toBeVisible();
  await page.waitForTimeout(300);
  await expect(delta).toBeHidden();
});

Notes & guardrails (apply throughout)

  1. One task per iteration. Keep PRs small and focused.
  2. TDD: tests first; minimal implementation; re-run npm run check before/after.
  3. “Why this test matters” comment in every new/changed test.
  4. Search before implement. If functionality exists, prefer refactor over duplication.
  5. Append learnings to docs/implementation-progress.md after each iteration (serves as project memory & devlog).
  6. CI-friendly: avoid interactive prompts; stable output; use line reporters.
  7. Performance budgets: draw calls < 150; active particles ≤ ~250; watch dev HUD (optional) that prints counters.

About

Only sharp calculations will carry your soldiers back to safety

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • JavaScript 100.0%