feat(city): implement marketplace, missions, customization and admin dashboard

- Add database migrations for market_listings, missions and character cosmetics
- Create controllers for marketplace, missions, presence and character customization
- Implement admin world dashboard with live platform metrics
- Add feature tests for world functionality
- Include phase 2 design documents and gap analysis
This commit is contained in:
2026-10-17 23:21:23 -03:00
parent a620f83dd8
commit c299a4ae21
100 changed files with 254 additions and 0 deletions
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
Regular → Executable
View File
View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 118 KiB

View File

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

View File

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 218 KiB

View File

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

View File

Before

Width:  |  Height:  |  Size: 282 KiB

After

Width:  |  Height:  |  Size: 282 KiB

View File

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 120 KiB

View File

Before

Width:  |  Height:  |  Size: 243 KiB

After

Width:  |  Height:  |  Size: 243 KiB

View File

Before

Width:  |  Height:  |  Size: 223 KiB

After

Width:  |  Height:  |  Size: 223 KiB

View File

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 277 KiB

View File

Before

Width:  |  Height:  |  Size: 203 KiB

After

Width:  |  Height:  |  Size: 203 KiB

View File

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 222 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Before

Width:  |  Height:  |  Size: 235 KiB

After

Width:  |  Height:  |  Size: 235 KiB

View File

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

View File

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

View File

Before

Width:  |  Height:  |  Size: 261 KiB

After

Width:  |  Height:  |  Size: 261 KiB

View File

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 155 KiB

After

Width:  |  Height:  |  Size: 155 KiB

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

View File

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

View File

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

Before

Width:  |  Height:  |  Size: 110 KiB

After

Width:  |  Height:  |  Size: 110 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

View File

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

