Sistema FLIP - Roleta Dupla com Itens Raros
O sistema FLIP e uma mecanica especial de abertura de caixas que ativa uma segunda roleta contendo exclusivamente itens do tier raro. Cada caixa tem um flipThreshold configuravel (Case.flipThreshold, nullable). Quando o roll normal do jogador e maior ou igual a esse threshold (roll >= flipThreshold), o FLIP e ativado automaticamente. O jogador passa por duas roletas consecutivas: a primeira para na KNIFE (card especial), seguida de uma animacao, e a segunda roleta gira com os itens raros da caixa.
Indice
- Visao Geral do Fluxo
- Determinacao do FLIP Threshold
- Verificacao de Ativacao do FLIP
- Itens Elegiveis para FLIP
- Itens Normais (Nao-FLIP)
- Selecao do Item Ganho
- Preparacao de Itens para Roleta
- Integracao com OpenCaseUseCase
- Provably Fair - Seeds e Nonce
- Modelo de Dados
- Response da API
- Live Drop Delay
- Timeline do Frontend
- Casos de Borda
- Debugging
- Arquivos Relacionados
Visao Geral do Fluxo
1. Roll normal e calculado -> provablyFairService.calculateRoll()
2. Flip threshold e lido da caixa -> caseData.flipThreshold (Case.flipThreshold)
3. Verifica se roll >= threshold -> flipService.shouldTriggerFlip(roll, threshold)
4. Se FLIP ativado:
a. Separa itens normais -> hashRangeStart < threshold
b. Separa itens raros (FLIP) -> hashRangeStart >= threshold
c. Prepara itens para roleta -> prepareFlipItemsForRoulette()
d. Seleciona item pelo roll -> flipService.selectFlipWinner(flipItems, roll)
e. Item ganho e do tier raro -> (roll >= threshold = range de item raro)
5. Dados FLIP sao retornados na response
6. Frontend exibe duas roletas consecutivas (25.5s total)
7. Live drop emitido com delay de 30sDeterminacao do FLIP Threshold
O threshold do FLIP e configurado por caixa na coluna Case.flipThreshold (Int, nullable). Se for null, a caixa nao tem FLIP.
Implementacao
// src/application/services/flip.service.ts
async getFlipThreshold(caseId: bigint): Promise<number | null> {
const caseData = await this.caseRepository.findById(caseId);
return caseData?.flipThreshold ?? null;
}Como Funciona
- Le o campo
flipThresholdda caixa (Case.flipThreshold) - Se for
null, retornanull(FLIP desativado para essa caixa) - Caso contrario, esse valor e o threshold: qualquer roll
>= thresholdativa o FLIP - Os itens do tier raro sao os que tem
hashRangeStart >= threshold; os normais temhashRangeStart < threshold
No fluxo de abertura (
OpenCaseUseCase), o threshold e lido diretamente decaseData.flipThreshold(a caixa ja vem carregada viafindByIdCached), sem uma chamada extra aoFlipService.
Exemplo Pratico
Supondo uma caixa com flipThreshold = 9.280.000 e estes hash ranges:
| Item | hashRangeStart | hashRangeEnd | Tier |
|---|---|---|---|
| MAG-7 Wildwood | 0 | 6.681.999 | Normal |
| P250 Sand Dune | 6.682.000 | 8.279.999 | Normal |
| AK-47 Redline | 8.280.000 | 9.279.999 | Normal |
| AWP Asiimov | 9.280.000 | 9.679.999 | FLIP |
| M4A4 Howl | 9.680.000 | 9.879.999 | FLIP |
| Butterfly Emerald | 9.880.000 | 9.999.999 | FLIP |
Como flipThreshold = 9.280.000, qualquer roll >= 9.280.000 ativa o FLIP e cai no range de um item do tier FLIP (hashRangeStart >= threshold). A probabilidade do FLIP e a soma das probabilidades desses itens:
- AWP Asiimov: 4.00%
- M4A4 Howl: 2.00%
- Butterfly Emerald: 1.20%
- Probabilidade total do FLIP: 7.20%
Diferenca do Modelo Antigo
O modelo antigo usava um threshold fixo (roll < 5000), ou seja, 0.05% de chance fixa. O modelo atual usa o flipThreshold configurado por caixa, tornando a probabilidade ajustavel por caixa (definida por quais itens ficam acima do threshold). O campo CaseItem.isRare ainda existe no schema, mas NAO e usado pelo FlipService — a separacao FLIP/normal e feita 100% por hashRangeStart vs flipThreshold.
Verificacao de Ativacao do FLIP
// src/application/services/flip.service.ts
shouldTriggerFlip(roll: number, flipThreshold: number | null): boolean {
if (flipThreshold === null) {
return false;
}
return roll >= flipThreshold;
}Regras:
- Se
flipThreshold === null(nenhum item raro na caixa), FLIP nunca ativa - Se
roll >= flipThreshold, FLIP ativa - O roll ja determina QUAL item raro foi ganho (via hash ranges)
- A ativacao do FLIP e uma consequencia natural do roll cair no range de um item raro
Itens Elegiveis para FLIP
// src/application/services/flip.service.ts
async getFlipItems(caseId: bigint, flipThreshold: number | null = null) {
const threshold = flipThreshold ?? await this.getFlipThreshold(caseId);
if (threshold === null) return [];
const caseItems = await this.caseRepository.getCaseItems(caseId);
return caseItems.filter((ci) => ci.hashRangeStart >= threshold);
}Comportamento:
- Retorna os
CaseItemcujohashRangeStart >= flipThreshold(o tier do topo) - Se a caixa nao tem
flipThreshold(null), retorna array vazio - Nao usa o campo
isRarenem a raridade (Rarity enum) - a elegibilidade e puramente pela posicao do hash range - Os itens retornados incluem a relacao com
Item(nome, imagem, valor, etc.)
Diferenca do Modelo Antigo
O modelo antigo buscava itens por raridade (COVERT, EXTRAORDINARY, com fallback para CLASSIFIED). O modelo atual seleciona os itens pela posicao do hashRangeStart em relacao ao flipThreshold da caixa, dando controle direto sobre o corte via o threshold configurado.
Itens Normais (Nao-FLIP)
// src/application/services/flip.service.ts
async getNormalItems(caseId: bigint, flipThreshold: number | null = null) {
const threshold = flipThreshold ?? await this.getFlipThreshold(caseId);
if (threshold === null) {
return this.caseRepository.getCaseItems(caseId);
}
const caseItems = await this.caseRepository.getCaseItems(caseId);
return caseItems.filter((ci) => ci.hashRangeStart < threshold);
}Comportamento:
- Retorna os
CaseItemcujohashRangeStart < flipThreshold - Se a caixa nao tem
flipThreshold(null), retorna TODOS os itens da caixa - Esses itens compoe a primeira roleta (roleta normal)
- Sao retornados no campo
normalItemsda response da API
Selecao do Item Ganho
O item ganho e SEMPRE determinado pelo roll unico, independente de ser FLIP ou nao. Quando o FLIP ativa, a selecao passa primeiro por selectFlipWinner (busca no tier raro); se nada casar, cai no getItemByRoll sobre todos os itens:
// src/application/services/flip.service.ts
selectFlipWinner(flipItems: any[], roll: number): any | null {
const matchedItem = flipItems.find(
(item) => roll >= item.hashRangeStart && roll <= item.hashRangeEnd,
);
return matchedItem ?? null;
}
getItemByRoll(items: any[], roll: number) {
const matchedItem = items.find(
(item) => roll >= item.hashRangeStart && roll <= item.hashRangeEnd,
);
if (!matchedItem) {
throw new Error(`No item found for roll ${roll}`);
}
return matchedItem;
}Fluxo no OpenCaseUseCase:
let wonItem;
let actualIsFlip = false;
if (isFlip && flipItems.length > 0) {
const flipWinner = this.flipService.selectFlipWinner(flipItems, roll);
if (flipWinner) {
wonItem = flipWinner;
actualIsFlip = true;
}
}
if (!wonItem) {
wonItem = this.flipService.getItemByRoll(allItems, roll);
}Pontos criticos:
- A selecao FLIP usa
selectFlipWinnersobre os itens do tier raro (flipItems); o fallbackgetItemByRollcobre TODOS os itens - O roll ja esta no range de um item raro (porque
roll >= flipThreshold) actualIsFlipso viratruequandoselectFlipWinnerretorna um vencedor — e esse flag (nao oisFlipbruto) que e persistido e retornado- NAO existe um segundo roll separado para o FLIP no codigo atual
- O
flipRollna response e o MESMO valor dorollnormal (retornado apenas quandoactualIsFlip = true)
Preparacao de Itens para Roleta
O metodo prepareFlipItemsForRoulette formata os itens para o frontend:
// src/application/use-cases/case-opening/open-case.use-case.ts
private prepareFlipItemsForRoulette(items: any[]): any[] {
const sortedItems = [...items].sort(
(a, b) => Number(b.item.valueCentsBrl) - Number(a.item.valueCentsBrl),
);
return sortedItems.map((item) => ({
id: item.id.toString(),
itemId: item.item.id.toString(),
itemName: item.item.name,
itemImage: item.item.imageUrl,
rarity: item.item.rarity,
valueCents: item.item.valueCentsBrl.toString(),
valueFormatted: (Number(item.item.valueCentsBrl) / 100).toFixed(2),
dropChance: item.dropChance,
weight: item.weight,
weaponCategory: item.item.weaponCategory,
exterior: item.item.exterior ?? undefined,
isStatTrak: item.item.isStatTrak,
}));
}Comportamento:
- Ordena por
valueCentsBrldescendente (mais caros primeiro) - Retorna TODOS os itens passados (sem limitacao de 15 slots)
- Converte todos os BigInt para string (necessario para JSON)
valueCentsno retorno evalueCentsBrl(BRL, nao USD)valueFormattede o valor em reais com 2 casas decimais (ex:"707.65")- Usado tanto para
normalItemsquanto paraflipItems
Diferenca do Modelo Antigo
O modelo antigo limitava a 15 slots e usava preenchimento circular quando havia menos de 15 itens. O modelo atual retorna TODOS os itens e delega ao frontend a responsabilidade de montar a roleta visual.
Integracao com OpenCaseUseCase
O FLIP e integrado diretamente no fluxo principal de abertura. Trecho relevante do OpenCaseUseCase.execute():
// Le flip threshold e itens direto da caixa (ja carregada via findByIdCached)
const flipThreshold = caseData.flipThreshold;
const allItems = caseData.items;
// Verifica se o roll ativa FLIP
const isFlip = this.flipService.shouldTriggerFlip(roll, flipThreshold);
// Separa itens por tier usando o threshold
const normalItems =
flipThreshold === null ? allItems : allItems.filter((ci) => ci.hashRangeStart < flipThreshold);
const normalItemsData = this.prepareFlipItemsForRoulette(normalItems);
const flipItems =
flipThreshold === null ? [] : allItems.filter((ci) => ci.hashRangeStart >= flipThreshold);
const flipItemsData = this.prepareFlipItemsForRoulette(flipItems);
// Seleciona item ganho: FLIP primeiro (selectFlipWinner), fallback getItemByRoll
let wonItem;
let actualIsFlip = false;
if (isFlip && flipItems.length > 0) {
const flipWinner = this.flipService.selectFlipWinner(flipItems, roll);
if (flipWinner) {
wonItem = flipWinner;
actualIsFlip = true;
}
}
if (!wonItem) {
wonItem = this.flipService.getItemByRoll(allItems, roll);
}Dados salvos no CaseOpening:
const opening = await this.caseOpeningRepository.create({
userId,
caseId,
itemId: wonItem.item.id,
pricePaidCents: caseData.priceCents,
itemValueCents: wonItem.item.valueCentsBrl,
profitCents,
serverSeed,
serverSeedHash,
clientSeed: finalClientSeed,
nonce,
roll,
isFlip: actualIsFlip,
flipServerSeed: null,
flipClientSeed: null,
flipNonce: null,
flipRoll: null,
});Observacao importante: No codigo atual, os campos flipServerSeed, flipClientSeed, flipNonce e flipRoll sao salvos como null. O flipRoll retornado na response e o mesmo roll normal:
return {
opening,
item: wonItem.item,
dropChance: wonItem.dropChance,
inventoryItemId: inventoryItem.id,
serverSeedHash,
clientSeed: finalClientSeed,
nonce,
roll,
isFlip: actualIsFlip,
flipRoll: actualIsFlip ? roll : undefined, // Mesmo valor do roll normal, so quando ativa
flipItems: actualIsFlip ? flipItemsData : undefined,
normalItems: normalItemsData,
};Provably Fair - Seeds e Nonce
Estado Atual
No codigo atual, o FLIP usa o mesmo roll da abertura normal. O item ganho e determinado pelo roll principal caindo no hash range de um item do tier raro (hashRangeStart >= flipThreshold). Por isso:
- Nao ha seeds separados para o FLIP
flipServerSeed,flipClientSeed,flipNoncesao salvos comonullflipRollna response e igual aorollprincipal
Modelo Documentado (CLAUDE.md)
O CLAUDE.md documenta um modelo com seeds derivados:
flipServerSeed = serverSeed + '_flip'
flipClientSeed = clientSeed (mesmo)
flipNonce = nonce + 1
flipRoll = calculateRoll(flipServerSeed, flipClientSeed, flipNonce)Este modelo com seeds derivados esta descrito como a arquitetura alvo, mas a implementacao atual em FlipService e OpenCaseUseCase usa o roll unico. Os campos no banco de dados (flipServerSeed, flipClientSeed, flipNonce, flipRoll) ja existem no schema Prisma para suportar a implementacao futura.
Verificacao Provably Fair do FLIP
Como o FLIP usa o mesmo roll da abertura normal, a verificacao e identica:
// POST /cases/openings/:id/verify
const isServerSeedValid = this.provablyFairService.verifyServerSeedHash(
opening.serverSeed,
opening.serverSeedHash,
);
const isRollValid = this.provablyFairService.verifyResult(
opening.serverSeed,
opening.clientSeed,
opening.nonce,
opening.roll,
);O usuario pode verificar que:
- O
serverSeedHashcorresponde aoserverSeedrevelado - O
rollcalculado com os seeds e nonce corresponde ao armazenado - O roll cai no
hashRangeStart/hashRangeEndde um item do tier raro (hashRangeStart >= flipThreshold), confirmando que o FLIP deveria ativar
Modelo de Dados
CaseItem (campo isRare)
model CaseItem {
id BigInt @id
caseId BigInt @map("case_id")
itemId BigInt @map("item_id")
weight Int
hashRangeStart Int @map("hash_range_start")
hashRangeEnd Int @map("hash_range_end")
dropChance Float @map("drop_chance")
isRare Boolean @default(false) @map("is_rare")
case Case @relation(fields: [caseId], references: [id], onDelete: Cascade)
item Item @relation(fields: [itemId], references: [id])
@@unique([caseId, itemId])
@@map("case_items")
@@index([caseId])
@@index([itemId])
}O campo isRare ainda existe no schema (legado), mas NAO e usado pelo FlipService. A participacao no FLIP e determinada pela posicao do hashRangeStart do item em relacao ao Case.flipThreshold. Padrao de isRare: false.
Case (campo flipThreshold)
model Case {
// ... demais campos ...
flipThreshold Int? @map("flip_threshold")
flipDisplayRareSpecial Boolean @default(false) @map("flip_display_rare_special")
// ...
}flipThreshold (nullable) e o unico determinante do FLIP: se null, a caixa nao tem FLIP; caso contrario, roll >= flipThreshold ativa o FLIP e itens com hashRangeStart >= flipThreshold formam o tier raro.
CaseOpening (campos FLIP)
model CaseOpening {
// ... campos de abertura normal ...
isFlip Boolean @default(false) @map("is_flip")
flipServerSeed String? @map("flip_server_seed")
flipClientSeed String? @map("flip_client_seed")
flipNonce Int? @map("flip_nonce")
flipRoll Int? @map("flip_roll")
// ...
@@index([isFlip])
}Campos presentes no schema para suportar dual provably fair:
isFlip:truequando o FLIP foi ativadoflipServerSeed: Reservado (atualmentenull)flipClientSeed: Reservado (atualmentenull)flipNonce: Reservado (atualmentenull)flipRoll: Reservado (atualmentenull)
Response da API
Abertura Normal (sem FLIP)
{
"id": "789012345",
"inventoryItemId": "111222333",
"userId": "100",
"caseId": "123456789",
"caseName": "",
"caseImage": "",
"itemId": "456",
"itemName": "AK-47 | Redline",
"itemImage": "https://...",
"itemRarity": "CLASSIFIED",
"itemValueCents": "15000",
"itemValueFormatted": "R$ 150,00",
"pricePaidCents": "1590",
"pricePaidFormatted": "R$ 15,90",
"profitCents": "13410",
"profitFormatted": "R$ 134,10",
"serverSeedHash": "a1b2c3d4e5f6...",
"clientSeed": "minha-seed",
"nonce": 456789,
"roll": 8500000,
"isFlip": false,
"normalItems": [
{
"id": "1",
"itemId": "456",
"itemName": "AK-47 | Redline",
"itemImage": "https://...",
"rarity": "CLASSIFIED",
"valueCents": "15000",
"valueFormatted": "150.00",
"dropChance": 0.26,
"weight": 26,
"weaponCategory": "RIFLE",
"isStatTrak": false
}
],
"createdAt": "2026-02-11T10:30:00.000Z"
}Campos FLIP quando isFlip = false:
isFlip:falseflipRoll: Ausente (undefined) - so e retornado quando o FLIP ativaflipItems: Ausente (undefined, nao serializado)normalItems: Presente, com itens do tier normal (hashRangeStart < flipThreshold) ordenados por valor desc
Abertura com FLIP
{
"id": "789012345",
"inventoryItemId": "111222333",
"userId": "100",
"caseId": "123456789",
"caseName": "",
"caseImage": "",
"itemId": "999",
"itemName": "Butterfly Knife | Emerald",
"itemImage": "https://...",
"itemRarity": "EXTRAORDINARY",
"itemValueCents": "70765",
"itemValueFormatted": "R$ 707,65",
"pricePaidCents": "1590",
"pricePaidFormatted": "R$ 15,90",
"profitCents": "69175",
"profitFormatted": "R$ 691,75",
"serverSeedHash": "a1b2c3d4e5f6...",
"clientSeed": "minha-seed",
"nonce": 456789,
"roll": 9950000,
"isFlip": true,
"flipRoll": 9950000,
"flipItems": [
{
"id": "10",
"itemId": "999",
"itemName": "Butterfly Knife | Emerald",
"itemImage": "https://...",
"rarity": "EXTRAORDINARY",
"valueCents": "70765",
"valueFormatted": "707.65",
"dropChance": 1.20,
"weight": 120,
"weaponCategory": "KNIFE",
"exterior": "FACTORY_NEW",
"isStatTrak": false
},
{
"id": "9",
"itemId": "998",
"itemName": "M4A4 | Howl",
"itemImage": "https://...",
"rarity": "COVERT",
"valueCents": "45000",
"valueFormatted": "450.00",
"dropChance": 2.00,
"weight": 200,
"weaponCategory": "RIFLE",
"isStatTrak": false
},
{
"id": "8",
"itemId": "997",
"itemName": "AWP | Asiimov",
"itemImage": "https://...",
"rarity": "COVERT",
"valueCents": "25000",
"valueFormatted": "250.00",
"dropChance": 4.00,
"weight": 400,
"weaponCategory": "SNIPER",
"exterior": "FIELD_TESTED",
"isStatTrak": false
}
],
"normalItems": [
{
"id": "1",
"itemId": "456",
"itemName": "MAG-7 | Wildwood",
"itemImage": "https://...",
"rarity": "CONSUMER",
"valueCents": "37",
"valueFormatted": "0.37",
"dropChance": 66.82,
"weight": 6682,
"weaponCategory": "SHOTGUN",
"isStatTrak": false
}
],
"createdAt": "2026-02-11T10:30:00.000Z"
}Campos FLIP quando isFlip = true:
isFlip:trueflipRoll: Presente, igual aoroll(mesmo valor)flipItems: Presente, array com itens do tier raro (hashRangeStart >= flipThreshold) ordenados porvalueCentsBrldescnormalItems: Presente, array com itens do tier normal (hashRangeStart < flipThreshold) ordenados porvalueCentsBrldesc- O
itemId/itemName/itemRarity/itemValueCentsna raiz da response referem-se ao item raro ganho
Live Drop Delay
const liveDropDelay = result.isFlip ? 30000 : 12000;| Tipo | Delay | Motivo |
|---|---|---|
| Normal | 12.000ms (12s) | Animacao da roleta normal |
| FLIP | 30.000ms (30s) | Primeira roleta + animacao faca + segunda roleta |
O live drop e emitido via setTimeout apos o commit da transacao. Todos os jogadores conectados recebem o evento live:drop via WebSocket (broadcast global).
Timeline do Frontend
Duracao Total
Normal: ~12 segundos FLIP: ~25.5 segundos
Timeline FLIP Detalhada
t = 0.0s -> Recebe response da API com isFlip = true
t = 0.0s -> Configura estados: setIsFlip(true), setFlipItems(...), setFlipRoll(...)
t = 1.5s -> Inicia PRIMEIRA roleta (itens normais + card KNIFE no meio)
t = 11.5s -> Primeira roleta PARA no card KNIFE
t = 11.5s -> Inicia animacao KNIFE (rotacao 3D)
t = 14.5s -> Esconde animacao KNIFE
t = 14.5s -> Mostra SEGUNDA roleta (apenas itens raros / flipItems)
t = 15.5s -> Inicia spin da segunda roleta (vai ate o item ganho)
t = 25.5s -> Segunda roleta PARA no item ganho
t = 25.5s -> Mostra tela de vitoriaFases
| Fase | Inicio | Fim | Duracao | Descricao |
|---|---|---|---|---|
| Primeira roleta | 1.5s | 11.5s | 10s | Roleta normal para na KNIFE |
| Animacao KNIFE | 11.5s | 14.5s | 3s | KNIFE girando em 3D |
| Setup segunda roleta | 14.5s | 15.5s | 1s | Troca visual para roleta de raros |
| Segunda roleta | 15.5s | 25.5s | 10s | Roleta de raros para no item ganho |
Estados do Frontend
const [isFlip, setIsFlip] = useState(false);
const [showKnifeAnimation, setShowKnifeAnimation] = useState(false);
const [showFlipRoulette, setShowFlipRoulette] = useState(false);
const [flipItems, setFlipItems] = useState<any[]>([]);
const [flipRoll, setFlipRoll] = useState<number | null>(null);
const [currentPhase, setCurrentPhase] = useState<
'idle' | 'first-roulette' | 'knife' | 'second-roulette' | 'won'
>('idle');Primeira Roleta
A primeira roleta usa os normalItems da response e inclui um card KNIFE no meio (repeat index 2, posicao central). A roleta e calculada para parar exatamente no card KNIFE.
Animacao KNIFE
Card especial com rotacao 3D (rotateY: [0, 180, 360]) e efeito de escala (scale: [1, 1.2, 1]). Dura 3 segundos com overlay escuro (bg-black/80).
Segunda Roleta
Usa os flipItems da response. O frontend repete os itens visualmente para criar efeito de loop e calcula o offset final baseado no flipRoll para parar no item ganho.
Casos de Borda
Caixa sem flipThreshold (null)
Se Case.flipThreshold e null:
getFlipThreshold()retornanullshouldTriggerFlip()retornafalse- FLIP nunca ativa para essa caixa
flipItemsna response seraundefinednormalItemscontera todos os itens da caixa
Apenas 1 Item no Tier FLIP
Se apenas 1 item tem hashRangeStart >= flipThreshold:
flipItemstera apenas 1 elemento- Frontend precisa repetir esse item visualmente na segunda roleta
- A probabilidade do FLIP e igual a
dropChancedesse unico item
flipThreshold muito baixo
Se flipThreshold for muito baixo (ex.: 0 ou 1):
- Quase todos (ou todos) os itens terao
hashRangeStart >= flipThreshold - FLIP ativara em praticamente TODAS as aberturas
normalItemssera vazio ou quase vazio- Frontend precisa tratar a primeira roleta com poucos/nenhum item normal
Roll no Limite do Threshold
Se roll === flipThreshold:
shouldTriggerFlip()retornatrue(usa>=)- O roll esta exatamente no
hashRangeStartdo primeiro item raro - O item selecionado sera o item raro com menor
hashRangeStart
Nenhum Item Encontrado para Roll
Se getItemByRoll() nao encontra item:
- Lanca
Error('No item found for roll {roll}') - Indica que os hash ranges nao cobrem todo o espectro (bug de configuracao)
- Deve ser corrigido via
ProbabilityService.assignHashRanges()
Debugging
Backend - Logs Implementados
[OpenCaseUseCase] Roll generated: 9950000
[OpenCaseUseCase] Flip threshold: 9280000
[OpenCaseUseCase] Is FLIP: true (roll >= threshold)
[OpenCaseUseCase] Won item: Butterfly Knife | Emerald (tier FLIP)Checklist de Verificacao
FLIP nao ativa quando deveria:
- Verificar se
Case.flipThresholdesta configurado (naonull) - Verificar se ha itens com
hashRangeStart >= flipThreshold(tier raro nao vazio) - Verificar se o roll esta realmente
>= flipThreshold
- Verificar se
FLIP ativa quando nao deveria:
- Verificar o valor de
Case.flipThreshold(talvez baixo demais) - Verificar se os hash ranges estao corretos
- Verificar o valor de
Item errado ganho no FLIP:
- Verificar se
selectFlipWinner()recebe osflipItems(tier raro) e o fallbackgetItemByRoll()recebeallItems - Verificar se os hash ranges nao tem overlap ou gaps
- Usar
ProbabilityService.assignHashRanges()para recalcular
- Verificar se
flipItems vazio na response:
- Verificar se
Case.flipThresholdesta configurado e ha itens comhashRangeStart >= flipThreshold flipItemsso e incluido quando o FLIP ativa (actualIsFlip = true)
- Verificar se
Verificar integridade dos hash ranges:
typescriptconst items = await caseRepository.getCaseItems(caseId); const totalCoverage = items.reduce( (sum, i) => sum + (i.hashRangeEnd - i.hashRangeStart + 1), 0 ); // Deve ser exatamente 10.000.000 const { flipThreshold } = await caseRepository.findById(caseId); const flipItems = items.filter(i => i.hashRangeStart >= flipThreshold); // Estes sao os itens do tier FLIP
Arquivos Relacionados
| Arquivo | Responsabilidade |
|---|---|
src/application/services/flip.service.ts | Logica completa do FLIP: threshold, itens raros/normais, selecao por roll |
src/application/use-cases/case-opening/open-case.use-case.ts | Integracao do FLIP no fluxo de abertura, prepareFlipItemsForRoulette |
src/application/services/provably-fair.service.ts | Calculo e verificacao do roll |
src/application/services/probability.service.ts | Calculo de hash ranges (assignHashRanges) |
src/application/use-cases/case-opening/verify-case-opening.use-case.ts | Verificacao Provably Fair |
src/presentation/controllers/case-opening.controller.ts | Endpoint POST /cases/:id/open |
src/application/dto/case-opening.dto.ts | DTOs: FlipItemDto, CaseOpeningResponseDto |
src/infrastructure/websocket/websocket.gateway.ts | Emissao de live:drop e balance:updated |
prisma/schema.prisma | Modelos Case (flipThreshold), CaseItem e CaseOpening (campos FLIP) |
