> For the complete documentation index, see [llms.txt](https://atiysus-organization.gitbook.io/aty-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://atiysus-organization.gitbook.io/aty-scripts/weapon-editor/guide/balance-lab.md).

# Balance lab

## Balance lab

The lab edits weapon statistics in game, versions every change, and can roll back. It needs the `lab` scope.

```
/gunsmithlab
```

### Three kinds of field

Not every statistic reaches the game the same way, and the lab says so under every slider. This is the only question that matters when you move one: *will this actually do anything, and when?*

| Class        | Badge      | How it works                                                          | When it takes effect       |
| ------------ | ---------- | --------------------------------------------------------------------- | -------------------------- |
| **Live**     | `live`     | Broadcast to clients as a damage modifier                             | Immediately                |
| **Scripted** | `scripted` | Applied by the script — recoil, sway, zone multipliers, fire rate cap | Immediately                |
| **Deploy**   | `deploy`   | Written into a weapon meta patch                                      | **After a server restart** |

Live and scripted fields change while players are connected. Deploy fields — clip size, weapon range, spread, reload time, falloff, force, penetration — do not, and no configuration makes them.

{% hint style="warning" %}
Scripted fields are enforced client-side. A modified client can bypass them. They are balance, not anti-cheat; the anti-cheat clamp is `Config.Authority`.
{% endhint %}

### Does it overwrite my weapon files?

No. Nothing in your game files, and nothing in any other resource, is edited.

Deploy fields are written to a **separate satellite resource**, `aty_gunsmith_data`, as a single `weapons.meta` registered with `WEAPONINFO_FILE_PATCH`. That file is generated by the deploy pipeline on every deploy — it is never hand-edited, and editing it yourself will be overwritten.

Because it is a patch resource, it does take precedence over the vanilla weapon stats it names. What it does **not** do is delete anything:

{% hint style="danger" %}
Despite the name, `WEAPONINFO_FILE_PATCH` does not merge — it **replaces** the blob it is associated with. Any weapon missing from the patch is deleted from the game's weapon store, and handing a player a deleted weapon crashes their client.

That caused six crashes during development. The generator now writes the **entire core blob** — 91 weapons and 53 ammo definitions — and replaces only the field you changed inside it. A guard in the deploy pipeline blocks any deploy whose record count has dropped, and it was tested against a deliberately sabotaged file.

This is why the patch resource is generated rather than edited. A hand-written partial `weapons.meta` is exactly the failure mode above.
{% endhint %}

Mk II weapons are deliberately skipped by the meta writer. Writing them breaks the 31 tint indices and loses the camo component names, so their tint and camo are preserved instead.

### Deploying

A deploy writes the patch, archives the version, and applies it. The countdown before it goes live is `Config.Balance.deployCountdown`, 10 seconds by default.

How it is applied is `Config.Balance.applyMode`:

| Mode                 | Behaviour                                                                                                                            |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `'safe'` *(default)* | Never restart while players are connected. The patch is written and the version archived; it takes effect on the server's next start |
| `'empty'`            | Restart only when the server is empty. Same behaviour, states the intent                                                             |
| `'force'`            | Restart under any condition                                                                                                          |

{% hint style="danger" %}
**`'force'` crashes connected players.** Restarting the data resource live makes clients crash while unmounting the weapon meta (`c0000005`, *"The game will be terminated"*). This is not a fault in this script — FiveM cannot hot-unmount a weapon meta `data_file`.

Use `'force'` only inside a closed maintenance window with nobody connected.
{% endhint %}

With the default mode, expect to see *"Weapon data saved — it will take effect on the next server start"*. That message is the system working correctly, not a failure.

### Versions and rollback

Every deploy is a row in `aty_gunsmith_balance` holding the full stat set, the diff against the previous version, the author and a note. The version note is what you will see in the rollback list, so write one.

Rolling back is picking an earlier version. `Config.Balance.autoRollback = true` does it for you if a deploy fails verification.

Deploys are rate-limited by `Config.RateLimit.deploysPerHour`, 3 by default.

### The linter

The lab warns rather than blocks:

| Check          | Default | Warns when                                            |
| -------------- | ------- | ----------------------------------------------------- |
| `ttkFloorMs`   | `220`   | Time-to-kill drops below this floor                   |
| `ttkClassBand` | `0.35`  | TTK deviates this far from the weapon's class average |

Class averages are computed from recorded shooting range sessions, so the linter gets more useful the more you measure.

### Checking it reached the game

If a change appears saved but nothing happened in game, the chain has three links and each one can be inspected:

```
/gunsmith:doctor      bridge, tables, data resource, current balance version
```

The lab's own field notes cover the common cases directly — a field that is not implemented, a field that is staged for restart, a weapon with no vanilla record, or meta writing being switched off entirely (`Config.Balance.metaEmitFields`).

### Webhooks

```lua
Config.Webhooks = { deploy = '', moderation = '', damageFlag = '' }
```

`deploy` receives every deploy and rollback. Worth setting on a server where more than one person has the lab scope.

### Exports

```lua
exports.aty_gunsmith:getWeaponStats(weaponName)
exports.aty_gunsmith:setWeaponStats(weaponName, patch, actor)
exports.aty_gunsmith:deploy(actor, note)
exports.aty_gunsmith:rollback(version, actor)
```

And the event:

```lua
AddEventHandler('aty_gunsmith:balanceDeployed', function(data)
    -- { version, diff, actor }
end)
```

### See also

* Shooting range — measuring what a change did
* Configuration — the damage clamp
* Commands
