Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
75a4ddd
Make Capacity groups canonical
remdui Aug 4, 2026
22849ba
Reduce Capacity config mutations
remdui Aug 4, 2026
723f75c
Derive Capacity groups from group members
remdui Aug 4, 2026
3ae8abc
Store Capacity group membership once
remdui Aug 4, 2026
431a7eb
Simplify Capacity command interface
remdui Aug 4, 2026
8b49fe8
Align Capacity defaults and messages
remdui Aug 4, 2026
5e48b4f
Test the simplified Capacity model
remdui Aug 4, 2026
dc06c65
Validate the simplified Capacity commands
remdui Aug 4, 2026
c6a2434
Document the simple Capacity interface
remdui Aug 4, 2026
1301814
Bump simplified Capacity version
remdui Aug 4, 2026
9f7ab8a
Align Capacity snapshot documentation
remdui Aug 4, 2026
dd423a0
Scope the Capacity YAML acceptance check
remdui Aug 4, 2026
a4fb568
Make Capacity YAML acceptance indentation-independent
remdui Aug 4, 2026
4d1ac10
Fix Capacity acceptance shell syntax
remdui Aug 4, 2026
545c958
Harden Capacity configuration validation
remdui Aug 4, 2026
ed06c43
Use one Capacity policy instance at runtime
remdui Aug 4, 2026
3a8051a
Make Capacity control operations shutdown-safe
remdui Aug 4, 2026
b2303b2
Reload all Capacity runtime settings consistently
remdui Aug 4, 2026
2b13d5a
Tighten the Capacity command contract
remdui Aug 4, 2026
999daa9
Validate Capacity manual-state persistence
remdui Aug 4, 2026
5b747db
Test Capacity against explicit validated policies
remdui Aug 4, 2026
fb5792f
Cover Capacity command edge cases
remdui Aug 4, 2026
466b9fa
Reject malformed Capacity YAML without coercion
remdui Aug 4, 2026
491b6ce
Cover strict Capacity configuration invariants
remdui Aug 4, 2026
cd42ba8
Document Capacity validation and reload semantics
remdui Aug 4, 2026
0624d31
Load Capacity policy from one YAML revision
remdui Aug 4, 2026
26e93bb
Initialize Capacity from one stored policy snapshot
remdui Aug 4, 2026
3ac9647
Reload Capacity policy atomically
remdui Aug 4, 2026
110e33c
Validate Capacity snapshot settings with the policy
remdui Aug 4, 2026
f841663
Preserve Capacity snapshot settings during live edits
remdui Aug 4, 2026
848778d
Test validated Capacity snapshot settings
remdui Aug 4, 2026
091c8d6
Keep Capacity policy construction source-compatible
remdui Aug 4, 2026
3a2d266
Drive Capacity snapshot publishing from validated policy
remdui Aug 4, 2026
c1dcb42
Fix Capacity command test runtime fixtures
remdui Aug 4, 2026
009f50c
Persist the complete validated Capacity policy
remdui Aug 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/capacity-placeholders.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Capacity Snapshot Feed

> ProxyFeatures Capacity 1.3.0 · versioned Redis latest-value feed for ServerFeatures placeholders
> ProxyFeatures Capacity 1.4.0 · versioned Redis latest-value feed for ServerFeatures placeholders

## Purpose

Expand Down
274 changes: 119 additions & 155 deletions docs/features/capacity.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

> Velocity · Feature ID `Capacity` · disabled by default · authoritative admission control

Capacity decides whether a proxy login or backend connection may start. It applies proxy,
gameplay, group, server, reserved-slot and server-state rules in memory and creates a short-lived
lease before Velocity starts the connection.
Capacity is the single authority for proxy login and backend admission. It combines numeric limits,
reserved slots, server states and Queue integration before Velocity starts a connection.

Paper's local player limit remains an emergency resource ceiling, not the normal network limit.
The feature is intentionally small operationally: administrators can inspect the current state,
change live slot limits, change per-server queueability, set temporary server states and reload the
YAML policy. Topology and safety tuning remain configuration-only.

## Storage and authority

Expand All @@ -16,91 +17,126 @@ Capacity has no database support and creates no tables or views. Its only persis
plugins/proxyfeatures/features/Capacity/config.yml
```

The YAML file is always authoritative:
The YAML file is authoritative:

- startup loads configuration and manual states directly from `config.yml`;
- `/capacity config ...` writes the changed configuration back to `config.yml`;
- `/capacity state ...` writes or removes a manual state under `manual_states`;
- `/capacity reload` reloads all Capacity settings and manual states from `config.yml`;
- Redis snapshots remain observational and do not store or authorize Capacity state.
- startup loads the complete policy and manual states from `config.yml`;
- slot and queueability commands save their result to `config.yml` before applying it;
- state commands save manual state claims under `manual_states`;
- `/capacity reload` atomically validates and applies topology, timings, integrations, messaging and
manual states from YAML;
- an invalid reload is rejected and the last valid in-memory policy remains active;
- Redis snapshots are observational and never authorize admissions.

There is no SystemData connection, ORM context, SQL migration, revision history, rollback, audit,
runtime table, metric table, database view or database fallback.
## Configuration model

## Requirements
An admission can be constrained by all applicable scopes:

- one Velocity proxy process as the Capacity authority;
- Queue loaded after Capacity when Queue is enabled;
- Redis messaging only when backend Capacity placeholders are wanted.

DataProvider and MySQL are not required by Capacity itself.
```text
proxy
AND gameplay
AND group
AND exact server
```

## Configuration structure
A capacity of `0` disables that numeric scope. Reserved slots remain inside the absolute capacity;
they never increase it. Negative values and `reserved_slots` values greater than `capacity` are
invalid rather than silently corrected.

The generated config contains the complete policy:
Groups add one shared constraint over a set of servers. The group owns its membership:

```yaml
enabled: false