+1
View File
@@ -0,0 +1 @@
56096
+1
View File
@@ -0,0 +1 @@
23ba6490cfcbd10fdd1ca204a8a024301fb00ee50214d88fa21f31a360d74c23
@@ -0,0 +1,60 @@
<div style="text-align:center;margin-bottom:1.5rem">
<h2>🏙️ City10 — Visão da Cidade 3D</h2>
<p class="subtitle">Fase 2: Transformar o grid atual em uma cidade viva com ruas, árvores, prédios variados e atmosfera</p>
</div>
<div class="section">
<h3>O que já existe hoje</h3>
<ul>
<li>Chão verde com grid</li>
<li>Caixas coloridas representando lojas</li>
<li>Cápsulas amarelas para agentes</li>
<li>Órbita de câmera, zoom, clique para selecionar</li>
</ul>
</div>
<div class="section">
<h3>O que vamos adicionar</h3>
<ul>
<li><strong>Ruas asfaltadas</strong> com calçadas e marcações de trânsito</li>
<li><strong>Quadras/blocos</strong> com prédios de fachada variada</li>
<li><strong>Árvores, postes, bancos, floreiras</strong> — objetos de cena</li>
<li><strong>Prédios residenciais/comerciais</strong> de preenchimento entre as lojas</li>
<li><strong>Ciclo dia/noite</strong> com iluminação dinâmica</li>
<li><strong>Mini-mapa</strong> no canto da tela</li>
</ul>
</div>
<hr style="border-color:var(--border);margin:1.5rem 0">
<h2 style="text-align:center">Layout da Cidade</h2>
<p class="subtitle">Três abordagens para organizar ruas e quarteirões</p>
<div class="options" style="margin-top:1rem">
<div class="option" data-choice="grid" onclick="toggleSelect(this)">
<div class="letter">A</div>
<div class="content">
<h3>Grid Clássico</h3>
<p>Ruas formando quadras regulares. Lojas em posições fixas nas esquinas e frentes. Fácil de navegar, previsível, urbano.</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.5rem">✅ Mais simples de gerar, orientação clara</p>
</div>
</div>
<div class="option" data-choice="organic" onclick="toggleSelect(this)">
<div class="letter">B</div>
<div class="content">
<h3>Orgânico</h3>
<p>Ruas curvas que conectam as lojas existentes. Praça central como ponto focal. Visual mais natural e europeu.</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.5rem">✅ Mais bonito, ❌ Mais complexo de gerar</p>
</div>
</div>
<div class="option" data-choice="hybrid" onclick="toggleSelect(this)">
<div class="letter">C</div>
<div class="content">
<h3>Misto (Recomendado)</h3>
<p>Grid base com quarteirões de tamanhos variados, praça central, algumas vias diagonais. Combina organização com personalidade.</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.5rem">✅ 👍 Melhor equilíbrio entre beleza e implementação</p>
</div>
</div>
</div>
<p style="margin-top:1.5rem;text-align:center;color:var(--muted)">Clique na sua opção preferida e me diga no terminal se quer ajustar algo.</p>
@@ -0,0 +1,77 @@
<div style="text-align:center;margin-bottom:1.5rem">
<h2>📐 Design — Cidade 3D Fase 2</h2>
<p class="subtitle">Arquitetura, componentes, fluxo de dados e testes — revise seção por seção</p>
</div>
<div class="section">
<span class="label">1. Arquitetura</span>
<p>A cena Three.js passa de <strong>órbita isométrica</strong> para <strong>exploração a pé</strong>. O backend continua o mesmo (Sanctum + <code>/city/data</code>), mas o payload ganha: ruas, prédios gerados, e um streaming de mensagens de chat via <strong>SSE</strong> (Server-Sent Events) por agente — sem dependência nova.</p>
<pre style="background:#0b1220;border:1px solid var(--border);border-radius:.5rem;padding:.75rem;overflow:auto;font-size:.8rem">Frontend (Three.js)
├── CameraController — WASD + mouse, colisão AABB
├── CityGenerator — ruas, quadras, prédios de preenchimento
├── CharacterFactory — carrega modelos GLTF por tipo de agente
├── HudOverlay — painel POSIÇÃO + minimapa
└── ChatPanel — fluxo SSE /city/chat/{agent}
Backend (Laravel)
├── CityViewerController::data() — stores + agents + city layout
├── ChatController — SSE endpoint por agente
└── AgentThinkJob (queue) — chama OpenRouter, persiste last_decision</pre>
</div>
<div class="section">
<span class="label">2. Componentes</span>
<table style="width:100%;border-collapse:collapse;font-size:.9rem">
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.5rem"><strong>CameraController</strong></td><td>Pointer-lock, WASD, gravidade simples, raycast contra colisores AABB de prédios</td></tr>
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.5rem"><strong>CityGenerator</strong></td><td>Rua central + quadras laterais a partir das posições das lojas; prédios de preenchimento procedural (seed determinística)</td></tr>
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.5rem"><strong>StreetMesh</strong></td><td>Asfalto (canvas texture com faixas), calçada de ladrilho, postes a cada N metros</td></tr>
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.5rem"><strong>CharacterFactory</strong></td><td>GLTF por <code>agent.type</code>; fallback: cápsula texturizada. Animação Idle/Walk/Run</td></tr>
<tr style="border-bottom:1px solid var(--border)"><td style="padding:.5rem"><strong>HudOverlay</strong></td><td>Painel POSIÇÃO (x,y,z, triângulos) + minimapa 2D no canto</td></tr>
<tr><td style="padding:.5rem"><strong>ChatPanel</strong></td><td>Abre ao clicar no NPC; mostra nome, status (pensando/respondendo), mensagens; envia via POST e recebe via SSE</td></tr>
</table>
</div>
<div class="section">
<span class="label">3. Fluxo de dados</span>
<ol>
<li>Viewport carrega → <code>GET /city/data</code> → lojas + agentes + layout de ruas</li>
<li><code>CityGenerator</code> monta ruas e prédios; prédios de loja ganham fachada custom</li>
<li>Usuário anda (WASD) → colisão por AABB → câmera segue</li>
<li>Clica em NPC → <code>ChatPanel</code> abre → <code>GET /city/chat/{agent}/stream</code> (SSE)</li>
<li>Envia mensagem → <code>POST /city/chat/{agent}</code> → job enfileirado → OpenRouter → persiste <code>last_decision</code> → SSE emite resposta</li>
<li>HUD recalcula posição a cada frame</li>
</ol>
</div>
<div class="section">
<span class="label">4. Tratamento de erros</span>
<ul>
<li>SSE cai → reconexão automática com backoff, indicador "reconectando"</li>
<li>OpenRouter falha → <code>error_count++</code>, backoff exponencial, status vira <code>error</code> no painel</li>
<li>GLTF ausente → fallback silencioso para cápsula (nunca quebra a cena)</li>
<li>Colisão detecta NaN → reposiciona no spawn</li>
</ul>
</div>
<div class="section">
<span class="label">5. Testes</span>
<ul>
<li><strong>PHPUnit</strong>: <code>CityViewerController::data()</code> retorna layout; <code>ChatController</code> valida input e rate-limit; job persiste <code>last_decision</code></li>
<li><strong>JS</strong>: <code>CameraController</code> (colisão, WASD), <code>CityGenerator</code> (determinístico com seed), reconexão SSE</li>
<li><strong>Manual</strong>: navegar, colidir, conversar com NPC, verificar HUD</li>
</ul>
</div>
<hr style="border-color:var(--border);margin:1.5rem 0">
<p style="text-align:center"><strong>Esse design está correto? Aprovo para escrever a especificação formal.</strong></p>
<div class="options">
<div class="option" data-choice="approve" onclick="toggleSelect(this)">
<div class="letter">✅</div>
<div class="content"><h3>Aprovar design</h3><p>Escrever spec → revisão → plano de implementação</p></div>
</div>
<div class="option" data-choice="revise" onclick="toggleSelect(this)">
<div class="letter">✏️</div>
<div class="content"><h3>Revisar</h3><p>Quero mudar algo antes de seguir</p></div>
</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

