# Escape from Jail Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add a Bust Out mechanic to the jail page — spend 1000 cash, roll against a dexterity+speed formula, succeed to escape or fail to extend your sentence by 1.5×. **Architecture:** A new `JailService` holds all bust-out logic (mirrors the existing `HealthService::revive()` pattern). A new `JailController` handles the HTTP action. The existing jail view gains a Bust Out card with a CSRF form. No new database migrations. **Tech Stack:** Laravel 11, PHP 8.2, Blade templating, Eloquent ORM, Laravel Sail (Docker). All `artisan` commands must be run via `./vendor/bin/sail artisan`. ## Global Constraints - Project runs in **Laravel Sail** — all `php artisan` commands must use `./vendor/bin/sail artisan` - Dark crime theme: Tailwind classes `bg-syndicate-*`, `text-crime-*`, `bg-crime-*`, `text-yellow-*` — match the existing jail page colour palette (yellow for jail, not purple/green) - Services are instantiated with `new` in controllers — no DI container injection (see existing `CharacterController`, `GymController`) - No PHPUnit in this project — manual verification via `./vendor/bin/sail artisan tinker` and browser testing only - Flash messages use `session()->flash('success', ...)` / `session()->flash('error', ...)`; displayed via existing layout partials - CSRF required on all POST forms (`@csrf`) - Routes file: `routes/game.php` --- ## File Map | Action | File | |--------|------| | Modify | `config/game.php` — add two keys to `jail` block | | Create | `app/Services/Game/JailService.php` | | Create | `app/Http/Controllers/Game/JailController.php` | | Modify | `routes/game.php` — add bust-out route and pass new vars to jail view | | Modify | `resources/views/game/jail.blade.php` — add Bust Out card | --- ### Task 1: Config + JailService **Files:** - Modify: `config/game.php` (lines 81–86) - Create: `app/Services/Game/JailService.php` **Interfaces:** - Produces: - `JailService::bustOut(Character $character): array{success: bool, message: string}` - `JailService::sendToJail(Character $character, int $seconds): void` - `JailService::releaseFromJail(Character $character): void` - `JailService::calculateBustOutChance(Character $character): float` (returns 0.0–1.0) - [ ] **Step 1: Add two config keys to the `jail` block in `config/game.php`** Open `config/game.php`. The existing `jail` block is at lines 81–86: ```php // Jail system configuration 'jail' => [ 'sentence_length' => 3600, 'breakout_chance' => 0.05, 'breakout_cost' => 1000, 'sentence_multiplier' => 1.5, 'bust_out_stat_multiplier' => 0.003, // per combined dex+speed point 'bust_out_max_chance' => 0.80, // cap at 80% ], ``` Replace the existing block (4 keys) with the 6-key version above. - [ ] **Step 2: Verify config is readable** ```bash ./vendor/bin/sail artisan tinker ``` In the tinker prompt: ```php config('game.jail.bust_out_stat_multiplier'); // expect 0.003 config('game.jail.bust_out_max_chance'); // expect 0.8 exit ``` - [ ] **Step 3: Create `app/Services/Game/JailService.php`** ```php dexterity + $character->speed) * $multiplier); } /** * Attempt to bust out of jail. * * Deducts breakout_cost cash regardless of outcome. * On success: releases character immediately. * On failure: multiplies remaining sentence by sentence_multiplier. * * @return array{success: bool, message: string} */ public function bustOut(Character $character): array { $cost = config('game.jail.breakout_cost', 1000); if ($character->money < $cost) { return [ 'success' => false, 'message' => 'You need $' . number_format($cost) . ' to attempt a bust-out.', ]; } $character->money -= $cost; $successRate = $this->calculateBustOutChance($character); $roll = random_int(0, 9999) / 10000; if ($roll <= $successRate) { $this->releaseFromJail($character); $character->save(); return [ 'success' => true, 'message' => "You escaped! You're free.", ]; } // Failure: extend sentence $multiplier = config('game.jail.sentence_multiplier', 1.5); $remaining = max(0, $character->in_jail_until->diffInSeconds(now())); $extended = (int) ceil($remaining * $multiplier); $character->in_jail_until = now()->addSeconds($extended); $character->save(); $newReleaseTime = $character->in_jail_until->diffForHumans(); return [ 'success' => false, 'message' => "Bust failed — your sentence was extended to {$newReleaseTime}.", ]; } /** * Send a character to jail for the given number of seconds. */ public function sendToJail(Character $character, int $seconds): void { $character->status = 'in_jail'; $character->in_jail_until = now()->addSeconds($seconds); $character->save(); } /** * Release a character from jail immediately. */ public function releaseFromJail(Character $character): void { $character->status = 'active'; $character->in_jail_until = null; } } ``` - [ ] **Step 4: Smoke-test the service in tinker** ```bash ./vendor/bin/sail artisan tinker ``` ```php use App\Services\Game\JailService; use App\Models\Character; $svc = new JailService(); // Fake a character with known stats $char = new Character(['dexterity' => 5, 'speed' => 5]); $chance = $svc->calculateBustOutChance($char); // expect: 0.05 + (5+5)*0.003 = 0.08 echo $chance; // 0.08 $char2 = new Character(['dexterity' => 50, 'speed' => 50]); echo $svc->calculateBustOutChance($char2); // 0.35 $char3 = new Character(['dexterity' => 100, 'speed' => 100]); echo $svc->calculateBustOutChance($char3); // 0.8 (capped) exit ``` - [ ] **Step 5: Commit** ```bash git add config/game.php app/Services/Game/JailService.php git commit -m "feat: add JailService and bust-out config keys" ``` --- ### Task 2: JailController + Route **Files:** - Create: `app/Http/Controllers/Game/JailController.php` - Modify: `routes/game.php` **Interfaces:** - Consumes: - `JailService::bustOut(Character): array{success: bool, message: string}` (Task 1) - `JailService::calculateBustOutChance(Character): float` (Task 1) - Produces: - Route `game.jail.bust-out` (POST `/game/jail/bust-out`) - Updated jail view route passes `$bustOutChance` and `$bustOutCost` to the view - [ ] **Step 1: Create `app/Http/Controllers/Game/JailController.php`** ```php jailService = new JailService(); } /** * Attempt to bust out of jail. */ public function bustOut(Request $request): RedirectResponse { $character = $request->user()->character; if (!$character || !$character->isInJail()) { return redirect()->route('game.dashboard'); } $result = $this->jailService->bustOut($character); if ($result['success']) { return redirect()->route('game.dashboard') ->with('success', $result['message']); } return redirect()->route('game.jail') ->with('error', $result['message']); } } ``` - [ ] **Step 2: Add the bust-out route and update the jail GET route in `routes/game.php`** The existing jail route group starts at line 81: ```php // Jail route (for when character is in jail) Route::middleware(['auth'])->group(function () { Route::get('/game/jail', function () { $character = auth()->user()->character; if (!$character || !$character->isInJail()) { return redirect()->route('game.dashboard'); } $releaseTime = $character->in_jail_until ? $character->in_jail_until->diffForHumans() : 'unknown'; $bustOutChance = (int) round((new \App\Services\Game\JailService())->calculateBustOutChance($character) * 100); $bustOutCost = config('game.jail.breakout_cost', 1000); return view('game.jail', compact('character', 'releaseTime', 'bustOutChance', 'bustOutCost')); })->name('game.jail'); Route::post('/game/jail/bust-out', [\App\Http\Controllers\Game\JailController::class, 'bustOut']) ->name('game.jail.bust-out'); }); ``` Replace the existing jail group (lines 81–91 of `routes/game.php`) with the block above. - [ ] **Step 3: Verify routes are registered** ```bash ./vendor/bin/sail artisan route:list --name=game.jail ``` Expected output includes two rows: ``` GET /game/jail game.jail POST /game/jail/bust-out game.jail.bust-out ``` - [ ] **Step 4: Commit** ```bash git add app/Http/Controllers/Game/JailController.php routes/game.php git commit -m "feat: add JailController and bust-out route" ``` --- ### Task 3: Jail View — Bust Out Card **Files:** - Modify: `resources/views/game/jail.blade.php` **Interfaces:** - Consumes (variables passed from the route closure, Task 2): - `$character` — Character model (has `->money`) - `$releaseTime` — string, e.g. "in 45 minutes" - `$bustOutChance` — int (percentage, e.g. 8 for 8%) - `$bustOutCost` — int (e.g. 1000) - `$bustOutCost` formatted with `number_format()` in the view - [ ] **Step 1: Replace `resources/views/game/jail.blade.php` with the updated version** ```blade @extends('layouts.game') @section('title', 'Jail') @section('game-content')
{{-- Flash messages --}} @if(session('success'))
{{ session('success') }}
@endif @if(session('error'))
{{ session('error') }}
@endif
🚔