global:
proxy:
capacity: 600
reserved_slots: 10
gameplay:
capacity: 500
groups:
survival:
capacity: 250
reserved_slots: 10
servers:
- survival
- survival1
- survival2
```

leases:
ttl_millis: 15000
login_ttl_millis: 20000
There is deliberately no `group` property below an individual server. A server may occur in at most
one `groups.<name>.servers` list. Duplicate membership, duplicate identifiers after normalization,
blank members and malformed lists are rejected during startup and reload.

reconciliation:
interval_seconds: 5
Exact-server policy is independent from group membership:

integrations:
maintenance_sync_interval_millis: 500
```yaml
servers:
survival:
capacity: 100
reserved_slots: 5
counts_toward_gameplay: true
queueable: true
initial_state: OPEN

queue:
capacity: 0
reserved_slots: 0
counts_toward_gameplay: false
queueable: false
initial_state: OPEN
```

queue:
on_full: true
A server can be listed only in a group, only in `servers`, or in both. Group-only servers inherit the
unknown-server exact policy while still counting against the group. Server-only entries have no
group constraint. Configured servers remain visible in Capacity snapshots and `/capacity info` even
while they are not currently registered or online in Velocity.

failure:
fail_open_on_internal_error: false
Configuration values are type-strict. Booleans must be YAML booleans, capacities and durations must
be integer values, and identifiers cannot contain whitespace, control characters or `|`.

restart_return:
use_reserved_slots: true
Topology and non-operational behavior are edited in YAML and applied with `/capacity reload`:

unknown_servers:
counts_toward_gameplay: true
queueable: false
capacity: 0
reserved_slots: 0
- group creation, deletion and membership;
- `counts_toward_gameplay` and `initial_state`;
- unknown-server policy;
- lease and reconciliation timings;
- Maintenance and Restart integration behavior;
- fail-open behavior and Redis snapshot channel, publisher and interval settings.

## Commands

groups: {}
servers: {}
manual_states: {}
Aliases:

```text
/capacity
/cap
```

Groups and servers are normal YAML maps. A command that adds, changes or deletes one rewrites those
maps in the same file. Default group/server maps are injected only when the complete section is
missing, so an intentionally deleted entry is not recreated on the next startup.
Inspection:

## Capacity scopes
```text
/capacity status
/capacity status group <group>
/capacity info <server>
```

An admission can be constrained by:
`status` shows the proxy and gameplay scopes. `status group` shows a shared group constraint.
`info` shows the exact-server scope, its group scope when applicable, queueability, gameplay
participation, configured initial state, effective state and any manual state claim.

Operational changes:

```text
proxy hard limit
AND gameplay-global limit
AND optional group limit
AND optional exact-server limit
/capacity slots proxy <capacity> [reserved]
/capacity slots gameplay <capacity> [reserved]
/capacity slots group <group> <capacity> [reserved]
/capacity slots server <server> <capacity> [reserved]
/capacity queueable <server> <true|false>
/capacity state <server> <open|draining|closed|offline|clear> [duration] [reason]
/capacity reload
```

A capacity of `0` disables only that numeric scope. Server states are independent of numeric
capacity: a server with no configured numeric limit is still blocked when its effective state is
not `OPEN`.
Command arity is strict: trailing or incomplete arguments are rejected instead of being ignored.
When `reserved` is omitted from a slots command, the current value is retained and clamped to the new
capacity. Lowering a limit never kicks players; it blocks new positive-scope admissions until usage
returns below the limit. Raising a limit or reloading a more permissive policy wakes affected queues
immediately.

Connected players, pending leases and restart restoration reservations are counted together.
Reserved slots stay inside the absolute capacity; they never increase it.
The command interface cannot create groups, move servers between groups, change timing internals,
change failure policy or mutate unknown-server behavior. Edit `config.yml` and reload for those
changes.

## Server states

Expand All @@ -114,28 +150,12 @@ The most restrictive owner wins. Owners include the configured initial state, th
Maintenance and Restart.

- `OPEN`: new connections are allowed when numeric capacity permits.
- `DRAINING`: new normal connections are blocked; players already on the server stay connected.
- `CLOSED`: new normal connections are blocked; players already on the server stay connected.
- `OFFLINE`: new normal connections are blocked; players already on the server stay connected.

Capacity is an admission controller and deliberately does not move or disconnect existing players.
Maintenance and Restart retain their controlled evacuation/reconnect workflows. A state-bypass
permission can enter restrictive states; existing prepared non-bypass leases are revoked when a
state becomes restrictive.

Manual state commands persist readable YAML such as:

```yaml
manual_states:
survival:
state: DRAINING
expires_at: '2026-08-04T12:00:00Z'
reason: Deploying update
actor: AdminName
```
- `DRAINING`: new normal connections are blocked; connected players remain.
- `CLOSED`: new normal connections are blocked; connected players remain.
- `OFFLINE`: new normal connections are blocked; connected players remain.

For a permanent state, `expires_at` is `permanent`. Expired entries are removed from the config when
Capacity loads them or when the expiry task runs.
Capacity is an admission controller. It does not move or disconnect existing players. Maintenance
and Restart retain ownership of their controlled evacuation and reconnect workflows.

Examples:

Expand All @@ -146,94 +166,38 @@ Examples:
/capacity state survival clear
```

