docs: add escape from jail design spec
This commit is contained in:
@@ -0,0 +1,138 @@
|
|||||||
|
# Escape from Jail — Design Spec
|
||||||
|
|
||||||
|
**Date:** 2026-06-28
|
||||||
|
**Status:** Approved
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Add a **Bust Out** mechanic to the jail page. A jailed character can spend cash to attempt an escape. Success rate is based on `dexterity + speed` stats. Success releases immediately; failure multiplies remaining sentence by 1.5×. Cash is always deducted regardless of outcome.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
No new tables or columns required. Uses existing `characters.status` and `characters.in_jail_until`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bust-Out Mechanics
|
||||||
|
|
||||||
|
### Cost
|
||||||
|
1000 cash (from `config('game.jail.breakout_cost')`). Deducted whether the attempt succeeds or fails.
|
||||||
|
|
||||||
|
### Success Rate Formula
|
||||||
|
```
|
||||||
|
success_rate = min(
|
||||||
|
config('game.jail.bust_out_max_chance'),
|
||||||
|
config('game.jail.breakout_chance') + (dexterity + speed) * config('game.jail.bust_out_stat_multiplier')
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
With defaults:
|
||||||
|
- `breakout_chance` = 0.05 (existing)
|
||||||
|
- `bust_out_stat_multiplier` = 0.003 (new)
|
||||||
|
- `bust_out_max_chance` = 0.80 (new)
|
||||||
|
|
||||||
|
A fresh character (dex=5, speed=5) starts at **8%**. A well-trained character (dex=50, speed=50) reaches **35%**. Max possible is **80%**.
|
||||||
|
|
||||||
|
### On Success
|
||||||
|
- Deduct `breakout_cost` cash
|
||||||
|
- Set `status → active`
|
||||||
|
- Set `in_jail_until → null`
|
||||||
|
- Redirect to `game.dashboard` with success flash
|
||||||
|
|
||||||
|
### On Failure
|
||||||
|
- Deduct `breakout_cost` cash
|
||||||
|
- Multiply remaining jail time by `sentence_multiplier` (1.5×):
|
||||||
|
`in_jail_until = now()->addSeconds(ceil($character->in_jail_until->diffInSeconds(now()) * 1.5))`
|
||||||
|
- Redirect back to `game.jail` with failure flash showing new release time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Config Changes (`config/game.php`)
|
||||||
|
|
||||||
|
Add two keys to the existing `jail` block:
|
||||||
|
|
||||||
|
```php
|
||||||
|
'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%
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## New Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `app/Services/Game/JailService.php` | `bustOut(Character): array`, `sendToJail(Character, int): void`, `releaseFromJail(Character): void` |
|
||||||
|
| `app/Http/Controllers/Game/JailController.php` | `bustOut(Request): RedirectResponse` |
|
||||||
|
|
||||||
|
### `JailService` interface
|
||||||
|
|
||||||
|
```php
|
||||||
|
/**
|
||||||
|
* @return array{success: bool, message: string}
|
||||||
|
*/
|
||||||
|
public function bustOut(Character $character): array
|
||||||
|
|
||||||
|
public function sendToJail(Character $character, int $seconds): void
|
||||||
|
|
||||||
|
public function releaseFromJail(Character $character): void
|
||||||
|
```
|
||||||
|
|
||||||
|
`bustOut()` returns `['success' => true/false, 'message' => string]`. The message is a human-readable outcome description suitable for a flash notice.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modified Files
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `config/game.php` | Add `bust_out_stat_multiplier` and `bust_out_max_chance` to `jail` block |
|
||||||
|
| `resources/views/game/jail.blade.php` | Add Bust Out card below status card |
|
||||||
|
| `routes/game.php` | Add `POST /game/jail/bust-out` → `JailController@bustOut` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Routes
|
||||||
|
|
||||||
|
```php
|
||||||
|
Route::middleware(['auth'])->group(function () {
|
||||||
|
Route::post('/game/jail/bust-out', [JailController::class, 'bustOut'])->name('game.jail.bust-out');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The bust-out route lives in the same `auth`-only group as the existing jail view route (no `verify.character` needed — jailed characters can't have characters, but do need auth).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI — Jail Page
|
||||||
|
|
||||||
|
Dark crime theme, consistent with existing pages.
|
||||||
|
|
||||||
|
**Bust Out card** sits below the existing status card. Contains:
|
||||||
|
- Success percentage (calculated from character stats, shown as e.g. "12% chance")
|
||||||
|
- Cash cost ("Costs $1,000")
|
||||||
|
- "Bust Out" button — disabled with tooltip if character has < 1000 cash
|
||||||
|
- CSRF-protected POST form
|
||||||
|
|
||||||
|
**Flash messages:**
|
||||||
|
- Success: "You escaped! You're free." → redirects to dashboard
|
||||||
|
- Failure: "Bust failed — your sentence was extended to [new release time]." → stays on jail page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of Scope (this iteration)
|
||||||
|
|
||||||
|
- Busting out another player (requires PvP)
|
||||||
|
- Nerve/energy cost for bust attempts
|
||||||
|
- Per-attempt cooldown (failure penalty is time extension only)
|
||||||
|
- Bust attempt history / activity log entry (can add later)
|
||||||
Reference in New Issue
Block a user