@@ -0,0 +1,85 @@
<div style="text-align:center;margin-bottom:1.5rem">
<h2>✅ Entendi o Visual Alvo</h2>
<p class="subtitle">Baseado nas 4 telas de exemplo — a diferença para o que existe hoje é enorme</p>
</div>
<div class="split">
<div class="mockup">
<div class="mockup-header">Hoje (existe)</div>
<div class="mockup-body" style="text-align:center;padding:1.5rem">
<div style="background:#14532d;border:1px solid #334155;border-radius:.5rem;height:100px;display:flex;align-items:center;justify-content:center;gap:8px">
<div style="width:20px;height:20px;background:#0ea5e9;border-radius:3px"></div>
<div style="width:25px;height:30px;background:#8b5cf6;border-radius:3px"></div>
<div style="width:15px;height:15px;background:#f59e0b;border-radius:999px"></div>
</div>
<p style="margin-top:.75rem;font-size:.85rem">Vista isométrica · caixas coloridas · cápsulas amarelas</p>
</div>
</div>
<div class="mockup">
<div class="mockup-header">Alvo (imagens de exemplo)</div>
<div class="mockup-body" style="text-align:center;padding:1.5rem">
<div style="background:linear-gradient(#87ceeb 0%, #87ceeb 35%, #333 36%, #333 100%);border:1px solid #555;border-radius:.5rem;height:100px;position:relative;overflow:hidden">
<div style="position:absolute;left:0;top:0;bottom:0;width:35%;background:linear-gradient(#d4a843,#8b6914);border-right:3px solid #222"></div>
<div style="position:absolute;right:0;top:0;bottom:0;width:35%;background:linear-gradient(#c49a3a,#7a5a12);border-left:3px solid #222"></div>
<div style="position:absolute;left:50%;bottom:0;transform:translateX(-50%);width:10px;height:100%;background:#222"></div>
<div style="position:absolute;left:30%;bottom:40%;width:15px;height:15px;background:#555;border-radius:50%"></div>
<div style="position:absolute;right:30%;bottom:40%;width:15px;height:15px;background:#555;border-radius:50%"></div>
</div>
<p style="margin-top:.75rem;font-size:.85rem">1ª pessoa · asfalto + faixas · prédios fachadas · personagens 3D</p>
</div>
</div>
</div>
<hr style="border-color:var(--border);margin:2rem 0">
<h3 style="text-align:center">Gap — o que precisa ser construído</h3>
<div class="options" style="margin-top:1rem" data-multiselect>
<div class="option" data-choice="fps-camera" onclick="toggleSelect(this)">
<div class="letter">1</div>
<div class="content">
<h3>Câmera 1ª pessoa / 3ª pessoa</h3>
<p>Substituir a órbita por WASD + mouse, com colisão contra prédios e pedestres</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.4rem">Bloqueante: sem isso, não tem a experiência das imagens</p>
</div>
</div>
<div class="option" data-choice="street" onclick="toggleSelect(this)">
<div class="letter">2</div>
<div class="content">
<h3>Rua realista</h3>
<p>Asfalto escuro, faixas brancas, calçada de ladrilho, postes, semáforos</p>
</div>
</div>
<div class="option" data-choice="buildings" onclick="toggleSelect(this)">
<div class="letter">3</div>
<div class="content">
<h3>Prédios com fachadas</h3>
<p>Janelas, portas, estrutura escura, fachadas amarelo/mostarda e marrom — não caixas planas</p>
</div>
</div>
<div class="option" data-choice="characters" onclick="toggleSelect(this)">
<div class="letter">4</div>
<div class="content">
<h3>Personagens 3D estilo anime</h3>
<p>Modelos detalhados com cabelo, roupas, animação de andar — substituir cápsulas amarelas</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.4rem">Requer modelo 3D (GLTF) ou compra/asset pack</p>
</div>
</div>
<div class="option" data-choice="hud" onclick="toggleSelect(this)">
<div class="letter">5</div>
<div class="content">
<h3>HUD — Painel POSIÇÃO</h3>
<p>Coordenadas x,y,z, no chão, triângulos de colisão no canto inferior esquerdo</p>
</div>
</div>
<div class="option" data-choice="chat" onclick="toggleSelect(this)">
<div class="letter">6</div>
<div class="content">
<h3>Chat com NPCs (OpenRouter)</h3>
<p>Painel de diálogo com nome, status (pensando/respondendo), conversa em tempo real</p>
<p style="color:var(--muted);font-size:.85rem;margin-top:.4rem">Integração direta com agentes existentes no banco</p>
</div>
</div>
</div>
<p style="margin-top:1.5rem;text-align:center;color:var(--muted)">Marque os itens que confirma estar no escopo da Fase 2 e me diga se algo está faltando ou sobrando.</p>
@@ -0,0 +1,29 @@
<div style="text-align:center;margin-bottom:1rem">
<h2>📷 Imagens de Referência</h2>
<p class="subtitle">As quatro telas de exemplo na raiz do projeto — me ajude a entender o que cada uma mostra</p>
</div>
<div class="cards">
<div class="card" data-choice="1" onclick="toggleSelect(this)">
<h3>Tela 1</h3>
<img src="/files/exemplo1.png" style="max-width:100%;border-radius:.6rem;border:1px solid var(--border)">
<p style="color:var(--muted);font-size:.85rem">Descreva: o que esta tela mostra?</p>
</div>
<div class="card" data-choice="2" onclick="toggleSelect(this)">
<h3>Tela 2</h3>
<img src="/files/exemplo2.png" style="max-width:100%;border-radius:.6rem;border:1px solid var(--border)">
<p style="color:var(--muted);font-size:.85rem">Descreva: o que esta tela mostra?</p>
</div>
<div class="card" data-choice="3" onclick="toggleSelect(this)">
<h3>Tela 3</h3>
<img src="/files/exemplo3.png" style="max-width:100%;border-radius:.6rem;border:1px solid var(--border)">
<p style="color:var(--muted);font-size:.85rem">Descreva: o que esta tela mostra?</p>
</div>
<div class="card" data-choice="4" onclick="toggleSelect(this)">
<h3>Tela 4</h3>
<img src="/files/exemplo4.png" style="max-width:100%;border-radius:.6rem;border:1px solid var(--border)">
<p style="color:var(--muted);font-size:.85rem">Descreva: o que esta tela mostra?</p>
</div>
</div>
<p style="margin-top:1.5rem;text-align:center;color:var(--muted);font-weight:600">Por favor, no terminal, me diga o que cada imagem mostra para eu entender o visual que você quer atingir.</p>
@@ -0,0 +1 @@
{"reason":"stop-server.sh","timestamp":1792266689}
Regular → Executable
View File
Regular → Executable
View File
BIN
View File
Binary file not shown.
View File
View File
View File
View File
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
Regular → Executable
View File
View File
View File
View File
View File
View File
View File