> ## Documentation Index
> Fetch the complete documentation index at: https://docs.awokenlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reaction & Precision

> Eleven timing, aim and steady-hand games - Skill Check, Circle Click, Hold Zone, Aim Test, Balance, Rhythm, Firewall Pulse, Safe Crack, Lockpick, Osu and Slider.

Eleven games built around timing, aim and steady hands. Defaults live in `config/games/reaction.lua`.

<Info>
  Set any option as a server-wide default in `config/games/reaction.lua`, or pass it in a single call to change it just that once. Every game also accepts the [shared options](/resources/awoken-minigames/configuration/overview#shared-options) (`canCancel`, `title`, `theme`, ...) - see the [Overview](/resources/awoken-minigames/configuration/overview).
</Info>

***

## Skill Check

A marker slides across a bar. Press the shown key while the marker is inside the highlighted zone.

**Export:** `exports['awoken_minigames']:SkillCheck(opts)` - returns `true` only on a win.

| Option            | Default             | Notes                                                                                  |
| ----------------- | ------------------- | -------------------------------------------------------------------------------------- |
| `rounds`          | `3`                 | Checks to clear (`1`-`20`).                                                            |
| `keys`            | `{ 'E', 'F', 'R' }` | Key pool - a random one is shown each round. This is a pool size, NOT the round count. |
| `speed`           | `65`                | Marker speed, % of the bar per second.                                                 |
| `zoneSize`        | `18`                | Success zone width % (`8`-`40`).                                                       |
| `perfectZoneSize` | `5`                 | Inner perfect band % (`0` = disabled).                                                 |
| `timeLimit`       | `0`                 | Total ms budget across rounds (`0` = no limit).                                        |
| `randomizeZone`   | `true`              | Randomise zone position each round.                                                    |
| `mistakesAllowed` | `0`                 | Misses/wrong keys tolerated before fail.                                               |
| `canCancel`       | `true`              |                                                                                        |
| `autoStart`       | `true`              | Starts immediately. `false` shows a "press to start" gate first.                       |

```lua Presets theme={"dark"}
-- Easy
exports['awoken_minigames']:SkillCheck({ rounds = 3, keys = { 'E','F' }, speed = 55, zoneSize = 22, mistakesAllowed = 1 })
-- Standard
exports['awoken_minigames']:SkillCheck({ rounds = 3, keys = { 'E','F','R' }, speed = 65, zoneSize = 18 })
-- Hard
exports['awoken_minigames']:SkillCheck({ rounds = 4, keys = { 'E','F','R','G' }, speed = 80, zoneSize = 14 })
-- Long haul
exports['awoken_minigames']:SkillCheck({ rounds = 8, keys = { 'E','F','R' }, speed = 65, zoneSize = 18, mistakesAllowed = 1 })
```

<Info>
  `rounds` is how many checks the player faces; `keys` is just the pool each prompt is drawn from. A bigger pool makes the prompt less predictable, not the game longer.
</Info>

***

## Circle Click

A pointer spins around a dial. Each round shows a key - press it while the pointer is inside the highlighted arc. It speeds up every round.

**Export:** `exports['awoken_minigames']:CircleClick(opts)` - returns `true` only on a win.

| Option               | Default                  | Notes                                                            |
| -------------------- | ------------------------ | ---------------------------------------------------------------- |
| `rounds`             | `5`                      | Rounds to clear (`1`-`20`).                                      |
| `keys`               | `{ 'W', 'A', 'S', 'D' }` | Key pool, random pick each round.                                |
| `rotationSpeed`      | `120`                    | Pointer speed, degrees per second.                               |
| `speedIncrease`      | `15`                     | Deg/s added to the pointer each round.                           |
| `zoneSize`           | `45`                     | Target arc width, degrees (`10`-`120`).                          |
| `randomizeDirection` | `true`                   | Flip rotation direction from round 2.                            |
| `timeLimit`          | `0`                      | Total ms budget across rounds (`0` = no limit).                  |
| `mistakesAllowed`    | `2`                      | Misses/wrong keys tolerated before fail.                         |
| `canCancel`          | `true`                   |                                                                  |
| `autoStart`          | `true`                   | Starts immediately. `false` shows a "press to start" gate first. |

```lua Presets theme={"dark"}
-- Tutorial
exports['awoken_minigames']:CircleClick({ rounds = 3, rotationSpeed = 90, zoneSize = 60, keys = { 'E' }, randomizeDirection = false })
-- Easy
exports['awoken_minigames']:CircleClick({ rounds = 4, rotationSpeed = 105, zoneSize = 55 })
-- Standard
exports['awoken_minigames']:CircleClick({ rounds = 5, rotationSpeed = 120, zoneSize = 45 })
-- Hard
exports['awoken_minigames']:CircleClick({ rounds = 6, rotationSpeed = 150, zoneSize = 35, speedIncrease = 20, mistakesAllowed = 1 })
-- Expert
exports['awoken_minigames']:CircleClick({ rounds = 7, rotationSpeed = 180, zoneSize = 28, speedIncrease = 20, mistakesAllowed = 0, timeLimit = 25000 })
```

***

## Hold Zone

Hold a key to shrink a ring inward, then release it while the ring is inside the highlighted band.

**Export:** `exports['awoken_minigames']:HoldZone(opts)` - returns `true` only on a win.

| Option            | Default             | Notes                                                                                  |
| ----------------- | ------------------- | -------------------------------------------------------------------------------------- |
| `keys`            | `{ 'E', 'F', 'R' }` | Key pool - a random one is shown each round. This is a pool size, NOT the round count. |
| `rounds`          | `3`                 | Successful releases required (`1`-`10`).                                               |
| `speed`           | `18`                | Ring shrink speed, % of radius per second.                                             |
| `zoneSize`        | `18`                | Success band width % (`8`-`40`).                                                       |
| `mistakesAllowed` | `1`                 | Failed releases tolerated before fail.                                                 |
| `canCancel`       | `true`              |                                                                                        |
| `autoStart`       | `true`              | Starts immediately. `false` shows a "press to start" gate first.                       |

```lua Presets theme={"dark"}
-- Easy
exports['awoken_minigames']:HoldZone({ rounds = 3, speed = 15, zoneSize = 22 })
-- Standard
exports['awoken_minigames']:HoldZone({ rounds = 3, speed = 18, zoneSize = 18 })
-- Hard
exports['awoken_minigames']:HoldZone({ rounds = 4, speed = 26, zoneSize = 14, mistakesAllowed = 0 })
-- Single key (always E)
exports['awoken_minigames']:HoldZone({ rounds = 3, speed = 18, zoneSize = 18, keys = { 'E' } })
```

***

## Aim Test

Targets pop up one at a time and vanish after a moment. Click enough of them before too many slip away or the clock runs out.

**Export:** `exports['awoken_minigames']:AimTest(opts)` - returns `true` only on a win.

| Option           | Default | Notes                                                            |
| ---------------- | ------- | ---------------------------------------------------------------- |
| `timeLimit`      | `30000` | Total ms budget (`0` = no limit).                                |
| `targetsToHit`   | `10`    | Targets to click to win.                                         |
| `targetLifetime` | `1500`  | Ms before a target expires (`300`-`10000`).                      |
| `targetSize`     | `60`    | Target diameter in px (`24`-`200`).                              |
| `shrinkTarget`   | `true`  | Shrink over its lifetime.                                        |
| `maxMisses`      | `5`     | Expired targets tolerated before fail.                           |
| `timePenalty`    | `0`     | Ms removed per miss (`0` = disabled).                            |
| `canCancel`      | `true`  |                                                                  |
| `autoStart`      | `true`  | Starts immediately. `false` shows a "press to start" gate first. |

```lua Presets theme={"dark"}
-- Tutorial
exports['awoken_minigames']:AimTest({ targetsToHit = 6, targetLifetime = 2500, targetSize = 80, shrinkTarget = false, maxMisses = 6 })
-- Easy
exports['awoken_minigames']:AimTest({ targetsToHit = 8, targetLifetime = 2000, targetSize = 70 })
-- Standard
exports['awoken_minigames']:AimTest({ targetsToHit = 10, targetLifetime = 1500, targetSize = 60 })
-- Hard
exports['awoken_minigames']:AimTest({ targetsToHit = 12, targetLifetime = 1100, targetSize = 48, maxMisses = 3 })
-- Expert
exports['awoken_minigames']:AimTest({ targetsToHit = 15, targetLifetime = 900, targetSize = 40, maxMisses = 2 })
```

***

## Balance

A needle drifts along a meter. Hold Q and E to nudge it back into the green band and keep it out of the red until the timer runs out.

**Export:** `exports['awoken_minigames']:Balance(opts)` - returns `true` only on a win.

| Option            | Default | Notes                                                                                        |
| ----------------- | ------- | -------------------------------------------------------------------------------------------- |
| `timeLimit`       | `10000` | Ms to survive (`3000`-`120000`).                                                             |
| `driftSpeed`      | `3`     | Base drift strength (`1`-`10`).                                                              |
| `sensitivity`     | `8`     | Q/E counter-steer strength (`2`-`20`).                                                       |
| `greenZoneWidth`  | `30`    | Safe band half-width, degrees (`5`-`45`).                                                    |
| `yellowZoneWidth` | `25`    | Warning band beyond green, degrees. `greenZoneWidth + yellowZoneWidth` must be `85` or less. |
| `driftRandomness` | `2`     | Per-frame drift jitter (`0`-`10`).                                                           |
| `maxDangerTime`   | `1000`  | Ms allowed in the red before fail.                                                           |
| `canCancel`       | `true`  |                                                                                              |
| `autoStart`       | `false` | Shows a "press to start" gate. `true` starts immediately.                                    |

```lua Presets theme={"dark"}
-- Tutorial
exports['awoken_minigames']:Balance({ timeLimit = 8000, driftSpeed = 2, sensitivity = 10, greenZoneWidth = 38, yellowZoneWidth = 25, driftRandomness = 1, maxDangerTime = 2000 })
-- Easy
exports['awoken_minigames']:Balance({ timeLimit = 10000, driftSpeed = 2.5, sensitivity = 9, greenZoneWidth = 34, yellowZoneWidth = 25, maxDangerTime = 1500 })
-- Standard
exports['awoken_minigames']:Balance({ timeLimit = 10000, driftSpeed = 3, sensitivity = 8, greenZoneWidth = 30, yellowZoneWidth = 25 })
-- Hard
exports['awoken_minigames']:Balance({ timeLimit = 15000, driftSpeed = 4, sensitivity = 8, greenZoneWidth = 22, yellowZoneWidth = 22, driftRandomness = 3 })
-- Expert
exports['awoken_minigames']:Balance({ timeLimit = 20000, driftSpeed = 5, sensitivity = 7, greenZoneWidth = 16, yellowZoneWidth = 18, driftRandomness = 4, maxDangerTime = 800 })
```

***

## Rhythm

Notes fall down several lanes toward a line. Press each lane's key as its note reaches the line. Land enough hits before too many notes slip past.

**Export:** `exports['awoken_minigames']:Rhythm(opts)` - returns `true` only on a win.

| Option           | Default                  | Notes                                                                                   |
| ---------------- | ------------------------ | --------------------------------------------------------------------------------------- |
| `keys`           | `{ 'A', 'S', 'D', 'F' }` | The lane set - one lane per key, so 4 keys = 4 lanes. Deduped, capped at `6`.           |
| `noteSpeed`      | `240`                    | Fall speed, px/second (`60`-`600`).                                                     |
| `noteSpawnRate`  | `900`                    | Ms between note spawns (`250`-`4000`).                                                  |
| `requiredNotes`  | `15`                     | Successful hits needed to win.                                                          |
| `difficulty`     | `'normal'`               | `'easy'`\|`'normal'`\|`'hard'` (hit window size).                                       |
| `hitWindowMs`    | *(unset)*                | Optional exact catch window in ms (`30`-`400`); overrides difficulty, smaller = harder. |
| `maxWrongKeys`   | `5`                      | Off-beat presses tolerated before fail.                                                 |
| `maxMissedNotes` | `3`                      | Notes allowed to slip past before fail.                                                 |
| `canCancel`      | `true`                   |                                                                                         |
| `autoStart`      | `true`                   | Starts immediately. `false` shows a "press to start" gate first.                        |

```lua Presets theme={"dark"}
-- Easy (4 lanes)
exports['awoken_minigames']:Rhythm({ keys = { 'A','S','D','F' }, noteSpeed = 210, noteSpawnRate = 1000, requiredNotes = 10, difficulty = 'easy' })
-- Standard (4 lanes)
exports['awoken_minigames']:Rhythm({ keys = { 'A','S','D','F' }, noteSpeed = 240, noteSpawnRate = 900, requiredNotes = 15, difficulty = 'normal' })
-- Hard (4 lanes)
exports['awoken_minigames']:Rhythm({ keys = { 'A','S','D','F' }, noteSpeed = 300, noteSpawnRate = 700, requiredNotes = 20, difficulty = 'hard' })
-- Six lanes (the maximum)
exports['awoken_minigames']:Rhythm({ keys = { 'A','S','D','F','G','H' }, noteSpeed = 300, noteSpawnRate = 650, requiredNotes = 24, difficulty = 'normal' })
```

***

## Firewall Pulse

A marker pulses across a bar. Press the shown key while it's inside the safe zone. Each hit makes the marker faster and the zone smaller.

**Export:** `exports['awoken_minigames']:FirewallPulse(opts)` - returns `true` only on a win.

| Option                 | Default       | Notes                                                                                                 |
| ---------------------- | ------------- | ----------------------------------------------------------------------------------------------------- |
| `keys`                 | `{ 'SPACE' }` | Key pool - a random one is rolled each hack. Entries are single characters, or the literal `'SPACE'`. |
| `requiredHacks`        | `3`           | Breaches needed to win (`1`-`15`).                                                                    |
| `initialSpeed`         | `30`          | Starting marker speed, % of bar/second.                                                               |
| `maxSpeed`             | `150`         | Speed ceiling as it ramps (+15%/s per hack).                                                          |
| `timeLimit`            | `8000`        | Overall ms budget (`0` = no limit).                                                                   |
| `safeZoneMinWidth`     | `10`          | Smallest the zone shrinks to, % of bar.                                                               |
| `safeZoneMaxWidth`     | `30`          | Starting zone width, % of bar.                                                                        |
| `safeZoneShrinkAmount` | `4`           | % of bar removed per landed hack.                                                                     |
| `canCancel`            | `true`        |                                                                                                       |
| `autoStart`            | `true`        | Starts immediately. `false` shows a "press to start" gate first.                                      |

```lua Presets theme={"dark"}
-- Easy
exports['awoken_minigames']:FirewallPulse({ requiredHacks = 3, initialSpeed = 30, maxSpeed = 120, safeZoneMaxWidth = 34 })
-- Standard
exports['awoken_minigames']:FirewallPulse({ requiredHacks = 3, initialSpeed = 30, maxSpeed = 150, safeZoneMaxWidth = 30, timeLimit = 8000 })
-- Hard
exports['awoken_minigames']:FirewallPulse({ requiredHacks = 4, initialSpeed = 45, maxSpeed = 180, safeZoneMaxWidth = 24, safeZoneMinWidth = 8, safeZoneShrinkAmount = 5, timeLimit = 10000 })
-- Key pool instead of SPACE
exports['awoken_minigames']:FirewallPulse({ requiredHacks = 4, keys = { 'E','F','R' }, initialSpeed = 35, maxSpeed = 150, safeZoneMaxWidth = 28 })
```

***

## Safe Crack

Spin a 0-99 dial back and forth and press Space when it lands on each target number. Clear every tumbler to crack the safe.

**Export:** `exports['awoken_minigames']:SafeCrack(opts)` - returns `true` only on a win.

| Option      | Default | Notes                                                            |
| ----------- | ------- | ---------------------------------------------------------------- |
| `tumblers`  | `3`     | Combination positions to find (`2`-`5`).                         |
| `maxTime`   | `0`     | Ms time limit (`0` = no limit).                                  |
| `canCancel` | `true`  |                                                                  |
| `autoStart` | `true`  | Starts immediately. `false` shows a "press to start" gate first. |

```lua Presets theme={"dark"}
-- Easy
exports['awoken_minigames']:SafeCrack({ tumblers = 2 })
-- Standard
exports['awoken_minigames']:SafeCrack({ tumblers = 3 })
-- Hard
exports['awoken_minigames']:SafeCrack({ tumblers = 4 })
-- Expert
exports['awoken_minigames']:SafeCrack({ tumblers = 5, maxTime = 60000 })
-- (defaults)
exports['awoken_minigames']:SafeCrack({})
```

<Info>
  The dial is always 0-99 and isn't configurable; difficulty comes from `tumblers` and `maxTime`.
</Info>

***

## Lockpick

Rotate each ring with A/D to line its coloured pins up with the matching coloured notches, then press Enter or Space to lock it in. Lock every ring before the timer runs out - locking a ring that isn't lined up jams it.

**Export:** `exports['awoken_minigames']:Lockpick(opts)` - returns `true` only on a win. (Pure pass/fail - a fail meaning "trip an alarm", cost an item, etc. is up to the caller.)

| Option            | Default                                         | Notes                                                                                           |
| ----------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `rings`           | `4`                                             | A ring **count** (`2`-`8`, density auto-scales), OR an **array** of per-ring specs (see below). |
| `ringDefaults`    | *(the template in `config/games/reaction.lua`)* | The density template used when `rings` is a plain count. Overridable per call.                  |
| `mistakesAllowed` | `1`                                             | Wrong lock-ins tolerated before it jams.                                                        |
| `timeLimit`       | `20000`                                         | Ms budget (`0` falls back to `20000`).                                                          |
| `canCancel`       | `true`                                          |                                                                                                 |
| `autoStart`       | `true`                                          | Starts immediately. `false` shows a "press to start" gate first.                                |

Per-ring array - each entry sets that ring's `balls` and `markers`, either a fixed number or a `{min, max}` random range:

```lua Presets theme={"dark"}
-- 3 rings, auto density
exports['awoken_minigames']:Lockpick({ rings = 3, mistakesAllowed = 2, timeLimit = 25000 })
-- Standard (defaults)
exports['awoken_minigames']:Lockpick({})
-- 8 rings, auto density
exports['awoken_minigames']:Lockpick({ rings = 8, mistakesAllowed = 0, timeLimit = 36000 })
-- fully custom per ring
exports['awoken_minigames']:Lockpick({
    rings = {
        { balls = 6,        markers = 3 },
        { balls = 10,       markers = 4 },
        { balls = {14, 18}, markers = {4, 6} },
        { balls = 22,       markers = 6 },
    },
    mistakesAllowed = 1, timeLimit = 28000,
})
```

When `rings` is a plain **count**, each ring's balls/markers come from the `ringDefaults` template in `config/games/reaction.lua` (innermost first; ring N reuses the last entry if the list is shorter). Tune that array to change what a numeric ring count looks like server-wide. Bigger rings have more room, so a fixed ball count looks sparser the further out it sits - the defaults ramp the outer rings up to stay full.

***

## Osu

Numbered targets fade in one after another, each with a shrinking ring around it. Move onto the current target and click just as the ring closes on it. Miss, mistime, or let one expire and you fail; clear the whole chain to win.

**Export:** `exports['awoken_minigames']:Osu(opts)` - returns `true` only on a win.

| Option      | Default | Notes                                                           |
| ----------- | ------- | --------------------------------------------------------------- |
| `targets`   | `6`     | Numbered targets in the chain (`3`-`16`).                       |
| `interval`  | `900`   | Ms between beats (`350`-`2500`); smaller = faster.              |
| `hitWindow` | `320`   | Hit-timing tolerance either side of the beat, ms (`120`-`600`). |
| `canCancel` | `true`  |                                                                 |
| `autoStart` | `false` | Shows a "press to start" gate. `true` starts immediately.       |

```lua Presets theme={"dark"}
-- Easy
exports['awoken_minigames']:Osu({ targets = 5, interval = 1100, hitWindow = 420 })
-- Standard (defaults)
exports['awoken_minigames']:Osu({})
-- Hard
exports['awoken_minigames']:Osu({ targets = 9, interval = 700, hitWindow = 230 })
-- Expert
exports['awoken_minigames']:Osu({ targets = 12, interval = 550, hitWindow = 170 })
```

<Tip>
  `hitWindow` sets how forgiving the timing is; `interval` only sets the pace. Lower `hitWindow` first to make it harder.
</Tip>

***

## Slider

Symbols scroll past a catch zone and a prompt tells you which one to catch. Press Space only while that symbol is inside the zone. You can read every symbol early, so the challenge is picking the right one, not reacting fast.

**Export:** `exports['awoken_minigames']:Slider(opts)` - returns `true` only on a win.

| Option            | Default | Notes                                                                 |
| ----------------- | ------- | --------------------------------------------------------------------- |
| `targets`         | `6`     | Symbols to catch correctly to win (`1`-`20`).                         |
| `symbols`         | `4`     | Distinct symbols in play (`2`-`6`); more = harder to tell apart.      |
| `speed`           | `32`    | Scroll speed, % of the track per second (`5`-`120`).                  |
| `zoneSize`        | `16`    | Catch zone width, % of the track (`6`-`30`).                          |
| `mistakesAllowed` | `2`     | Wrong catches, empty presses, and wanted symbols let past, tolerated. |
| `timeLimit`       | `0`     | Total ms budget (`0` = no limit).                                     |
| `timePenalty`     | `0`     | Ms removed per mistake (`0` = disabled).                              |
| `canCancel`       | `true`  |                                                                       |
| `autoStart`       | `false` | Shows a "press to start" gate. `true` starts immediately.             |

```lua Presets theme={"dark"}
-- Tutorial
exports['awoken_minigames']:Slider({ targets = 3, symbols = 2, speed = 20, zoneSize = 26 })
-- Standard (defaults)
exports['awoken_minigames']:Slider({})
-- Hard
exports['awoken_minigames']:Slider({ targets = 8, symbols = 5, speed = 44, zoneSize = 12, mistakesAllowed = 1 })
-- Expert
exports['awoken_minigames']:Slider({ targets = 10, symbols = 6, speed = 52, mistakesAllowed = 0 })
-- Against the clock
exports['awoken_minigames']:Slider({ targets = 6, speed = 36, timeLimit = 30000 })
```

<Tip>
  Catching the wrong symbol, pressing with nothing in the zone, or letting the wanted symbol pass all count as a mistake - so it rewards restraint as much as timing.
</Tip>
