21 - LOBO DO CERRADO
Technical Design Document (TDD)
Referência: Coyote Moon (IGT)
Arquitetura: Lottopar Core (Pool Finito)
Status: Design Finalizado
Data: 2026-04-03
1. VISÃO GERAL TÉCNICA
Este documento detalha a implementação técnica do jogo Lobo do Cerrado com foco em:
- Integração com VltCore.dll (Motor matemático)
- Configuração de reel strips para Stacked Wilds
- Pattern matching otimizado para 40 linhas
- Sincronismo visual com Pool Finito
Stack Tecnológico
- Linguagem Core: C# (.NET Framework 4.8)
- Motor Gráfico: Unity 2022 LTS
- Comunicação: SAS Protocol v6.0
- Banco de Dados: SQLite (logs locais) + PostgreSQL (servidor)
- Hardware Alvo: VLT Lottopar Padrão
2. REEL STRIPS E CONFIGURAÇÃO
Conceito de Reel Strips
Um reel strip é a sequência virtual de símbolos dentro de um rolo. Ao invés de um rolo físico com posições finitas, o strip é um array cíclico de símbolos que se repete.
Reel Strip Mapping - Lobo do Cerrado
Configuração Geral:
- Reels: 5 rolos
- Symbols per Reel: 48 posições (múltiplo de 12 para balanceamento)
- Total Combinações: 48^5 = 254,803,968 (Full Cycle)
Reel 1 (Esquerda - Sempre tem Wilds)
Posição | Símbolo | Frequência | Tipo
--------|------------|------------|-------
0-3 | Wild (K) | 8x | Stack (4)
4-7 | Lobo | 4x | Normal
8-11 | Onça | 4x | Normal
12-15 | Tamanduá | 4x | Normal
16-19 | Coruja | 4x | Normal
20-23 | A | 4x | Normal
24-27 | K | 4x | Normal
28-31 | Q | 4x | Normal
32-35 | J | 4x | Normal
36-39 | 10 | 4x | Normal
40-43 | 9 | 4x | Normal
44-47 | [Vazio] | 0x | Placeholder
Proporção de Wilds: 8/48 = 16.67%
Reel 2 (Scatter-Bearing + Wilds)
Posição | Símbolo | Frequência | Tipo
--------|------------|------------|-------
0-3 | Wild (K) | 8x | Stack (4)
4-6 | Scatter | 3x | Lua Vermelha
7-10 | Lobo | 4x | Normal
11-14 | Onça | 4x | Normal
15-18 | Tamanduá | 4x | Normal
19-22 | Coruja | 4x | Normal
23-26 | A | 3x | Normal
27-30 | K | 3x | Normal
31-34 | Q | 3x | Normal
35-38 | J | 3x | Normal
39-42 | 10 | 2x | Normal
43-47 | 9 | 3x | Normal
Proporção de Scatters: 3/48 = 6.25%
Proporção de Wilds: 8/48 = 16.67%
Reel 3 (Centro - Balanceado)
Mesmo padrão do Reel 2
Reel 4 (Scatter-Bearing)
Mesmo padrão do Reel 2
Reel 5 (Direita - Sempre tem Wilds)
Mesmo padrão do Reel 1
Probabilidades Derivadas
| Evento |
Probabilidade Teórica |
Frequência (1000 giros) |
| 3 Scatters (Free Spin) |
0.9% |
~9 giros |
| 4 Scatters (10 FS) |
0.08% |
~0.8 giros |
| 5 Scatters (15 FS) |
0.004% |
~0.04 giros |
| Win com 3+ Wilds |
2.1% |
~21 giros |
| Full Screen Wild (5x5) |
0.0002% |
~1 a cada 5000 giros |
3. ALGORITMO DE REEL MANIPULATION (POOL FINITO)
Conceito: Block Win Priority
Em vez de colocar símbolos soltos para atingir um prêmio, o motor prioriza "blocos" de Wilds para recriar a sensação visual do Coyote Moon.
Fase 1: Pool Bilhete
TicketWin: R$ 200,00 (prêmio calculado pelo Pool Finito)
Aposta: R$ 2,00
Multiplicador Necessário: 100x
Fase 2: OutcomeBuilder - Decisão de Estrutura
csharp
if (TicketWin > 400) {
// Use Full Screen ou múltiplos Stacks
strategy = "BLOCK_WIN";
target_stacks = 2;
} else if (TicketWin > 100) {
// Use um Stack grande + símbolos altos
strategy = "SINGLE_STACK";
target_stacks = 1;
} else {
// Use apenas símbolos baixos em múltiplas linhas
strategy = "SCATTER_SPREAD";
}
Fase 3: Posicionamento de Wilds (Block Placement)
```
Objetivo: Posicionar 2 Stacks de 4 Wilds em rolos diferentes
Reel 1: Índices 0-3 → Wild (Stack de 4)
Reel 3: Índices 8-11 → Wild (Stack de 4)
Reel 5: Índices 0-3 → Wild (Stack de 4)
Resultado Visual:
┌─────────────────────────┐
│ Wild │ Tamanduá │ Wild │ Linha 1: WILD + X + WILD = 50x Aposta
│ Wild │ Onça │ Wild │ Linha 2: WILD + X + WILD = 50x Aposta
│ Wild │ A │ Wild │ Linha 3: WILD + X + WILD = 50x Aposta
│ Wild │ K │ Wild │ Linha 4: WILD + X + WILD = 50x Aposta
└─────────────────────────┘
Total: 4 Linhas × 50x = 200x Aposta = R$ 400 (vs objetivo de R$ 200)
Compensação: Redesenhação automática com Stacks menores
```
Algoritmo de Limpeza de Resíduos
```csharp
decimal difference = CalculatedWin - TargetWin;
if (difference > 0.01m) {
// Excesso: Remover símbolos altos e substituir por baixos
foreach (var reel in reels) {
if (reel.totalWinValue > 0) {
// Substitua símbolos premium por comum
reel.ReplaceSymbol("Onça", "A");
RecalculateWins();
if (CalculatedWin <= TargetWin + 0.01m) break;
}
}
}
if (difference < -0.01m) {
// Déficit: Adicionar Stacks maiores
foreach (var reel in reels) {
if (reel.HasCapacity) {
reel.IncreaseStackSize();
RecalculateWins();
if (CalculatedWin >= TargetWin - 0.01m) break;
}
}
}
```
4. PATTERN MATCHING PARA 40 LINHAS
Definição das 40 Linhas de Pagamento
```
Convenção: Cada linha é um array [Row_R1, Row_R2, Row_R3, Row_R4, Row_R5]
Onde Row_Rn ∈ {0, 1, 2, 3} (índice vertical no rolo)
Linhas Horizontais (4 linhas):
1: [0, 0, 0, 0, 0] - Topo
2: [1, 1, 1, 1, 1]
3: [2, 2, 2, 2, 2]
4: [3, 3, 3, 3, 3] - Fundo
Linhas Diagonais Ascendentes (8 linhas):
5: [3, 2, 1, 0, 1]
6: [3, 2, 1, 0, 0]
7: [2, 1, 0, 1, 2]
8: [3, 3, 2, 1, 0]
... (mais 4 variações)
Linhas em "V" (8 linhas):
Padrão: Desce até o meio, depois sobe
21: [0, 1, 2, 1, 0]
22: [1, 2, 3, 2, 1]
... (6 mais)
Linhas "Zig-Zag" (12 linhas):
Padrão: Sobe e desce alternando
33: [0, 1, 0, 1, 0]
34: [1, 0, 1, 0, 1]
... (10 mais)
Total: 4 + 8 + 8 + 12 + 8 = 40 linhas
```
Algoritmo de Verificação (Win Detection)
```csharp
bool CheckLine(int[] lineConfig, Symbol[,] grid) {
Symbol firstSymbol = grid[lineConfig[0], 0];
if (firstSymbol.IsEmpty || firstSymbol.IsScatter) {
return false; // Scatters não contam em linhas
}
decimal lineWin = 0;
int matchCount = 0;
for (int reel = 0; reel < 5; reel++) {
Symbol current = grid[lineConfig[reel], reel];
// Substituição de Wild (igual ao primeiro)
if (current.IsWild) {
current = firstSymbol;
}
if (current == firstSymbol) {
matchCount++;
} else {
break; // Combinação quebrada (esquerda para direita)
}
}
// Pagamento ocorre apenas se 3+ símbolos combinam
if (matchCount >= 3) {
lineWin = paytable[firstSymbol][matchCount] * betPerLine;
}
return lineWin > 0;
}
```
Cálculo de Ganho Específico
```csharp
decimal CalculateLineWin(Symbol symbol, int matchCount, decimal betPerLine) {
decimal multiplier = paytable[symbol][matchCount];
decimal lineWin = multiplier * betPerLine;
return lineWin;
}
// Exemplo:
// Símbolo: Onça
// Correspondências: 4
// Multiplicador: 50x
// Aposta por linha: R$ 0,05
// Ganho: 50 * 0.05 = R$ 2,50
```
5. EXCLUSION MASK E LÓGICA DE CHEIOS
Problema: Overcombination Risk
Com 40 linhas e múltiplos Stacks, há risco de ganhos acidentais excedendo o TicketWin.
Solução: Trash Fill Inteligente
```csharp
public void FillEmptyPositions(Grid grid, string strategy) {
foreach (var position in grid.GetEmptyPositions()) {
// 1. Verificar Contexto Adjacente
Symbol left = grid.GetSymbol(position.reel - 1, position.row);
Symbol right = grid.GetSymbol(position.reel + 1, position.row);
// 2. Criar Blacklist
List<Symbol> forbidden = new List<Symbol>();
forbidden.Add(left);
forbidden.Add(right);
// 3. Se existe risco de linha acidental com Wild próximo
if (left.IsWild || right.IsWild) {
forbidden.Add(paytable.GetHighPaySymbols());
}
// 4. Selecionar símbolo permitido aleatoriamente
Symbol selected = GetRandomSymbol(forbidden);
grid.SetSymbol(position, selected);
// 5. Validar após cada inserção
if (CalculatedWin > TargetWin + 0.01m) {
grid.SetSymbol(position, Symbol.Empty); // Rollback
}
}
}
```
6. MANUSEIO DE FREE SPINS
Estrutura do Bônus no Pool
```csharp
public class FreeSpinSession {
public int InitialSpins { get; set; } = 5; // 3 Scatters
public int CurrentSpins { get; set; }
public decimal AccumulatedWin { get; set; } = 0;
public bool IsActive { get; set; } = true;
// Modificações no Bônus
public decimal StackMultiplier { get; set; } = 2.0m; // Stacks duplicados
public int RetriggerLimit { get; set; } = 5; // Máximo 5 retríggers
}
```
Fluxo de Requisição ao Pool
```
1. Pool envia TicketWin para Free Spin 1: R$ 50,00
2. OutcomeBuilder constrói Grid 1, calcula vitória: R$ 50,00 ✓
3. Apresentação: Grid 1 gira, resultado de R$ 50,00 exibido
4. Saldo atualizado: Saldo anterior + R$ 50,00
5. Verificação: Scatters presentes?
- Sim → RetriggerCount++, IniciarNovaRequisição
- Não → Próximo Spin
- Spin 2 com Retrigger:
- Pool envia TicketWin para Free Spin 2: R$ 75,00
- OutcomeBuilder reconstrói, calcula: R$ 75,00 ✓
- Apresentação: Pop-up "Ganhou +5 Giros!"
... (até fim de giros ou retriggers atingirem limite)
- Fim do Bônus:
- Estado muda para BASEGAME
- Saldo total exibido com resumo
```
JSON Payload Exemplo para FS
json
{
"round_id": "LOBO_CERRADO_999",
"game_id": 21,
"round_type": "FREE_SPIN",
"spin_number": 3,
"total_ticket_win": 12500,
"aposta_total": 200,
"aposta_por_linha": 5,
"num_linhas": 40,
"bonus_script": {
"is_retriggered": true,
"new_spins": 5,
"spins": [
{
"id": 1,
"reel_stops": [0, 4, 8, 12, 0],
"grid": [
["Wild", "Tamanduá", "Wild", "Onça", "Wild"],
["Wild", "A", "Wild", "Coruja", "Wild"],
["Wild", "K", "Wild", "A", "Wild"],
["Wild", "Q", "Wild", "J", "Wild"]
],
"winning_lines": [1, 2, 3, 4, 5, 8, 12, 15],
"individual_wins": {
"line_1": 12500,
"line_2": 0,
"line_3": 0,
...
},
"total_win": 12500,
"presentation": "FULL_SCREEN"
}
]
},
"hash": "abc123xyz789",
"timestamp": "2026-04-03T14:23:45Z"
}
7. INTEGRAÇÃO COM VltCore.dll
Diagrama de Fluxo de Dados
┌─────────────────────────────────────────────────────────────┐
│ 1. UNITY (ReelController.cs) │
│ - Usuário clica "GIRAR" │
│ - Captura Aposta Atual │
│ - Envia requisição ao Core │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────────┐
│ 2. VltCore.dll (C#) │
│ - Pool Finito sorteia TicketWin │
│ - OutcomeBuilder constrói Grid │
│ - Realiza validações e cálculos │
│ - Retorna JSON com Grid + Paradas │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────────┐
│ 3. UNITY (ReelController.cs) │
│ - Recebe JSON com reel_stops: [0, 4, 8, 12, 0] │
│ - Animação 1: Rolos giram (3-4 segundos) │
│ - Animação 2: Freio Ease-Out em cada rolo │
│ - Parada Visual: Índices do JSON devem coincidir │
│ - Validação: CalculateWinsFromGrid() == TicketWin │
└────────────────────┬────────────────────────────────────────┘
│
┌────────────────────▼────────────────────────────────────────┐
│ 4. UNITY (UIController.cs) │
│ - Desenha linhas de vitória │
│ - Anima ganhos (Pop-ups incrementados) │
│ - Sons de vitória toquem em sincronia │
│ - Saldo atualizado │
└─────────────────────────────────────────────────────────────┘
Interface de Comunicação C
```csharp
// VltCore.dll Export (Dll C++)
[DllImport("VltCore.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GenerateSpin(
double totalBet,
IntPtr outputBuffer,
int bufferSize
);
// Implementação na Unity
public class SpinRequestHandler {
public string RequestSpin(decimal totalBet) {
byte[] buffer = new byte[4096];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try {
int result = GenerateSpin((double)totalBet, handle.AddrOfPinnedObject(), buffer.Length);
if (result != 0) throw new Exception($"Core Error: {result}");
string jsonResponse = Encoding.UTF8.GetString(buffer);
return jsonResponse;
} finally {
handle.Free();
}
}
}
```
8. VALIDAÇÃO E SINCRONISMO CRÍTICO
Verificação Pré-Apresentação (Server-Side)
```csharp
public bool ValidateOutcome(Outcome outcome) {
// 1. Hash Integrity
string computedHash = SHA256(outcome.rawData);
if (computedHash != outcome.hash) {
LogCriticalError("Hash mismatch");
return false;
}
// 2. RTP Boundary
decimal rtp = outcome.totalWin / outcome.totalBet;
if (rtp < 0.88m || rtp > 2.5m) {
LogCriticalError($"RTP out of bounds: {rtp}");
return false;
}
// 3. Symbol Distribution
var symbolCounts = outcome.grid.CountSymbols();
if (symbolCounts["Wild"] > 20) { // Máximo 20 Wilds na tela
LogCriticalError("Excessive Wild count");
return false;
}
// 4. Win Recalculation
decimal recalculated = RecalculateWinFromGrid(outcome.grid);
if (Math.Abs(recalculated - outcome.totalWin) > 0.01m) {
LogCriticalError($"Win mismatch: calculated {recalculated}, got {outcome.totalWin}");
return false;
}
return true;
}
```
Sincronismo Visual (Client-Side)
```csharp
public class ReelAnimationController : MonoBehaviour {
public IEnumerator SpinAndStop(int[] stopIndices, float spinDuration) {
// 1. Inicia rotação de todos os rolos
for (int reel = 0; reel < 5; reel++) {
StartCoroutine(RotateReel(reel, spinDuration));
}
// 2. Espera duração total
yield return new WaitForSeconds(spinDuration);
// 3. Freia cada reel no índice exato (Ease-Out)
for (int reel = 0; reel < 5; reel++) {
StopReelAtIndex(reel, stopIndices[reel], 0.5f);
}
// 4. Validação crítica
yield return new WaitForSeconds(0.6f);
Symbol[,] visibleGrid = ReadVisibleGrid();
decimal visibleWin = CalculateWinFromGrid(visibleGrid);
decimal expectedWin = jsonPayload.total_win;
if (Math.Abs(visibleWin - expectedWin) > 0.01m) {
Debug.LogError($"SYNC ERROR: Visual={visibleWin}, Expected={expectedWin}");
// Reportar ao servidor e congelar jogo até validação manual
}
}
private void StopReelAtIndex(int reel, int targetIndex, float brakeDuration) {
float startRotation = reels[reel].rotation.z;
float targetRotation = targetIndex * 51.43f; // 360 / 7 símbolos = 51.43°
DOTween.To(() => startRotation,
x => reels[reel].rotation = new Vector3(0, 0, x),
targetRotation,
brakeDuration)
.SetEase(Ease.OutQuad); // Frenagem suave
}
}
```
9. ESPECIFICAÇÕES DE HARDWARE
Requisitos Mínimos VLT Lottopar
- Processador: Intel Core i5 (6ª geração) ou equivalente
- Memória RAM: 4GB DDR4
- Armazenamento: SSD 256GB
- Display: 24" @ 60Hz, 1920x1080, Full HD
- GPU: NVIDIA GeForce GTX 750 ou equivalente
- Áudio: Som 5.1 surround (obrigatório para ambientes)
Especificações de Rede
- Protocolo: Ethernet Gigabit (obrigatório)
- Latência Máxima: 100ms (TicketWin request-response)
- Fallback: Modo Offline com cache local por até 4 horas
- Sincronização: NTP (Network Time Protocol) obrigatória
10. PROTOCOLO SAS/G2S
Configuração SAS v6.01
```
Machine Address: [Definido em instalação]
Game Number: 21
Game Version: 1.0
Configuration Version: 1.0
Comandos Críticos:
- 01: Get Machine Status
- 13: Get Ticket
- 14: Validation Number (para cada prêmio)
- 30: Get Game Meters
- 40: Game Lockup Request (travamento por erro)
```
Fluxo de Auditoria Completo
```
1. Jogador clica GIRAR (Saldo = R$ 100, Aposta = R$ 2)
→ SAS Log Entry: "SPIN_INITIATED,balance=100.00,bet=2.00"
-
VltCore retorna TicketWin = R$ 50
→ SAS Log Entry: "TICKET_GENERATED,id=ABC123,amount=50.00"
-
Apresentação finaliza, saldo atualizado (R$ 148)
→ SAS Log Entry: "TICKET_REDEEMED,balance=148.00"
-
End of Session
→ SAS Log Entry: "SESSION_END,total_spins=50,total_net=48.00"
```
11. SISTEMA DE CACHE E FALLBACK
Cache Local (Offline Mode)
```csharp
public class OfflineCacheManager {
private Queue<CachedOutcome> outcomeBuffer = new();
private const int MAX_CACHE_SIZE = 240; // 4 horas @ 1 spin/min
public bool TryGetCachedOutcome(decimal bet, out string json) {
if (HasNetworkConnection()) {
outcomeBuffer.Clear();
return false;
}
if (outcomeBuffer.Count > 0) {
var cached = outcomeBuffer.Dequeue();
json = cached.payload;
return true;
}
return false;
}
public void CacheOutcome(string json) {
outcomeBuffer.Enqueue(new CachedOutcome {
payload = json,
timestamp = DateTime.UtcNow
});
if (outcomeBuffer.Count > MAX_CACHE_SIZE) {
outcomeBuffer.Dequeue(); // FIFO overflow
}
}
}
```
12. TESTES E VALIDAÇÃO
Test Case 1: Full Screen Wild
```
Input:
- Aposta: R$ 2,00
- Esperado: Full Screen de 20 Wilds (5x4)
Processo:
1. OutcomeBuilder constrói Grid com Stacks em todos os 5 rolos
2. CalculateWins() deve retornar 1000x * R$ 0,05 * 40 linhas
3. Total esperado: R$ 1.000,00 (limite máximo ajustado)
Verificação Visual:
- Todas as 20 posições mostram Wild
- Todos os 40 linhas se iluminam
- Som "Uivo Épico" toca
- Saldo atualizado de forma explícita
```
Test Case 2: Scatter Retrigger
```
Input:
- Free Spins em andamento, 1 giro restante
- Esperado: 3 Scatters na tela (Rolos 2, 3, 4)
Processo:
1. OutcomeBuilder posiciona 3 Scatters (índices aleatórios nos rolos 2,3,4)
2. Pool detecta e gera novo TicketWin para +5 Giros
3. RetriggerCount incrementa
Verificação:
- Pop-up "Ganhou +5 Giros Grátis!"
- Giros atualizados de 1 para 6 no visor
- Fluxo continua sem quebra
- RetriggerCount respeitado (máximo 5)
```
Test Case 3: Win Validation Stress
```
Cenário: 1.000.000 giros automáticos
Métrica 1: RTP Adherence
- Esperado: 94% ± 2%
- Medido: (Total Wins / Total Bets)
- Aceitável: 92% a 96%
Métrica 2: Symbol Distribution
- Wilds: ~16,67% da aparição
- Scatters: ~6,25% (apenas R2, R3, R4)
- Variância aceitável: ±2%
Métrica 3: Performance
- Tempo médio por spin: 4,2 segundos
- FPS: Mínimo 50 (máximo 60)
- Latência rede: < 100ms em 95% das requisições
```
13. REGISTRO E DIAGNÓSTICO
Log Entry Estrutura
[TIMESTAMP] [LEVEL] [MODULE] [MESSAGE]
[2026-04-03T14:23:45.123Z] [INFO] [ReelController] Spin started, bet=2.00
[2026-04-03T14:23:49.456Z] [DEBUG] [OutcomeBuilder] Generated outcome, win=50.00
[2026-04-03T14:23:50.789Z] [INFO] [UIController] Spin animation finished
[2026-04-03T14:23:51.012Z] [INFO] [SASLogger] Outcome logged to SAS
Erro Crítico Handling
csharp
try {
var outcome = coreInterface.RequestSpin(totalBet);
ValidateOutcome(outcome);
PresentOutcome(outcome);
} catch (NetworkException ex) {
LogCriticalError($"Network failure: {ex.Message}");
// Congelar jogo, mostrar "Manutenção em andamento"
// Notificar operador via SAS
GameState.Lockup();
} catch (ValidationException ex) {
LogCriticalError($"Validation failed: {ex.Message}");
// Reembolsar aposta, registrar incidente
player.Balance += totalBet;
IncidentLog.Record("VALIDATION_FAILURE");
}
14. CONFORMIDADE E CERTIFICAÇÃO
Documentação Requerida para Lottopar
- ✓ Mapa de Reel Strips com probabilidades
- ✓ Tabela de Pagamentos completa
- ✓ Algoritmo de Pool Finito (descrição matemática)
- ✓ Relatório RTP (teórico e observado)
- ✓ Teste de Aleatoriedade (StatTest)
- ✓ Especificação de Hardware
- ✓ Procedimento de Auditoria (SAS Logs)
Checklist Final
- [ ] Full Cycle test executado (254.8M combinações)
- [ ] RTP validado em 1M spins (92-96% aceitável)
- [ ] Sync test realizado (visual vs Core)
- [ ] Stress test de 48h sem interrupção
- [ ] Teste de Fallback/Offline
- [ ] Auditoria de segurança de dados
- [ ] Certificação de terceira parte (se requerido)
Documento Finalizado: 03/04/2026
Versão: 1.0 - Draft Final
Certificação: Pendente Lottopar