Add university and fire station civic buildings plus 7 new store types, each with facade and luminous blade signs. Extend landmark material colors and sign accents. Fix ATC dispatch to reject prompt-echoing AI responses via heuristic guard. Add CityPlacesTest for collision and roadway avoidance, and extend AirportTest and SignLabelsTest.
373 lines
16 KiB
Markdown
Executable File
373 lines
16 KiB
Markdown
Executable File
# City10 — Cidade 3D Virtual com Agentes de IA
|
||
|
||
**City10** é uma plataforma full-stack de simulação urbana 3D com agentes de
|
||
inteligência artificial, construída sobre **Laravel 13** e **Three.js**. O
|
||
visualizador3D é jogável no navegador: trânsito semafórico, pedestres, agentes
|
||
(Prefeito, lojistas), conversas de rua com IA, praças com academia e uma praia
|
||
com lazer. O raciocínio dos NPCs acontece através da API
|
||
[OpenRouter.ai](https://openrouter.ai), restrito a uma lista de modelos
|
||
gratuitos.
|
||
|
||
> Plataforma operada pela **Syspanel** — Santa Rita do Sapucaí/MG, Brasil.
|
||
> Contato: [contato@syspanel.com.br](mailto:contato@syspanel.com.br) ·
|
||
> WhatsApp: (35) 99226-1684 · [www.syspanel.com.br](https://syspanel.com.br)
|
||
|
||
---
|
||
|
||
## Visão Geral da Arquitetura
|
||
|
||
| Camada | Tecnologia | Responsabilidade |
|
||
|---|---|---|
|
||
| Backend / Painel Admin | Laravel 13 (PHP ≥ 8.3) | Autenticação, API REST, regras de negócio, consentimento LGPD |
|
||
| Banco de Dados | MariaDB (MySQL) | Persistência de usuários, lojas, agentes, conversas e consentimentos |
|
||
| Cache / Fila | Database (sessões/cache/jobs) | Sessões, cache, agendamentos e throttle |
|
||
| Frontend 3D | Three.js | Mundo jogável no navegador (cidade, trânsito, praia, clima) |
|
||
| Tempo Real | Polling HTTP + SSE | Presence (~2,5 s), chat (~2,5 s) e stream de resposta de NPC (SSE) |
|
||
| Cérebro dos Agentes | OpenRouter.ai | Raciocínio dos NPCs e conversas com modelos gratuitos |
|
||
|
||
---
|
||
|
||
## Fase 1 — Plataforma (Entregue)
|
||
|
||
- Estrutura Laravel 13 com autenticação diferenciada (**Admin** / **Usuário Comum**).
|
||
- **Migrations**: usuários com `role`, lojas com coordenadas 3D (`pos_x`, `pos_y`, `pos_z`), agentes de IA e trilha de consentimento legal (LGPD).
|
||
- **API REST** (Sanctum) para usuários, lojas, agentes, cidade e documentos legais.
|
||
- **CAPTCHA matemático** próprio (soma), sem dependências externas, nos formulários públicos.
|
||
- **Multi-idioma**: Português (Brasil), Inglês e Espanhol — selecionável pelo usuário.
|
||
- **Termos de Uso, Política de Privacidade e Política de Cookies** (LGPD), versionados e com aceite auditável (data, hora, versão, IP).
|
||
- **Banner de cookies** com link para os Termos de Uso e Privacidade.
|
||
- **Página inicial** responsiva (desktop e smartphone).
|
||
|
||
---
|
||
|
||
## Fase 2 — Mundo 3D Jogável (Entregue)
|
||
|
||
O visualizador `/city` carrega o mundo completo em tempo real:
|
||
|
||
- **Cidade de 72 quarteirões** (grade 9 × 8 de blocos de 40 m; bounds
|
||
`−120..160 × −120..120`), com rio e ponte a oeste, extensão rodoviária ao
|
||
sul, casas de jogadores e um conjunto de lugares públicos com letreiros
|
||
luminosos: **hospital, escola, prefeitura, universidade, corpo de
|
||
bombeiros** (prédios cívicos) e **supermercado, drogaria, restaurante,
|
||
cabeleireiro, banco, hotel e shopping** (11 lojas no total, todas com
|
||
placa frontal + letreiro vertical blade).
|
||
- **Trânsito**: carros, motos, bikes e ônibus com tráfego à direita, loop por
|
||
quarteirão e **semáforos por cruzamento** (fases NS/EW com amarelos e
|
||
all-red; veículos param na linha de parada **antes** das faixas).
|
||
- **Faixas zebradas reais** (barras longitudinais paralelas ao fluxo, design
|
||
MUTCD) com calçadas e meio-fios interrompidos nas travessias — 323 faixas no
|
||
layout atual.
|
||
- **Pedestres** com travessia sincronizada ao semáforo; bloqueio de travessia
|
||
do jogador no vermelho.
|
||
- **Praças** (1 a cada 4 células) com academia externa: exercícios com pose,
|
||
efeito em saúde/energia e missão “Shape em dia”.
|
||
- **Praia a leste** (`/city` + `App\Support\Beach`): areia, mar com ondas
|
||
animadas (deslocamento de vértices), espuma na orla, **banhistas, nadadores,
|
||
surfistas, salva-vidas (torre + patrulha), quadra de basquete com jogadores
|
||
e bola em arremesso, barracas de comidas, cadeiras e guarda-sóis**.
|
||
O jogador pode **relaxar na areia** (tecla `E` → `POST /city/actions/beach`),
|
||
recuperando energia/saúde por tick até o máximo.
|
||
- **Dois aeroportos** derivados dos bounds (pavimento + sinalização de
|
||
pista com designadores, taxiways com eixo amarelo, pátio de embarque com
|
||
posições de estacionamento de aeronaves, terminal com pontes articuladas,
|
||
hangars, torre de controle e helipista): **7 aeronaves de 7 companhias**
|
||
(jato comercial, jato regional, monomotor e helicóptero) executam o ciclo
|
||
completo *taxi → decolagem → cruíse sobre a cidade → aproximação → pouso
|
||
→ taxi → gate*, com contagem de decolagens/pousos em `stats().flights`.
|
||
- **Torre de controle com IA**: o agente `atc` "Torre de Controle City10"
|
||
(modelo `AI_ATC_MODEL`, `poolside/laguna-xs-2.1:free`) gera clearances de
|
||
decolagem a cada decolagem (`POST /city/airport/dispatch`, cooldown de
|
||
30 s) numa **cadeia de modelos** `AI_ATC_MODEL` → default →
|
||
`AI_FALLBACK_MODEL` (`xiaomi/mimo-v2.6-flash`), com texto determinístico
|
||
quando tudo falha; o clearance mais recente aparece como aviso "✈ ...".
|
||
- **Agentes vivos**: prefeito itinerante, lojistas nas lojas, pedestres com
|
||
rotas, mais trens (passagem de nível com barreiras), ônibus/trem
|
||
embarcáveis, avião e barcos no rio.
|
||
- **Dia/noite e clima** com ciclo de horas, névoa, chuva e sombras que seguem
|
||
o jogador.
|
||
- **Conversas de rua** com outros jogadores e NPCs (ver próxima seção).
|
||
- **Performance**: *batching* estático por material, sombra seguindo o
|
||
jogador (janela ±120), lâmpadas/rodas com LOD por distância e fachadas a
|
||
256 px — medições ~30–60 fps numa GTX 770.
|
||
|
||
### Controles
|
||
|
||
| Controle | Ação |
|
||
|---|---|
|
||
| `W A S D` | Andar (A/D = lateral) |
|
||
| Mouse (pointer lock) | Olhar; **arrastar na cena** quando sem lock |
|
||
| `←` `→` | **Viram a câmera** (não andam de lado) |
|
||
| Joystick virtual (mobile) | Vertical = andar, horizontal = **virar a câmera** |
|
||
| `E` | Contexto: exercício na praça / relaxar na praia / parar |
|
||
| `Espaço` | Pular |
|
||
| Clique num NPC | Abrir chat |
|
||
| Chegar perto de alguém | Chat abre sozinho; sai de perto ele fecha |
|
||
|
||
---
|
||
|
||
## Conversas de Rua (`/talk/person`) e IA por Personagem
|
||
|
||
- **Registro por dia**: cada fala gravada em `person_talks`
|
||
(`from/to` de character ou agent + `talked_on` + `is_fallback`), consultável
|
||
via `GET /talk/person?kind=agent|character&peer=ID`.
|
||
- **Fio da meada**: o histórico completo (dias anteriores + hoje) é enviado
|
||
como contexto nas respostas de IA — placeholders de erro (`is_fallback`)
|
||
ficam visíveis na UI mas **nunca** entram no contexto do modelo.
|
||
- **Quem responde**:
|
||
- Par com **humano vivo** (sessão tocada nos últimos 5 min) → relay puro,
|
||
nunca é substituído por IA;
|
||
- Par **sem humano** (cidadãos demo, NPCs) → persona anônima de morador
|
||
responde via OpenRouter, sem dados cadastrais no payload.
|
||
- **Resposta de IA** é gerada *after-response* (não depende de worker de
|
||
fila), com espaçamento por par, allow-list de modelos, fallback localizado
|
||
em falha e auditoria em `last_decision`.
|
||
- **Identidade por personagem**: colunas em `characters` — `ai_model`,
|
||
`ai_age`, `ai_gender`, `ai_personality`, `ai_bio` (persona injetada no
|
||
prompt; recém-criados já nascem com personalidade/idade).
|
||
- **Modelo por personagem** (ordem de resolução, sempre validado em
|
||
`allowed_models`): coluna `ai_model` → mapa `AI_CHARACTER_MODELS` (id ou
|
||
nome) → slot determinístico do pool `AI_MODELS` → `AI_DEFAULT_MODEL`.
|
||
|
||
---
|
||
|
||
## Requisitos
|
||
|
||
- PHP **≥ 8.3** (com `pdo_mysql`, `mbstring`, `curl`, `zip`, `gd`, `bcmath`)
|
||
- Composer 2
|
||
- MariaDB ≥ 10.6 (ou MySQL ≥ 8.0)
|
||
- Node.js ≥ 18 (apenas para assets Vite/Tailwind, opcional na Fase 1)
|
||
- Docker (opcional, para o ambiente `dockercity10`)
|
||
|
||
---
|
||
|
||
## Instalação
|
||
|
||
### 1. Dependências PHP
|
||
|
||
```bash
|
||
composer install
|
||
```
|
||
|
||
### 2. Configuração do ambiente
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
php artisan key:generate
|
||
```
|
||
|
||
Edite o `.env` e configure o banco de dados:
|
||
|
||
```env
|
||
DB_CONNECTION=mariadb
|
||
DB_HOST=127.0.0.1
|
||
DB_PORT=3306
|
||
DB_DATABASE=laravel
|
||
DB_USERNAME=root
|
||
DB_PASSWORD=<sua_senha>
|
||
```
|
||
|
||
### 3. Chave do OpenRouter.ai e modelos
|
||
|
||
A chave **não** é versionada. Insira-a apenas no `.env` local:
|
||
|
||
```env
|
||
OPENROUTER_API_KEY=<sua_chave>
|
||
AI_DEFAULT_MODEL=deepseek/deepseek-v4-flash-0731
|
||
|
||
# Pool de modelos gratuitos distribuídos deterministicamente entre os
|
||
# personagens (cada um mantém o próprio modelo estável).
|
||
AI_MODELS=deepseek/deepseek-v4-flash-0731,nvidia/nemotron-3.5-lightning:free,liquid/lfm-2.5-2.6b:free,openrouter/free,nvidia/nemotron-3-ultra-550b-a55b:free
|
||
|
||
# Override por personagem: "nome ou id=modelo" separado por vírgula.
|
||
AI_CHARACTER_MODELS=
|
||
|
||
# AI_ATC_MODEL: default model of the control-tower agent (airport
|
||
# clearances: takeoff authorizations). Must be allow-listed.
|
||
AI_ATC_MODEL=poolside/laguna-xs-2.1:free
|
||
|
||
# AI_FALLBACK_MODEL: tried when the preferred model of a call fails
|
||
# (owner-approved paid model with near-zero cost).
|
||
AI_FALLBACK_MODEL=xiaomi/mimo-v2.6-flash
|
||
```
|
||
|
||
### 4. Banco de dados
|
||
|
||
```bash
|
||
php artisan migrate --seed
|
||
```
|
||
|
||
### 5. Servidor de desenvolvimento
|
||
|
||
```bash
|
||
php artisan serve --port=16980
|
||
```
|
||
|
||
A aplicação ficará disponível em `http://localhost:16980`.
|
||
|
||
### Ambiente Docker (dockercity10)
|
||
|
||
O container `dockercity10` já expõe a aplicação na porta do host **16980**
|
||
(container `:80`, Apache + PHP-FPM) e o MariaDB na porta **16981**. Após clonar
|
||
o projeto em `/app` (bind-mount), rode as migrations dentro do container:
|
||
|
||
```bash
|
||
docker exec -it dockercity10 bash -c "cd /app && php artisan migrate --seed"
|
||
```
|
||
|
||
---
|
||
|
||
## Contas de Teste (ambiente de desenvolvimento)
|
||
|
||
| Papel | E-mail | Senha |
|
||
|---|---|---|
|
||
| Administrador | `marcocosta@gmx.com` | `Mega886ax` |
|
||
| Usuário Comum | `user@sistema.net` | `User123` |
|
||
|
||
> ⚠️ Estas credenciais são exclusivas para desenvolvimento. Altere-as ou
|
||
> remova-as (`php artisan db:seed --class=UserSeeder` após ajustar) antes de
|
||
> qualquer implantação em produção.
|
||
|
||
---
|
||
|
||
## Testes
|
||
|
||
```bash
|
||
php artisan test # suíte completa (PHPUnit)
|
||
./vendor/bin/pint # formatação de código
|
||
```
|
||
|
||
Destaques da suíte (testes de recurso): autenticação/LGPD, ações da cidade
|
||
(exercício, posição com clamp até a orla da praia), social, mercado,
|
||
missões, **conversas de rua** (`PersonTalkTest` — histórico multidiário,
|
||
resolução de modelo, ausência de PII no payload) e **lazer na praia**
|
||
(`BeachLeisureTest` — geometria no payload, efeitos e zona).
|
||
|
||
---
|
||
|
||
## Estrutura de Endpoints da API
|
||
|
||
Base URL: `http://localhost:16980/api` (Bearer Sanctum)
|
||
|
||
### Públicos
|
||
|
||
| Método | Rota | Descrição |
|
||
|---|---|---|
|
||
| GET | `/auth/captcha` | Gera desafio matemático (necessário para login/registro) |
|
||
| POST | `/auth/login` | Autentica e emite token Sanctum |
|
||
| POST | `/auth/register` | Cria conta de usuário comum |
|
||
| GET | `/legal/documents` | Lista a versão atual dos documentos legais |
|
||
|
||
### Autenticados (Bearer token)
|
||
|
||
| Método | Rota | Descrição |
|
||
|---|---|---|
|
||
| GET | `/auth/me` | Perfil + status de consentimento |
|
||
| PATCH | `/auth/profile` | Atualiza nome, idioma ou senha |
|
||
| POST | `/auth/logout` | Revoga o token atual |
|
||
| GET | `/city/world` | Snapshot da cidade (lojas + agentes com coordenadas 3D) |
|
||
| GET/POST | `/stores` | Lista/cria lojas |
|
||
| GET/PUT/DELETE | `/stores/{id}` | Gerencia uma loja (dono ou admin) |
|
||
| GET | `/stores-world/seed` | Mundo de lojas (seed 3D) |
|
||
| GET | `/legal/status` | Status de aceite dos documentos |
|
||
| POST | `/legal/accept` | Registra o aceite (auditoria LGPD) |
|
||
| GET | `/legal/history` | Histórico de consentimentos do usuário |
|
||
|
||
### Somente Administrador
|
||
|
||
| Método | Rota | Descrição |
|
||
|---|---|---|
|
||
| CRUD | `/api/users` | Gestão de usuários |
|
||
| CRUD | `/api/agents` | Gestão de agentes de IA |
|
||
| GET | `/api/agents-meta/models` | Lista os modelos gratuitos permitidos |
|
||
| GET | `/api/agents-world/seed` | Mundo de agentes (seed 3D) |
|
||
| GET/POST | `/stores`, `/citizens`, `/cities`, `/world` | Admin web (aprovação de cidadãos, cidades, mundo) |
|
||
|
||
---
|
||
|
||
## Rotas Web da Cidade (sessão de navegador)
|
||
|
||
Todas exigem login + termos aceitos.
|
||
|
||
| Método | Rota | Descrição |
|
||
|---|---|---|
|
||
| GET | `/city` | Visualizador 3D (HTML + bundle Three.js) |
|
||
| GET | `/city/data` | Payload do mundo: layout, lojas, agentes, praças, prédios, `beach` |
|
||
| GET | `/city/presence` | Cidadãos online (posições, `is_me`) — throttle 60/min |
|
||
| GET/POST | `/talk/person` | Conversas de rua: histórico/contexto e envio (throttle 120 e 30/min) |
|
||
| POST | `/city/actions/position` | Sincroniza posição (clamp até a orla da praia) |
|
||
| POST | `/city/actions/{action}` | Ações: `eat`, `drink`, `sleep`, `wake`, `work`, `study`, `buy`, `sell`, `build`, `apply-job`, `exercise`, `stop-exercise`, `beach`, `ride`, `accident`… |
|
||
| POST/GET | `/city/chat/{agent}` (+`/stream`) | Conversa com NPC (envio + SSE de resposta) |
|
||
| POST/GET | `/city/messages/{character}` | Mensagens assíncronas entre personagens |
|
||
| POST | `/city/social/{verb}` · GET `/city/social/inbox` | Amizade, casamento, filho, inbox |
|
||
| GET | `/city/airport` | Sites dos aeroportos, torre (ATC) e clearance atual |
|
||
| POST | `/city/airport/dispatch` | Pede um clearance de decolagem à torre (cooldown 30 s) |
|
||
| GET/POST | `/city/market` (`/buy`, `/sell`) | Mercado |
|
||
| GET/PUT | `/city/customize` | Personalização do personagem |
|
||
|
||
---
|
||
|
||
## Modelos de IA Permitidos
|
||
|
||
Somente os modelos abaixo podem ser usados (validado no backend em
|
||
`config/ai.php`; os quatro últimos foram verificados ao vivo no catálogo do
|
||
OpenRouter). Exceção aprovada pelo responsável: `xiaomi/mimo-v2.6-flash`
|
||
(pago, custo mínimo ~1,4e-7/token, verificado ao vivo) — usado como
|
||
**fallback** de todas as cadeias de chamada:
|
||
|
||
```
|
||
poolside/laguna-m.1:free cohere/north-mini-code:free
|
||
deepseek/deepseek-v4-flash-0731:free qwen/qwen3.8-27b:free
|
||
nvidia/nemotron-3-ultra-550b-a55b:free nvidia/nemotron-3-super-120b-a12b:free
|
||
google/gemma-4-31b-it:free google/gemma-4-26b-a4b-it:free
|
||
openai/gpt-oss-20b:free nvidia/nemotron-nano-12b-v2-vl:free
|
||
nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free
|
||
nvidia/nemotron-3-nano-30b-a3b:free nvidia/nemotron-nano-9b-v2:free
|
||
poolside/laguna-xs-2.1:free inclusionai/ling-3.0-flash-vl:free
|
||
qwen/qwen3.7-flash:free
|
||
deepseek/deepseek-v4-flash-0731 nvidia/nemotron-3.5-lightning:free
|
||
liquid/lfm-2.5-2.6b:free openrouter/free
|
||
xiaomi/mimo-v2.6-flash (fallback — ver nota acima)
|
||
```
|
||
|
||
---
|
||
|
||
## Segurança e Conformidade (LGPD)
|
||
|
||
- **Senhas** armazenadas com bcrypt (cast `hashed`).
|
||
- **Tokens de API** (Sanctum) revogáveis por usuário.
|
||
- **RBAC**: papéis `admin` / `user` com middleware dedicado.
|
||
- **CAPTCHA matemático** nos formulários públicos (login/registro).
|
||
- **Consentimento auditável**: data, hora, versão do documento, IP e user-agent
|
||
gravados em `user_consents` (imutáveis).
|
||
- **Banner de cookies** com opção de aceitar/recusar cookies não essenciais.
|
||
- **Direitos do titular** (art. 18 LGPD): histórico de consentimentos disponível
|
||
ao usuário; contato para exercer direitos: `contato@syspanel.com.br`.
|
||
- **IA sem PII**: ao chamar o OpenRouter envia-se apenas contexto simulado da
|
||
cidade e o diálogo/identidade in-world — nunca e-mail, senha ou dados de
|
||
conta (regras em [`RULES.md`](RULES.md), seção 2).
|
||
|
||
---
|
||
|
||
## Licença
|
||
|
||
Software **Proprietário** — © 2026 Syspanel. Todos os direitos reservados.
|
||
Veja o arquivo [`LICENSE`](LICENSE) para os termos completos de uso.
|
||
|
||
Bibliotecas de terceiros utilizadas são compatíveis com uso comercial
|
||
(por exemplo: Laravel — MIT; Three.js — MIT). O crédito aos autores originais
|
||
está registrado em [`RULES.md`](RULES.md).
|
||
|
||
---
|
||
|
||
## Contato
|
||
|
||
- **E-mail**: [contato@syspanel.com.br](mailto:contato@syspanel.com.br)
|
||
- **WhatsApp**: (35) 99226-1684
|
||
- **Site**: [www.syspanel.com.br](https://syspanel.com.br)
|
||
- **Localização**: Santa Rita do Sapucaí — Minas Gerais — Brasil
|
||
|
||
---
|
||
|
||
© 2026 — Todos os direitos reservados — Powered by
|
||
[Syspanel](https://syspanel.com.br) — www.syspanel.com.br
|