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,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.