# 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: ```php '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`: ```php '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 ```php 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)