Dated source reference. Current release status: handoff state. Full original relative paths are in the developer archive.
Dynamic crowd attendance
The crowd-enabled hook and API let any external application choose native audience fullness for each launched game and change it during play. Arena models need no attendance variants. The control uses the game's stable seat thresholds, so increasing fullness adds the same audience back in place.
This is the private 0.2.0.dev57 API / Crowd.1 hook feature. HTTP remains v1 and the main native snapshot remains ABI26. Use the matching crowd-enabled hook with the supported Windows executable. The acceptance record identifies the tested source and binaries.
The Windows bundle contains the matching hook, API, embedded runtime, SDKs, guarded installer and rollback helper.
Choose fullness before tipoff
Add crowd to the existing match request's launch_configuration:
{
"version": 1,
"rules": {"profile": "original"},
"crowd": {"fill": 35}
}
fill accepts a finite percentage from 0 to 100: 0 empties the native audience, 35 means 35% fullness, 50 is a medium attendance target, and 100 is full. Fractional percentages such as 35.5 are supported. The API divides by 100 internally; applications always send and receive percentages. The native distribution is weighted, so the percentage setting is not an exact occupied-seat count. The same setting works with "profile": "college". Omit crowd to use the game's attendance. An explicit 0 is a valid request.
Managed launch checks for the crowd hook before creating the child and verifies the requested fullness at the opening boundary before revealing play. Read /v1/launch for configuration verification and /v1/crowd or snapshot.crowd for current native readback. state.startup.fill records the original launch declaration independently of later live changes.
Change fullness during play
Use the existing bearer authentication and exclusive control lease. The Python SDK prepares a request with the current game, period, crowd scene, native epoch, revision and previous fullness:
from openhoops_api.client import Client
client = Client(token_file)
lease = client.request('/v1/control/lease', {
'client_id': 'my-dynasty', 'ttl_seconds': 30
})
body = client.crowd_command(
25, client_id='my-dynasty', lease_token=lease['lease_token'],
command_id='game-123-attendance-1'
)
# Commit body to your application's private durable command store first.
receipt = client.request('/v1/commands', body)
state = client.crowd()
client.request('/v1/control/release', {'lease_token': lease['lease_token']})
Pass None to prepare crowd.reset, which releases the live override and reads back native attendance. crowd.set takes value; reset has no value. A new match restores its per-launch declaration, or native attendance if no declaration was supplied. A live command cannot carry over to another crowd scene.
The TypeScript SDK provides the same flow:
const lease = await client.request('POST /v1/control/lease', {
body: {client_id: 'my-dynasty', ttl_seconds: 30}
});
const body = await client.crowdCommand(25, {
client_id: 'my-dynasty', lease_token: lease.lease_token,
command_id: 'game-123-attendance-1'
});
// Commit body to your application's private durable command store first.
const receipt = await client.request('POST /v1/commands', {body});
const state = await client.crowd();
await client.request('POST /v1/control/release', {
body: {lease_token: lease.lease_token}
});
Pass null for reset. Other languages can use the same authenticated HTTP operations described by OpenAPI. The runnable Python example retains the exact command in a private file before submission. From the bundle root:
.\runtime\python.exe .\api\examples\crowd_control.py --token-file "D:\Private\CrowdAPI\token" --fill 25 --command-file "D:\Private\attendance-1.json"
Use --reset with a new command file to restore native attendance.
Keep a command's complete body and ID if a response is lost. Query /v1/commands/<command_id> or resubmit that same body to retrieve its durable receipt. An indeterminate result requires inspection; the SDK never retries a mutation automatically. Stale scenes, changed fullness, replay, finished games and missing leases are refused.
Readback and scope
GET /v1/crowd returns health, run_id, a capability catalog, and nullable native state. Require health == "ready" before creating a live command. catalog.units and state.units are "percent"; both declare public contract version: 2. All fullness fields (fill, target, native_fill, startup.fill) and command receipt observed_value use 0–100. expected_value must be copied from public fill. Readback percentages preserve native Float32 precision while displaying ordinary targets such as 35 without binary rounding noise. Scene/revision/epoch, startup verification, authored row/anchor totals, eligible audience anchors and adapter errors remain available.
This changes the earlier private dev56 fraction contract: update callers to send 35 instead of 0.35 for 35%. A value of 0.35 now means 0.35%; there is no automatic scale detection. The percentage verification covers API conversion and SDKs. The unchanged native hook retains its original game acceptance.
Fullness controls native 3D spectators and the native distant audience path. The game's 255 attendance thresholds quantize the distribution. Eligible anchors are a rendering diagnostic, not a venue's real attendance or licensed capacity; two arenas can have different anchor densities. Bench staff, players, photographers, crowd audio and spectators baked into a custom static mesh have their own systems. Section selection and crowd noise are separate controls.
The hook owns only the native fullness getter/setter and a separate versioned mapping. Its game-thread mailbox applies commands with native preconditions and scalar readback. Arena geometry, seating layouts and presentation ownership remain independent.