> For the complete documentation index, see [llms.txt](https://tyrix.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://tyrix.gitbook.io/docs/script-resources/tyrix-droply/anti-abuse.md).

# Anti Abuse

Droply assumes the client is hostile. Every price, permission, position, and payment is decided on the server.

## Server authority

* **Prices are never trusted from the client.** The cart is re-priced against the live menu on every order; the client's own numbers are discarded.
* **Roles are re-resolved on every event**, regardless of what the UI allowed.
* **The manager subset is enforced by overwriting** identity and strategy fields with the stored row before validation — a forged manager payload cannot escape its subset. See [Roles](/docs/script-resources/tyrix-droply/roles.md#how-the-manager-subset-is-enforced).
* **Menu writes are validated against the station projection** — a boss cannot price an item their assigned stations cannot produce.
* **Drop-off pins are bounds-checked** (±5000 X/Y, −200 to 1000 Z).
* **The NUI bridge is allow-listed.** `client/app.lua` forwards only a fixed set of known event names to the server; anything else is rejected outright.

## Money safety

Funds are **verified at order time and captured only at handover**. An expired, cancelled, or abandoned order never charges anybody, so there is no refund path that can be exploited.

At handover, items move before money and every failure rolls back. Payment is charged **last**, so a payment failure cannot leave a customer holding free goods. See [Order Lifecycle → Money](/docs/script-resources/tyrix-droply/order-lifecycle.md#money).

## Distance enforcement

The handover distance is measured against the **customer's live ped**, not the recorded drop-off coordinates — a driver cannot complete a delivery to a customer who has walked away. The server limit is `handoverDistance + 2.0` m, slightly above the client's interaction radius so ordinary latency does not cause false rejections.

Violations are logged to the `exploit` channel with the measured distance.

## Tracking abuse

Position reports are validated on every tick:

* The caller must be the **assigned driver** of that order.
* The order must genuinely be `enroute`.
* Coordinates are clamped to world bounds, defeating teleport spoofing.
* Reports are **throttled** per driver and **coalesced** below `relayCoalesceMeters`, so a client that over-fires cannot flood the customer.

## Rate limiting

Every net event is cooldown-gated per player — typically 500 ms on reads and 1000 ms on writes. Cooldown state is cleared on disconnect.

## One active order per customer

With `oneActiveOrderPerCustomer` enabled, a customer may hold a single live order. The gate **self-heals**: if the recorded slot points at an order that is missing or already terminal, it is cleared rather than blocking, so a memory/database desync can never permanently lock a customer out of Droply.

The driver-side concurrency cap prunes dead entries the same way before enforcing `maxConcurrentPerWorker`.

## Cancellation fees

Charged when a customer cancels an order a driver has already started **preparing**. Cancelling while still `pending` — no driver assigned, no work done — is always free.

```lua
Config.Droply.cancellation = {
    enabled              = true,
    percent              = 0.25,
    minFee               = 50,
    recipient            = 'business',   -- 'business' | 'worker' | 'split'
    workerShare          = 0.5,
    requireFundsToCancel = false,
}
```

The fee is `max(minFee, floor(subtotal × percent))`. When `requireFundsToCancel` is `false`, a broke customer still gets to cancel and the waiver is logged; when `true`, the cancel is rejected and the order stays `preparing`.

Full breakdown in [Configuration → Cancellation fees](/docs/script-resources/tyrix-droply/configuration.md#cancellation-fees).

## Customer blocklist

Bosses and managers can block specific customers from ordering at their business, up to `maxBlockedCustomers` (default 50).

The blocklist picker lists recent customers with their **order count and cancel count** side by side, so a business can identify serial cancellers without guesswork.

Blocked customers see the store greyed out and marked as blocked; the block is also re-checked server-side at `placeOrder`, so it holds even against a stale or modded client.

## Exploit logging

Rejected actions are written to the `exploit` Discord channel with the offending player's identifiers. Logged events include:

* Unauthorised storefront saves, manager edits, blocklist edits, and open/close toggles.
* Non-admin calls to the station-assignment endpoints.
* Invalid delivery fees, commissions, minimum grades, and off-menu item prices.
* Handover distance violations.

Set up a dedicated `exploit` webhook to keep these separate from routine traffic. See [Discord Logging](/docs/script-resources/tyrix-droply/discord-logging.md).

## Restart safety

Unfinished orders cannot be resumed after a restart, so they are rehydrated and cleanly cancelled on start. This releases promo slots, frees every customer's active-order slot, updates the database, and notifies connected players — so a crash never leaves Droply bricked. See [Order Lifecycle → Restart reconciliation](/docs/script-resources/tyrix-droply/order-lifecycle.md#restart-reconciliation).

## Webhook safety

`config_sv.lua` is a server-only script. Never reference `Config_SV` from client code — webhook URLs must never reach a client.
