Files
thecrimesynticate/docs/superpowers/specs/2026-06-28-nerve-gym-design.md
2026-06-28 15:24:36 +03:00

6.7 KiB

Nerve Stat + Gym Training — Design Spec

Date: 2026-06-28
Status: Approved


Overview

Add a nerve stat that acts as a daily crime-volume cap alongside the existing per-attempt energy cost. Add a gym where players spend energy to train the four combat stats (strength, defense, speed, dexterity), subject to a per-stat daily session cap.


Data Model

characters table — new columns

Column Type Default Notes
nerve int 100 Current nerve
max_nerve int 100 Max nerve; grows +2 per level-up
nerve_regen_at timestamp null Tracks next trickle tick

crimes table — new column

Column Type Default Notes
nerve_cost int 1 Nerve consumed per attempt

crime_attempts table — new column

Column Type Default Notes
nerve_cost int 1 Nerve actually spent (for logs)

gym_sessions table — new table

Column Type Notes
id bigint PK
character_id bigint FK
stat enum(strength, defense, speed, dexterity)
sessions_today int Resets lazily when reset_date < today
reset_date date Date sessions_today was last reset
created_at timestamp
updated_at timestamp

Unique constraint on (character_id, stat). One row per character per stat, upserted on each training session.


Nerve Mechanics

Role

Nerve is a secondary resource gating crime volume. Each crime attempt costs nerve (default 1, configurable per-crime). Energy still gates individual attempts as today. A player with full energy but 0 nerve cannot commit crimes.

Regen

  • Trickle: +1 nerve every 5 minutes (same interval as energy). Handled by a new ProcessNerveRegen artisan command on the scheduler.
  • Midnight refill: nerve set to max_nerve for all characters at 00:00 daily. Handled by a new NerveMidnightRefill artisan command.

Max nerve scaling

Starts at 100. Grows by +2 per level-up (slower than max_energy's +5, since nerve caps daily crime volume rather than individual actions).

Crime integration

CrimeController@attempt checks nerve after the existing energy check. If insufficient, returns an error. On success, deducts nerve and records nerve_cost on the CrimeAttempt.


Gym Mechanics

Route

POST /game/gym/train → GymController@train
GET /game/gym → GymController@index (the gym page)

Training flow

  1. Player selects a stat and submits the Train form.
  2. Controller verifies: character is active (not in jail/hospital), has ≥ 20 energy, daily cap not reached for that stat.
  3. TrainingService::train($character, $stat) executes:
    • Deduct 20 energy from character.
    • Increment the chosen stat by 1.
    • Upsert gym_sessions row: if reset_date < today, reset sessions_today to 0 first; then increment.
  4. Flash success/error message, redirect back to gym page.

Daily cap

5 sessions per stat per day (configurable via config/game.php gym.daily_cap). Reset is lazy — no scheduled command; checked on each train attempt by comparing reset_date to today.

Stat gain

Flat +1 per session. No diminishing returns in this iteration.

Energy cost

20 energy per session. Uses existing check.energy:gym middleware (requires adding gym to config/game.php energy.action_costs).


Config (config/game.php)

Add two new blocks:

'nerve' => [
    'start_amount'    => 100,
    'max'             => 100,
    'regen_rate'      => 1,       // nerve per tick
    'regen_interval'  => 300,     // seconds (5 minutes)
    'midnight_refill' => true,
],

'gym' => [
    'energy_cost' => 20,
    'daily_cap'   => 5,           // sessions per stat per day
    'stat_gain'   => 1,           // stat points per session
],

Also add to existing energy.action_costs:

'gym' => 20,

Character Model Changes

Add to $fillable: nerve, max_nerve, nerve_regen_at
Add to $casts: nerve_regen_at → datetime

New methods (mirroring existing energy methods):

  • hasNerve(int $amount): bool
  • consumeNerve(int $amount): bool
  • addNerve(int $amount): void
  • getNervePercentageAttribute(): float

levelUp() updated to increment max_nerve by 2.


New Files

File Purpose
app/Http/Controllers/Game/GymController.php index() and train() actions
app/Services/Game/TrainingService.php Training logic, daily cap check, stat increment
app/Models/GymSession.php Eloquent model for gym_sessions
app/Console/Commands/Game/ProcessNerveRegen.php Trickle regen command
app/Console/Commands/Game/NerveMidnightRefill.php Midnight full refill command
resources/views/game/gym/index.blade.php Gym page (dark crime theme)
database/migrations/..._add_nerve_to_characters_table.php nerve columns on characters
database/migrations/..._add_nerve_cost_to_crimes_table.php nerve_cost on crimes
database/migrations/..._add_nerve_cost_to_crime_attempts_table.php nerve_cost on crime_attempts
database/migrations/..._create_gym_sessions_table.php gym_sessions table

Existing Files Modified

File Change
config/game.php Add nerve + gym blocks; add gym to energy.action_costs
app/Models/Character.php Add nerve fields, methods, levelUp() update
app/Http/Controllers/Game/CrimeController.php Add nerve check + deduction in attempt()
app/Services/Game/CrimeService.php Record nerve_cost on CrimeAttempt
routes/game.php Add gym routes
bootstrap/app.php (or Kernel) Register two new scheduled commands
resources/views/game/layouts/game.blade.php (or nav partial) Add Gym link to sidebar
resources/views/game/partials/ Add nerve bar partial (mirrors energy-bar)
resources/views/game/dashboard.blade.php Show nerve bar alongside energy + health

UI — Gym Page

Dark crime theme, consistent with existing pages. Shows:

  • Four stat cards (Strength, Defense, Speed, Dexterity)
  • Each card: current stat value, sessions used today / daily cap, energy cost per session
  • Train button: disabled (greyed) if capped or insufficient energy, active otherwise
  • Flash message area for success/failure feedback

Scheduler Registration

Schedule::command('game:nerve-regen')->everyFiveMinutes();
Schedule::command('game:nerve-midnight-refill')->dailyAt('00:00');

Out of Scope (this iteration)

  • Diminishing returns on stat gains
  • Money cost for training
  • Nerve cost > 1 for specific crimes (column added, seeded at 1 for all)
  • Nerve displayed on crime cards (can add later)