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
ProcessNerveRegenartisan command on the scheduler. - Midnight refill:
nerveset tomax_nervefor all characters at00:00daily. Handled by a newNerveMidnightRefillartisan 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
- Player selects a stat and submits the Train form.
- Controller verifies: character is active (not in jail/hospital), has ≥ 20 energy, daily cap not reached for that stat.
TrainingService::train($character, $stat)executes:- Deduct 20 energy from character.
- Increment the chosen stat by 1.
- Upsert
gym_sessionsrow: ifreset_date< today, resetsessions_todayto 0 first; then increment.
- 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): boolconsumeNerve(int $amount): booladdNerve(int $amount): voidgetNervePercentageAttribute(): 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)