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:
2026-10-17 21:46:23 -03:00
parent 79d12fa2f4
commit 79ce2a1825
132 changed files with 11809 additions and 1036 deletions
@@ -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.