You're in Jail

Your character has been arrested and is currently serving time.

{{-- Status card --}}

Your Status

Status In Jail
Release Time {{ $releaseTime }}
Cash on Hand ${{ number_format($character->money) }}
{{-- Bust Out card --}}

Bust Out

Spend cash for a chance to escape early. Failure extends your sentence.

Success Chance {{ $bustOutChance }}%
Cost ${{ number_format($bustOutCost) }}
On Failure Sentence × 1.5
@if($character->money >= $bustOutCost)
@csrf
@else @endif

Or wait for your sentence to complete automatically.

Back to Dashboard
@endsection ``` - [ ] **Step 2: Verify the jail page renders** In a browser (or via `curl`), navigate to `/game/jail` while logged in with a jailed character. Confirm: - Status card shows release time and cash on hand - Bust Out card shows correct % and cost - If character has ≥ $1,000: button is active (yellow), form submits to `game.jail.bust-out` - If character has < $1,000: button is disabled, grey, shows tooltip To temporarily put a character in jail for testing: ```bash ./vendor/bin/sail artisan tinker ``` ```php use App\Models\Character; $c = Character::first(); $c->status = 'in_jail'; $c->in_jail_until = now()->addHour(); $c->save(); exit ``` Then visit `/game/jail`. - [ ] **Step 3: Test the full bust-out flow** With the jailed character, click "Bust Out": - **Success path:** You are redirected to `/game` with a green "You escaped! You're free." flash. - **Failure path:** You stay on `/game/jail` with a red "Bust failed — your sentence was extended to..." flash and the release time has increased. Verify cash was deducted in both cases: ```bash ./vendor/bin/sail artisan tinker ``` ```php use App\Models\Character; Character::first()->money; // should be 1000 less than before exit ``` - [ ] **Step 4: Commit** ```bash git add resources/views/game/jail.blade.php git commit -m "feat: add bust-out card to jail page" ```