Building a “Cheat Skill” in Roblox Without Breaking Fairness: Cat Eyes
If you want a premium ability that feels powerful but still keeps your game fair, the Cat Eyes pattern is a strong approach.
In this system, Cat Eyes is a paid intelligence skill:
- It does **not** auto-win matches.
- It does **not** modify server outcomes directly.
- It gives a player better information during split/steal decisions.
The result is a feature that feels like a “cheat” to players (in a fun way), while still being deterministic, server-authoritative, and easy to maintain.
Why this design works
Most “OP” abilities fail in one of two ways:
1. They are purely cosmetic and players don’t care.
2. They bypass core game logic and destroy competitive trust.
Cat Eyes avoids both by making the advantage **informational**.
During split/steal:
- The server tracks each participant’s current choice.
- A Cat Eyes owner receives an aggregated hint: whether any opponents are currently on split and/or steal.
- The player still makes their own decision.
This gives meaningful edge without replacing gameplay.
System architecture
Cat Eyes uses four layers:
1. **Eligibility + authority (server):** `GameManager`
2. **Purchase & ownership lifecycle (server):** `PurchaseManager`
3. **Persistence (server):** `PlayerManager`
4. **Rendering (client):** highlight + timer + merchant/store UI
Main files:
- [src/server/GameManager.luau](src/server/GameManager.luau)
- [src/server/Managers/PurchaseManager.luau](src/server/Managers/PurchaseManager.luau)
- [src/server/Managers/PlayerManager.luau](src/server/Managers/PlayerManager.luau)
- [src/client/catEyes.client.luau](src/client/catEyes.client.luau)
- [src/client/CatEyesTimer.client.luau](src/client/CatEyesTimer.client.luau)
- [src/server/CatEyesMerchant.server.luau](src/server/CatEyesMerchant.server.luau)
- [src/client/catEyesMerchant.client.luau](src/client/catEyesMerchant.client.luau)
Server-authoritative eligibility
The key pattern is: client can render, server decides entitlement.
`GameManager:_CanUseCatEyes(player)` checks:
- Permanent unlock (`CatEyesUnlocked == true`)
- Or timed validity (`CatEyesExpiresAt > os.time()`)
It also self-heals `CatEyesOwned` to keep state consistent.
This is important because UI and round logic should never trust a stale boolean that can drift from expiry timestamps.
Information model, not raw leaks
The server doesn’t send all player internals. It sends a compact payload:
split = true/false,
steal = true/false,That payload is computed from active participants excluding the viewer.
Why this is good:
- Lower exploit value than shipping full per-player choices.
- Lower network payload.
- Easier to version later.
In `GameManager`, this flow is split into dedicated methods:
- `_SetActiveSplitParticipants`
- `_BuildCatEyesInsightPayload`
- `_BroadcastCatEyesInsightForParticipants`
- `_ClearCatEyesInsightForParticipants`
This keeps Cat Eyes logic isolated from round orchestration complexity.
Client rendering with reversible styles
On the client (`catEyes.client.luau`), tagged split/steal buttons are highlighted when payload flags are true.
A subtle but important implementation detail: **base button style is captured once** and restored when insight clears.
That avoids long-term UI corruption from repeated updates and dynamic GUI cloning.
Pattern used:
- `buttonBaseStyles[button] = { … }`
- `applyHighlight(button, isActive)` toggles between base and highlighted style
This is a clean way to make runtime highlights safe across respawns and UI rebuilds.
Monetization lifecycle (timed + permanent)
Cat Eyes products are handled in `PurchaseManager`:
- `CatEyes5` dev product
- `CatEyes10` dev product
- `CatEyes60` dev product
- `CatEyesPerm` game pass
Timed purchases stack by extending from the greater of:
- current expiry
- current server time
```luau
Get Krakendono’s stories in your inbox
Join Medium for free to get updates from this writer.
local baseTime = math.max(currentExpires, nowUnix)
local nextExpires = baseTime + durationSeconds
```
This is a high-quality UX decision: players never lose remaining paid time.
Permanent purchase sets:
- `CatEyesUnlocked = true`
- `CatEyesOwned = true`
- `CatEyesExpiresAt = 0`
Also, ownership refresh runs on initialize/player-added via `UserOwnsGamePassAsync`, so cross-server reliability is strong.
Timer UX that survives account lifecycle
The timer label script (`CatEyesTimer.client.luau`) supports both tags:
- `CatEYesTimer`
- `CatEyesTimer`
It resolves label location from tagged instances under `PlayerStats`, including StarterGui→PlayerGui clone mapping.
Most importantly, visibility is controlled by `CatEyesLabelUnlocked`:
- Hidden before first unlock.
- Once unlocked, label remains available.
Text behavior:
- Permanent: `Cat Eyes: Permanent`
- Timed: `Cat Eyes: MM:SS`
This solves common confusion where a paid feature “disappears” visually even if your progression model says the user discovered/unlocked it.
Secret discovery funnel (merchant reveal)
Cat Eyes is not only sold through flat UI; it’s embedded in exploration:
- `SecretShop` tagged spots trigger discovery interaction.
- Discovery sets `CatEyesMerchantFound`.
- `CatEyesMerchant` model remains hidden until found, then becomes available.
- Merchant prompt opens Cat Eyes store UI (`CatEyesStore`).
Server side (`CatEyesMerchant.server.luau`) remains authoritative:
- Attaches prompts from tags.
- Validates discovered state.
- Fires minimal remote events for client behavior.
Client side handles presentation:
- Show/hide merchant model locally.
- Toggle secret vs merchant prompts.
- Bind close and purchase buttons by tag.
This is a great progression pattern: monetization attached to world discovery, not just a static shop menu.
Persistence strategy
`PlayerManager` stores these Cat Eyes fields:
- `catEyesUnlocked`
- `catEyesExpiresAt`
- `catEyesLabelUnlocked`
- `catEyesMerchantFound`
Load logic recomputes effective ownership:
local catEyesOwned = catEyesUnlocked or (catEyesExpiresAt > os.time())This ensures resilience when old saves miss fields, and avoids long-term drift.
Security and anti-exploit posture
The design follows practical anti-exploit rules:
1. **Server computes entitlement and insight payload.**
2. **Client receives only what it needs to render.**
3. **Critical state persists on server save path.**
4. **Remotes are command-style and narrow.**
Could a client fake a highlight locally? Yes — but only for itself. It cannot alter server split/steal outcomes through Cat Eyes channels.
That is exactly the trust boundary you want.
Dev tips if you add similar “cheat skills”
If you build your own premium advantage system, reuse these principles:
- Prefer **decision support** over direct action override.
- Keep eligibility checks centralized in one server method.
- Persist both entitlement and UX-state flags separately.
- Add explicit clear/update events so UI can always recover.
- Keep payloads aggregate-level unless you absolutely need detail.
Quick implementation checklist
- [ ] Add server entitlement attributes (`Unlocked`, `ExpiresAt`, derived `Owned`)
- [ ] Add purchase grant + ownership refresh
- [ ] Add save/load fields
- [ ] Add remote for live insight updates
- [ ] Add client renderer with reversible style cache
- [ ] Add timer label with explicit visibility policy
- [ ] Add world discovery + merchant gating
- [ ] Add diagnostics logs around grant/broadcast flows
Conclusion
Cat Eyes works because it feels powerful while preserving competitive integrity.
As a developer, the biggest win is not the highlight effect — it’s the architecture:
- server-authoritative entitlement,
- compact insight payloads,
- reliable persistence,
- and progression-aware UX.








