> For the complete documentation index, see [llms.txt](https://docs.rainmad.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.rainmad.com/resources/3d-interactive-minigames-bundle/systems/minigames.md).

# Minigames

Thirty-three minigames, one export each. Every one returns `true` on success and `false` on cancel or fail. Every one accepts an `opts` table where you can override the difficulty, the placement, or any React prop the component reads.

The list below groups them by feel. Names in the code map straight to `cfg.<name>` and `exports['rm_3dminigames']:<name>()`.

***

### Puzzle & logic

| Name              | Feel                                         | Time-ish |
| ----------------- | -------------------------------------------- | -------- |
| `untangle`        | Drag nodes so no edges cross                 | 40-90s   |
| `slidingPuzzle`   | Slide tiles to rebuild an image              | 60s      |
| `wireMatching`    | Match coloured wires across a panel          | 30-60s   |
| `wireSnip`        | Cut the right wire before the timer runs out | 20s      |
| `pathTracing`     | Trace a path through a grid without lifting  | 15-25s   |
| `dataConduit`     | Route packets across a board of switches     | 45s      |
| `circuitPulse`    | Fire pulses in time so they arrive together  | 20-45s   |
| `laserGrid`       | Reflect a laser through mirrors to a target  | 30-60s   |
| `sequenceBreaker` | Decode a hidden sequence from partial hints  | 30s      |
| `chemicalBalance` | Balance a chemical equation by drag-and-drop | 60-120s  |

***

### Timing & reflex

| Name             | Feel                                                      | Time-ish |
| ---------------- | --------------------------------------------------------- | -------- |
| `rhythmArrows`   | Press arrow keys in sync with the incoming beat           | 30s      |
| `flashPad`       | Repeat the flashing pattern back at it, longer each round | 30-60s   |
| `motionDetector` | Move only when the guard looks away                       | 30s      |
| `spinnerLock`    | Stop the spinner when it lines up with the target         | 20s      |
| `vaultDrill`     | Hold pressure in the sweet spot while the drill bites     | 30s      |
| `powerGrid`      | Balance current across breakers before one blows          | 30s      |

***

### Terminal & hack flavour

| Name                | Feel                                                    | Time-ish |
| ------------------- | ------------------------------------------------------- | -------- |
| `terminalHack`      | Fallout-style word guess against a countdown            | 30-60s   |
| `portScanner`       | Sweep ports for the one that opens the door             | 30s      |
| `dataStreamCapture` | Snap the moving cursor when the target byte is in frame | 30s      |
| `firewallBreach`    | Peel firewall layers by matching packet types           | 30s      |
| `ipTracer`          | Ping subnets to zero in on the target IP                | 25-50s   |
| `packetInterceptor` | Grab packets off a moving stream before they leave      | 30s      |
| `signalDescrambler` | Rotate carrier bands until the audio comes clean        | 30s      |

***

### Vault, keycard & combination

| Name                | Feel                                          | Time-ish |
| ------------------- | --------------------------------------------- | -------- |
| `vaultCombination`  | Dial N wheels to the target number sequence   | 30-60s   |
| `cipherWheel`       | Line up cipher wheels to decode a phrase      | 30s      |
| `keycardReassembly` | Match keycard-fragment pairs on a memory grid | 55-100s  |

***

### Investigation flavour

| Name               | Feel                                                   | Time-ish |
| ------------------ | ------------------------------------------------------ | -------- |
| `fingerprintTrace` | Repair a scanned print by fixing corrupt zones         | 35-70s   |
| `cctvSweep`        | Sweep cameras until the target appears in shot         | 30s      |
| `dnaSplicer`       | Splice DNA strands so the pattern reads clean          | 30s      |
| `frequencyMatch`   | Tune two dials until the waveforms align               | 30s      |
| `dataMatcher`      | Pick the matching record from a scrolling wall of data | 30s      |
| `colorMatch`       | Reproduce the shown colour by mixing sliders           | 30s      |
| `mathChallenge`    | Solve N arithmetic problems back to back               | 30-45s   |

***

### Per-minigame tuning

Every entry has a `cfg.<name>` key. Two shapes:

#### Full difficulty table

```lua
cfg.untangle = {
    default = 'medium',
    difficulty = {
        easy   = { nodeCount = 6,  timeLimit = 90 },
        medium = { nodeCount = 8,  timeLimit = 60 },
        hard   = { nodeCount = 12, timeLimit = 40 },
    },
}
```

The values are handed straight to the React component as props. Adjust freely.

#### Difficulty-only

```lua
cfg.rhythmArrows = { default = 'medium' }
```

The React component owns its own numbers and reads only the difficulty label. If you want finer control, open the component in `web/src/minigames/<name>.tsx`, expose the tunables as props, and rebuild.

#### Overriding per-call

Any field of a difficulty preset can be overridden at the callsite:

```lua
exports['rm_3dminigames']:untangle({
    difficulty = 'hard',
    timeLimit  = 20,       -- overrides the hard preset's 40
    placement  = 'monitor',
    _targetEntity = ent,
})
```

Fields the component reads that are not in the preset also work, so this is the escape hatch for one-off tuning. Anything unrecognised is ignored.

***

### Localisation

All player-facing text lives in `locales/`. `en.json` is the reference file. To add a language, copy `en.json` to a new locale code and translate the values while leaving the keys untouched. Locales load through `ox_lib`, so a player with `lib.locale('de')` will see the German file.

Keep any `%d` or `{token}` placeholders exactly where they were. They are filled in at runtime, so shuffling them shows wrong numbers or breaks the message.

***

### Adding a new minigame

1. Write `web/src/minigames/MyGame.tsx`. Accept `locales` plus whatever props you want.
2. Register it in the `Minigames` map at the top of `web/src/App.tsx`.
3. Add `makeExport('myGame', cfg.myGame)` in `client/client.lua`.
4. Add `cfg.myGame = { default = 'medium', difficulty = { ... } }` in `cfg.lua`.
5. Add `myGame = true` to `KNOWN_MINIGAMES` in `server/sync.lua` so `/create_minigame` will accept it.
6. Add it to the `MINIGAMES` lists at the top of `client/creator.lua` and `client/test_command.lua` so the dropdowns list it.
7. Rebuild the bundle: `cd web && npm run build`.

Call it once with `/test_minigame screen myGame` to sanity-check. If it opens and returns to the console, you are done.
