feat(world): add persistent citizen simulation with needs, jobs, and social features
- Adds Character model with survival needs (hunger, thirst, energy) and state machine - Implements city:tick world ticker for offline progression every minute - Adds city territory expansion and building construction system - Introduces job economy, inventory, and transaction audit trail - Adds social graph (friends, marriage, children) and character chat - Implements admin approval workflow for new registrations - Adds hospital admission system for traffic accidents - Includes comprehensive feature tests and world seeders
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
# Cidadãos Persistentes — 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:** Each admin-approved user gets a persistent human-like character (needs, jobs, housing, family, chat) in a multi-city world that progresses via an always-on scheduler without requiring anyone online.
|
||||
|
||||
**Architecture:** Laravel 13 + MariaDB hold all character/world state. `city:tick` (every minute) applies need decay, salaries, building progress and simple offline AI. The existing Three.js viewer becomes a client for the caller's character (throttled position sync) plus action HUD/UI. Admin manages citizen approval, territory expansion and new cities.
|
||||
|
||||
**Tech Stack:** Laravel 13, PHP 8.3+, MariaDB, Sanctum (existing), Three.js viewer (existing), PHPUnit, Playwright MCP. No new production dependencies.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-18-citizens-persistence-design.md`
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- PHP 8.3+; follow existing conventions (Form Requests, `#[Fillable]`, English PHPDoc, Pint).
|
||||
- All user-visible strings in `lang/pt_BR.json`, `lang/en.json`, `lang/es.json`.
|
||||
- LGPD protections, CAPTCHA, CSRF unchanged; no secrets in repo.
|
||||
- `php artisan test` and `vendor/bin/pint --test` must pass before considering a task done.
|
||||
- Registration must leave users `pending` until admin approval.
|
||||
- World must progress with zero users online (`city:tick` + `schedule:run`).
|
||||
- Character death is not simulated; needs floor at 0 with energy penalties only.
|
||||
|
||||
## Review Focus
|
||||
|
||||
1. **Unapproved login:** user with `status=pending` must not enter dashboard/city; expect friendly wait screen + logout.
|
||||
2. **Tick idempotency:** running `city:tick` twice in one minute must not double-decay needs or double-pay salary (guard by `last_tick_at`).
|
||||
3. **Ownership:** actions (eat/build/sleep/position) must reject another user's character ID (403/422).
|
||||
4. **Build occupancy:** two characters must not complete houses on the same footprint (unique site check by AABB).
|
||||
5. **City bounds:** movement and building must clamp/fail outside active city bounds; new city layout must derive from `cities.bounds`, not only store positions.
|
||||
6. **Family graph:** spouse requires accepted friendship; child requires both spouses; child NPC has `user_id=null` and never logs in.
|
||||
7. **Migration safety:** existing `is_active` users become `active` and can still log in after the `status` column ships.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Schema + seeders (cities, characters, buildings, relationships, inventory, transactions, jobs, messages)
|
||||
|
||||
**Files:**
|
||||
- Create: `database/migrations/2026_10_18_000100_add_status_to_users_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000200_create_cities_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000300_create_characters_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000400_create_buildings_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000500_create_relationships_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000600_create_inventory_items_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000700_create_transactions_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000800_create_jobs_table.php`
|
||||
- Create: `database/migrations/2026_10_18_000900_create_character_messages_table.php`
|
||||
- Create: `app/Models/City.php`, `Character.php`, `Building.php`, `Relationship.php`, `InventoryItem.php`, `Transaction.php`, `Job.php`, `CharacterMessage.php`
|
||||
- Create: `database/factories/CharacterFactory.php`, `CityFactory.php`
|
||||
- Create: `database/seeders/CityWorldSeeder.php` (City10 + jobs; backfill users status; ensure characters for active users)
|
||||
- Modify: `database/seeders/DatabaseSeeder.php` (register seeder)
|
||||
- Modify: `app/Models/User.php` (status const/cast/relation)
|
||||
|
||||
**Steps:**
|
||||
- [ ] Write migrations/models/factories/seeder per spec §3
|
||||
- [ ] `php artisan migrate` in container
|
||||
- [ ] Feature test: seeder creates City10 + character for active user; `users.status` backfilled
|
||||
- [ ] Run `php artisan test --filter=CityWorld` and Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 2: Registration pending + admin citizens approval
|
||||
|
||||
**Files:**
|
||||
- Create: `app/Http/Controllers/Web/Admin/CitizenAdminController.php`
|
||||
- Create: `resources/views/admin/citizens/index.blade.php`
|
||||
- Create: `app/Http/Requests/...` if needed
|
||||
- Modify: `app/Http/Controllers/Web/AuthController.php` (register → pending; login branch for pending)
|
||||
- Create: `resources/views/auth/pending.blade.php`
|
||||
- Modify: `routes/web.php` (admin citizens routes; pending view route or inline response)
|
||||
- Modify: `app/Http/Middleware/EnsureUserIsActive.php` if needed
|
||||
- Modify: lang files (pending/approve/ban strings)
|
||||
- Modify: `resources/views/dashboard` or layout nav (Admin → Cidadãos link)
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing tests: register creates pending; login as pending redirects to wait screen; admin approve activates + creates character; banned cannot login
|
||||
- [ ] Implement controller/views/routes
|
||||
- [ ] `php artisan test --filter=Citizen` green + Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 3: World ticker `city:tick` + schedule
|
||||
|
||||
**Files:**
|
||||
- Create: `app/Console/Commands/CityTick.php`
|
||||
- Modify: `routes/console.php` (everyMinute)
|
||||
- Create: `tests/Feature/CityTickTest.php`
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing test: needs decay once per call; second immediate call no-ops; sleep recovers energy; work pays; building progress completes at 100
|
||||
- [ ] Implement command + offline AI rules (spec §4)
|
||||
- [ ] Tests green + Pint; `php artisan schedule:list` shows entry
|
||||
- [ ] Commit
|
||||
|
||||
### Task 4: City data payload + layout from bounds + character position sync
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/Http/Controllers/Web/CityViewerController.php` (city param, characters/buildings/jobs/bounds; layout from city bounds)
|
||||
- Modify: `app/Http/Controllers/Api/...` if mirrored
|
||||
- Create: `app/Http/Controllers/Web/CityActionController.php` (position + actions stubs for this task: position only)
|
||||
- Modify: `routes/web.php`
|
||||
- Create: `tests/Feature/CityDataTest.php`, `CityPositionTest.php`
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing tests: `/city/data` includes own character + bounds; layout covers full bounds; position POST persists and clamps to bounds; cannot set another character
|
||||
- [ ] Implement
|
||||
- [ ] Tests green + Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 5: Actions API (eat/drink/sleep/wake/work/study/buy/sell/build)
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/Http/Controllers/Web/CityActionController.php`
|
||||
- Create: Form Requests under `app/Http/Requests/`
|
||||
- Create: `tests/Feature/CityActionsTest.php`
|
||||
- Modify: lang files (action labels/errors)
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing tests per action happy path + key rejects (no house, no job, no money, occupied site, insufficient materials)
|
||||
- [ ] Implement actions with DB transactions where money/inventory move
|
||||
- [ ] Tests green + Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 6: Social (friends, spouse, child, character chat)
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/Http/Controllers/Web/CitySocialController.php` (create)
|
||||
- Create: `app/Http/Controllers/Web/CharacterChatController.php` or extend `ChatController`
|
||||
- Create: `tests/Feature/CitySocialTest.php`
|
||||
- Modify: lang files
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing tests: friend request/accept; marry requires accepted friend + no existing spouse; child creates NPC character with parent links; message POST persists and is readable by recipient
|
||||
- [ ] Implement
|
||||
- [ ] Tests green + Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 7: Admin territory expand + multi-city
|
||||
|
||||
**Files:**
|
||||
- Create: `app/Http/Controllers/Web/Admin/CityAdminController.php`
|
||||
- Create: `resources/views/admin/cities/*` (index + create + expand form)
|
||||
- Modify: `routes/web.php`
|
||||
- Create: `tests/Feature/CityExpansionTest.php`
|
||||
- Modify: lang files
|
||||
|
||||
**Steps:**
|
||||
- [ ] Failing tests: create city; expand bounds +40 up to cap; layout for new city uses its bounds
|
||||
- [ ] Implement
|
||||
- [ ] Tests green + Pint
|
||||
- [ ] Commit
|
||||
|
||||
### Task 8: Viewer frontend — own character, HUD needs, action menu, citizens, buildings
|
||||
|
||||
**Files:**
|
||||
- Modify: `resources/views/city/viewer.blade.php` (HUD + action panel markup)
|
||||
- Modify: `public/js/city/app.js` (own character control, needs HUD poll, action fetches, render characters/buildings from payload, bounds clamp, position throttle)
|
||||
- Modify: lang files
|
||||
|
||||
**Steps:**
|
||||
- [ ] Manual/Playwright: after Task 2–7, load `/city`, see own avatar, move persists (reload keeps position), action buttons update needs/money, second character visible, build marker
|
||||
- [ ] No console errors; Pint N/A (JS); `node --check app.js`
|
||||
- [ ] Commit
|
||||
|
||||
### Task 9: End-to-end Playwright + bugfix pass
|
||||
|
||||
**Files:**
|
||||
- Screenshots under `.playwright-mcp/`
|
||||
- Fix any bugs found in previous tasks
|
||||
|
||||
**Steps:**
|
||||
- [ ] Full journey: register → pending screen → admin approves → login → accept terms if needed → `/city` → move → eat/work/sleep → build house → friend with second user → chat → admin expand → new city
|
||||
- [ ] Console clean; PHPUnit full suite green; Pint green
|
||||
- [ ] Final commit
|
||||
@@ -0,0 +1,131 @@
|
||||
# City10 — Cidadãos Persistentes, Economia Social e Expansão Territorial
|
||||
|
||||
**Data:** 2026-10-18
|
||||
**Status:** Aprovado pelo usuário (design + spec + implementação em um ciclo)
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Cada usuário registrado e **aprovado pelo Admin** ganha um **personagem (Character)** próprio na plataforma 3D, capaz de viver como um humano simulado: mover-se, construir e habitar, trabalhar, comer, estudar, comprar/vender, dormir/acordar, fazer amigos, formar família e ter filhos, conversar com outros personagens. O mundo **persiste em nuvem sem reiniciar** quando alguém entra ou sai, progride continuamente via ticker agendado, e suporta **expansão de território** e **criação de novas cidades**.
|
||||
|
||||
## 2. Aprovação de contas e personagem
|
||||
|
||||
- `users.status`: `pending` | `active` | `banned` (campo novo; `is_active` permanece espelhando `status === active` para compatibilidade).
|
||||
- `POST /register` cria usuário como `pending` (sem login no mundo até aprovação; login pendente mostra tela "aguardando aprovação").
|
||||
- Admin: rota `/admin/citizens` — listar, aprovar (`active`), banir (`banned`).
|
||||
- Ao transitar para `active`, cria-se `Character` automaticamente (factory: nome = first name do usuário, aparência aleatória, spawn no centro de `cities` ativa).
|
||||
- Usuários existentes com `is_active=true` migram para `status=active`; character é criado sob demanda no primeiro acesso se faltar (lazy-seed).
|
||||
|
||||
## 3. Modelo de dados
|
||||
|
||||
### cities
|
||||
`id, name, slug, origin_x/z (não usado — bounds), bounds_min_x, bounds_max_x, bounds_min_z, bounds_max_z, is_active, created_at, updated_at`
|
||||
- Seed: `City10` com bounds equivalentes ao grid atual (−80..80).
|
||||
|
||||
### characters
|
||||
`id, user_id (unique), city_id, name, skin, hair, shirt, pos_x, pos_y, pos_z, money (decimal), energy (0-100), hunger (0-100), thirst (0-100), state (awake|sleeping|working|studying|eating), job_id (nullable), house_id (nullable), education (0-100), is_online (boolean), last_tick_at, created_at, updated_at`
|
||||
|
||||
### buildings
|
||||
`id, city_id, owner_character_id, type (house|shop_extension|school|restaurant), name, pos_x, pos_z, width, depth, height, is_complete, build_progress (0-100), created_at, updated_at`
|
||||
|
||||
### relationships
|
||||
`id, character_a_id, character_b_id, type (friend|spouse|parent_child), status (pending|accepted), created_at`
|
||||
- `parent_child`: `character_a` = filho, `character_b` = pai/mãe (para filhos NPCs: child pode ter `user_id` null via character órfão — na prática filhos são `characters` sem `user_id`).
|
||||
|
||||
### inventory_items
|
||||
`id, character_id, item (food|water|materials|product), qty, created_at, updated_at` (unique character_id+item)
|
||||
|
||||
### transactions
|
||||
`id, from_character_id, to_character_id (nullable = loja/NPC), store_id (nullable), item, qty, amount, created_at`
|
||||
|
||||
### jobs
|
||||
`id, city_id, store_id (nullable), title, salary, requires_education (0-100), created_at, updated_at`
|
||||
- Seed: Padeiro (Padaria), Mercado, Prefeitura, Escola.
|
||||
|
||||
### children_npc
|
||||
Filhos nascem como `characters` com `user_id = NULL`, `state=awake`, idade simulada via `created_at` + `growth_days` opcional. Sem login.
|
||||
|
||||
## 4. Ticker de mundo (`city:tick`)
|
||||
|
||||
Comando Artisan agendado a cada 1 minuto (`routes/console.php` → `Schedule::command('city:tick')->everyMinute()`):
|
||||
|
||||
Para cada character com `state != 'dead'` (não existe morte):
|
||||
1. **Necessidades:** `hunger -= 0.5`, `thirst -= 0.8`, `energy -= 0.4` por tick (proporção ao delta real de tempo desde `last_tick_at`, com cap).
|
||||
2. **Dormindo** (`state=sleeping`): `energy += 15` até 100; se energy>=100 → `state=awake`.
|
||||
3. **Trabalhando** (`state=working` com `job_id`): creditar `salary/30` (dia de trabalho ≈ 30 ticks) em `money`; consumir energy extra.
|
||||
4. **Estudando:** `education += 0.5` até 100, consome energy.
|
||||
5. **Fome/sede severas** (hunger<=0 ou thirst<=0): energy drena 2× e `state` vira `awake` (não morre).
|
||||
6. **IA offline simples:** se `is_online=false` e energy<20 e house existe → dormir; se energy>80 e job existe e horário “dia” (tick % 1440 entre 480–1080) → trabalhar; senão awake.
|
||||
7. **Filhos:** characters sem `user_id` com `education` crescendo automaticamente se `age_days < 18*365` (simplificado: `growth` +1/tick até cap de adulto).
|
||||
|
||||
`city:tick` é idempotente por `last_tick_at` (skip se < 50s).
|
||||
|
||||
## 5. Gameplay (ações do jogador)
|
||||
|
||||
Endereços web (sessão) + espelho API Sanctum opcional:
|
||||
|
||||
| Ação | Rota | Efeito |
|
||||
|---|---|---|
|
||||
| Comer | `POST /city/actions/eat` | consome 1 food do inventário; hunger +40 |
|
||||
| Beber | `POST /city/actions/drink` | consome water; thirst +40 |
|
||||
| Dormir | `POST /city/actions/sleep` | state=sleeping (exige casa completa) |
|
||||
| Acordar | `POST /city/actions/wake` | state=awake |
|
||||
| Trabalhar | `POST /city/actions/work` | state=working (exige emprego) |
|
||||
| Parar trabalho | `POST /city/actions/stop-work` | state=awake |
|
||||
| Estudar | `POST /city/actions/study` | state=studying (exige school na cidade) |
|
||||
| Comprar | `POST /city/actions/buy` | store → inventário, money−preço |
|
||||
| Vender | `POST /city/actions/sell` | inventário → money |
|
||||
| Construir casa | `POST /city/actions/build` | valida terreno livre em quadra + `materials ≥ 50`; cria building `build_progress=0` e **de**bita os materials no ato; ticker avança `build_progress += 10` por tick até 100 → `is_complete=true` e vira `house_id` do dono se ainda não tiver casa |
|
||||
| Amizade | `POST /city/social/friend` + `accept` | relationship friend pending/accepted |
|
||||
| Casamento | `POST /city/social/marry` | ambos accepted friends → spouse (1 esposo) |
|
||||
| Filho | `POST /city/social/child` | exige casal spouse; cria character NPC criança |
|
||||
| Chat | `POST /city/chat/{character}` (estender fluxo existente) | mensagens persistidas em `character_messages` (nova tabela simples: id, from, to, body, created_at) |
|
||||
|
||||
Validações em Form Requests; autorização via policy/ownership (só o próprio character).
|
||||
|
||||
## 6. Viewer 3D (evolução do existente)
|
||||
|
||||
- Rota `GET /city` usa `?city=` (slug/id; default ativa).
|
||||
- `/city/data` retorna também `characters` (posição, estado, nome, money/needs do próprio), `buildings`, `jobs`, bounds da cidade.
|
||||
- Controla **character do usuário**: WASD grava posição localmente e **persiste** (throttled POST `/city/actions/position`, 1–2 s).
|
||||
- HUD: fome, sede, energia, dinheiro, estado.
|
||||
- Menu de ações (botões) chamando as rotas de ações; inventário simples.
|
||||
- Cidadãos (characters com user ou NPC filhos) renderizados como humanoides; `is_online` destaca.
|
||||
- Terrenos livres: quadras sem building de casa → painel “Construir aqui” quando próximo.
|
||||
- Limites da cidade aplicados (clipe nos bounds da cidade ativa).
|
||||
|
||||
## 7. Expansão e multi-cidade
|
||||
|
||||
- Admin `POST /admin/cities` cria cidade nova (bounds default 160×160) + layout grid gerado no cliente a partir de bounds (estender `CityViewerController::cityLayout` para usar bounds da cidade, não só stores).
|
||||
- Admin `POST /admin/cities/{city}/expand` aumenta `bounds_max_*` em 40 (uma quadra) — limitado a 10 expansões/cidade (coluna `expansions`).
|
||||
- Viewer e ticker filtram por `city_id`.
|
||||
|
||||
## 8. Persistência em nuvem
|
||||
|
||||
- Estado exclusivamente em MariaDB; `city:tick` via cron `* * * * * php artisan schedule:run` documentado no README de deploy.
|
||||
- Entrar/sair não reseta nada; múltiplas sessões simultâneas: posição mais recente vence (`updated_at`).
|
||||
- Fila opcional não é obrigatória neste ciclo.
|
||||
|
||||
## 9. LGPD e regras do repo
|
||||
|
||||
- Sem dados pessoais em payloads de IA; chat entre characters é dado do usuário (consentimento LGPD já coberto pelos termos).
|
||||
- Nenhuma remoção de proteção LGPD; CAPTCHA mantido; CSRF; i18n completa (pt_BR/en/es) para TODAS as strings novas.
|
||||
- PHPDoc em inglês; Pint; testes PHPUnit nos fluxos; licenças registradas em RULES.md se houver dependência nova (não planejado).
|
||||
|
||||
## 10. Testes e critérios de aceite
|
||||
|
||||
**PHPUnit:**
|
||||
- Registro cria `pending`; login pendente bloqueado com mensagem.
|
||||
- Admin aprova → `active` + character criado.
|
||||
- Tick degrada necessidades; dormir recupera energy; trabalhar paga salário; comer/beber funcionam com inventário.
|
||||
- Comprar/vender alteram money/inventário/transactions.
|
||||
- Construir valida terreno e materiais; progresso avança no tick.
|
||||
- Amizade → casamento → filho cria character NPC.
|
||||
- Expansão de bounds e criação de cidade pelo admin.
|
||||
|
||||
**Playwright MCP:**
|
||||
- Fluxo completo: register → pending → login vê espera → admin aprova → usuário entra no city com character → HUD necessidades → comer/dormir/trabalhar via UI → construir casa → adicionar amigo (2º usuário) → chat → admin expande território → nova cidade visível.
|
||||
- Console sem erros; screenshots de evidência; corrigir bugs até limpo.
|
||||
|
||||
## 11. Fora de escopo
|
||||
|
||||
Morte permanente, combate, Reverb realtime, OpenRouter obrigatório no tick, moeda fiat real, mobile nativo.
|
||||
Reference in New Issue
Block a user