Using `open` is equivalent to `clear`: it removes only the manual owner. Another owner or a
restrictive configured initial state may therefore keep the effective state restrictive.

## Commands

Aliases:

```text
/capacity
/cap
```

Inspection:

```text
/capacity status
/capacity status group <group>
/capacity info <server>
/capacity config show
```

`info` reports occupancy, pending leases, restoration reservations, limits, effective state, state
behavior and the current manual claim.

Configuration:

```text
/capacity config proxy <capacity> <reserved> [--confirm]
/capacity config gameplay <capacity> <reserved> [--confirm]
/capacity config group <group> limit <capacity> <reserved> [--confirm]
/capacity config group <group> add-server <server> [--confirm]
/capacity config group <group> remove-server <server> [--confirm]
/capacity config group <group> delete --confirm
/capacity config server <server> limit <capacity> <reserved> [--confirm]
/capacity config server <server> group <group|none> [--confirm]
/capacity config server <server> gameplay <true|false> [--confirm]
/capacity config server <server> queueable <true|false>
/capacity config server <server> initial-state <open|draining|closed|offline> --confirm
/capacity config server <server> delete --confirm
/capacity config unknown limit <capacity> <reserved> [--confirm]
/capacity config unknown gameplay <true|false>
/capacity config unknown queueable <true|false>
/capacity config option <option> <value> [--confirm]
/capacity reload
```

Supported options:

```text
lease-ttl
login-ttl
reconciliation
maintenance-sync
queue-on-full
fail-open
restart-reserved
```

Every successful mutation is saved to `config.yml` before the new in-memory policy is applied.
Risky changes require the dangerous permission and `--confirm`. Lowering a limit never kicks
players; it blocks positive-scope admissions until usage returns below the limit.
Using `open` is equivalent to `clear`: it removes only the manual owner. A restrictive initial state,
Maintenance claim or Restart claim may still keep the effective state restrictive. Clearing a server
without a manual state reports that nothing changed instead of claiming a state was removed.

## Fixed permissions

Permission nodes are code constants and are not configurable:
Permission nodes are constants and are not configurable:

| Permission | Purpose |
|---|---|
| `proxyfeatures.feature.capacity.reserved` | May consume reserved capacity. |
| `proxyfeatures.feature.capacity.bypass.capacity` | Bypasses numeric Capacity limits. |
| `proxyfeatures.feature.capacity.bypass.capacity` | Bypasses numeric limits. |
| `proxyfeatures.feature.capacity.bypass.state` | Bypasses restrictive server states. |
| `proxyfeatures.feature.capacity.admin` | Grants all Capacity administration. |
| `proxyfeatures.feature.capacity.admin.view` | Status, info and config display. |
| `proxyfeatures.feature.capacity.admin.view` | Status and info. |
| `proxyfeatures.feature.capacity.admin.state` | Set and clear manual server states. |
| `proxyfeatures.feature.capacity.admin.config` | Change and reload current configuration. |
| `proxyfeatures.feature.capacity.admin.dangerous` | Confirm risky configuration changes. |
| `proxyfeatures.feature.capacity.admin.config` | Change slots/queueability and reload YAML. |

The root command and tab completion expose only operations permitted to the command source.

## Queue, Maintenance and Restart

Queue never computes capacity independently. It may queue only ordinary `FULL` outcomes and every
advancement must acquire a Capacity lease again.
Queue never computes capacity independently. It may queue only ordinary `FULL` outcomes, only when
the exact target is queueable, and every advancement must acquire a Capacity lease again.

Maintenance contributes a `CLOSED` claim. Restart contributes a `DRAINING` claim and restoration
reservations. Opening a manual claim cannot override either feature because the most restrictive
owner wins.

## Redis snapshot feed

Capacity publishes its current in-memory snapshot to the configured Redis channel for
ServerFeatures placeholders. This feed is observational and is not used to authorize admissions.
A failed publication does not affect Capacity decisions.
Capacity publishes its current in-memory snapshot for ServerFeatures placeholders. Publication is
observational only. Redis failure does not alter admission decisions. A reload reconfigures the
publisher when its channel, publisher ID or interval changes and keeps the existing publisher when
those values are unchanged.
Loading