Dated source reference. Current release status: handoff state. Full original relative paths are in the developer archive.
App-owned match requests
Current extension: Match controls51 completes the three planned additions: game-clock rotation intent with native entry observations, original native timeout calls and connected-input/team assignment.510 Windows API tests/84 schemas and focused live52w/52za/52x pass; multi-controller physical input remains unverified because no gamepads are available. This closes the planned match-control implementation, with that hardware verification outstanding. Presentation, broad modding/rule semantics and public packaging remain separate. The earlier evidence/version paragraphs below remain their recorded checkpoints; consult the new ledger for current implementation and exact limits.
Current private source: development50, HTTPv1/snapshotABI25. Rules require ABI18, startup presets ABI19, startup camera ABI20, primary participation ABI21, per-team coaching ABI22 and startup defensive presets ABI25. Public preview ZIPs stay unchanged. Use a matching complete hook/broker on the signed-in Windows desktop. External flow records native substitutions and finished-session handling; expansion history retains earlier evidence.
An independent app can create one request document, launch the broker once, and observe its receipt while the native loading cover stays visible. The broker executes the documented public HTTP SDK; no app database is built into the adapter and no menu input is generated. The existing original Windows x86 game remains the simulation.
python -m openhoops_api --launch-game "C:\Games\OpenHoops\nba2k14.exe" --state "C:\MyDynasty\game-state" --match-request "C:\MyDynasty\request.json" --roster-tools "C:\OpenHoops\roster-runtime"
--roster-tools is needed for requested file edits/lineup preparation; a request that supplies an already prepared ROS does not need it. The optional component reuses the Roster Studio writer and a factual runtime catalog. It does not execute RED MC/Sigil or bundle their programs/help. tools/build_roster_runtime.py assembles an internal component on Windows from a hash-pinned factual catalog; it is not a public package publisher.
--match-request replaces --match-config. Optional request loading_screen replaces --loading-screen; supplying both branding sources refuses. All relative roster/image paths resolve beside the request document. Frozen configuration and asset copies live in private broker state; original INIs, ROS files and app images are preserved.
Request fields
| Field | Meaning |
|---|---|
format, version |
openhoops.match_request, integer1 |
request_id |
Single-use portable identifier,1–96 letters/digits/underscore/hyphen; starts with a letter/digit. Windows device names are refused. |
client_id |
App-owned API client identity, using the normal identifier syntax |
roster |
Exactly {path, sha256} for a standalone ROS, or {id} for an already admitted roster:<sha256> in this state directory |
launch_configuration |
Version1 original/college rules, optional startup settings/camera/per-team coaching and primary participation; applied before this child is revealed |
preparation |
Object of the13 documented preparation IDs. Omitted entries retain fresh loaded/current defaults; supply all applicable choices for repeatability. |
identity |
Version1 app-owned identity plan, with match/team IDs, expected player slot/name/ID entries, ruleset metadata and optional finite match metadata |
roster_edits |
Optional list of up to512 {table,row,field,value} edits, each cell once; creates a new immutable admitted ROS |
roster_preparation |
Optional home/away team rows, complete assigned-player order and minute preferences. Prepared/selected team rows must agree; missing team selections are filled from these rows. |
loading_screen |
Optional version1 title/subtitle/colors/local logo/background; same branding contract |
For example, an app that already has its preparation values and identity plan can assemble the document without a first-party launcher:
import hashlib, json
from pathlib import Path
roster = Path("Prepared.ROS").resolve()
request = {
"format": "openhoops.match_request", "version": 1,
"request_id": "season2026-game001", "client_id": "my-dynasty",
"roster": {"path": str(roster),
"sha256": hashlib.sha256(roster.read_bytes()).hexdigest()},
"launch_configuration": {
"version": 1,
"rules": {"profile": "college", "half_length_minutes": 10,
"media_timeouts": 4, "overtime_policy": "scaled"},
"participation": {"mode": "primary_home"},
"settings": {
"options": {"commentary_volume": 0, "action_replays": 0},
"sliders": {"user": {"3pt_success": 52},
"cpu": {"3pt_success": 50}},
"controllers": [{"player_slot": 0,
"values": {"defensive_assist_strength": 50}}],
"camera": {"id": "2k", "values": {"zoom": 4, "height": 9}},
"coaching": {"home": {"substitutions": 0, "substitution_method": 1},
"away": {"substitutions": 0, "substitution_method": 1}}
}
},
"preparation": json.loads(Path("choices.json").read_text()),
"identity": json.loads(Path("identities.json").read_text()),
"loading_screen": {"title": "My Dynasty", "subtitle": "Championship"}
}
with Path("request.json").open("x", encoding="utf-8") as output:
json.dump(request, output, indent=2)
The identity plan must describe the expected final roster after any edits/order changes. Physical ROS rows, native match slots and app-owned identities are separate. Every instantiated player must match its planned slot/name before binding. Extra plan entries that were not instantiated are reported separately. The905-team fixture stores15 assigned players/team but instantiates12/team; the helper binds24 and reports six other entries. No inactive/injury status or statistics are invented for them. This interface does not enlarge that active roster.
Rules, presets and cameras
Original rules use {profile:"original"} and the preparation quarter length1–12. College rules use half length1–20, fixed TV scheduling with media0/3/4, and the agreed scaled OT policy:1–4-minute halves →60-second OT,5–8→120,9–12→180,13–16→240,17–20→300. A10-minute half uses under8/under4 marks. Generic quarter length is not the authority for college half duration. The per-child contract overrides the exe-local college INI without editing it. GET /v1/launch compares requested rules with the adapter declaration; active snapshots separately expose actual game parameters.
Startup settings accept16 additional gameplay/presentation options,59 sliders on each User/CPU side and13 preferences across ten stored controller slots (264 values). ABI22 additionally accepts settings.coaching grouped by home/away: nine settings each, raising the batch maximum to282. ABI25 additionally accepts settings.defense by defending side/opponent, up to144 values, total426; see the external-flow guide for canonical keys, startup eligibility, live substitutions and durable result completion. See native coaching for choices, readback and current limits. Original Rotation mode and minute preferences do not guarantee an exact substitution schedule. Difficulty, game speed, fatigue and injuries remain preparation fields. A base Game Style is applied before slider overrides, which can make the native profile Custom. Runtime changes do not implement disk persistence. Controller preferences do not assign physical devices or team sides, and do not make a match CPU-versus-CPU.
Optional settings.camera uses a supported exhibition ID and only that camera's configurable controls. The native path retains Player1 input ownership, full initial clock, valid pre-play phase4, camera-menu exclusion and original activation/descriptor guards. Preset and camera readback must complete before reveal. GET /v1/launch/settings exposes catalogs, request and separate preset/camera status. Camera eligibility is not rendering certification of every type/resolution; see the current ledger.
Receipts and lifetime
GET /v1/launch/request reads the current request receipt. Add ?request_id=<id> to read a named receipt, including after an attached broker restarts with the same private state. Private files are under launch-requests/<id>/: frozen request/configuration and atomic status.json.
States are accepted, preparing, starting, started, failed. started:true confirms the native Start receipt and subsequent requested identity binding; it does not mean the game finished. started:null preserves an unknown Start outcome. Inspect saved command IDs through /v1/commands/<id> before deciding what to do. Request IDs are never automatically replayed; reuse refuses before creating another child. File-authoring outputs and earlier successful commands can remain after a later failure.
The final request receipt remains a historical launch receipt after cancellation. Use /v1/lifecycle, the current snapshot and managed match status for present game state. Journaled session.stop can stop only the original child owned by this broker. cancelled abandons without a result; finished requires an already finalized reconciled result. The broker stays available for durable results/receipts/independent consumer acknowledgments after the game exits. Normal broker shutdown leaves the child open.
Live statistics and final delivery retain their existing verification boundaries. Early pre-play binding can make started_mid_match:false; whole-match event completeness remains false. Legal possession/live/dead-ball authority, arbitrary rule callbacks, physical device assignment/multiple humans, safe asset hot-swap and custom in-game presentation remain separate unfinished capabilities. Existing rare-rule/full-game deferrals remain in force.
Primary participation and roster export
Optional launch_configuration.participation is {"mode":"primary_home"}, primary_away or cpu_vs_cpu. It addresses the existing primary logical input owner, slot0; physical USB/Bluetooth device choice, key remapping and multiple-human certification remain separate. Omitting it retains earlier launch behavior. For repeatability, apps should specify their intended mode. The hook must export OpenHoopsParticipationVersion=1 and publish ABI21. GET /v1/launch and snapshots expose pending/attempted/failed, native control commit and ready status. Original side/control handlers commit both current/saved roles and input records; readback must pass before reveal. Status records startup verification, not continuous participant authority. Home and away pass exact-native live checks; away has rendered marker evidence. Current combination checks are in the expansion ledger.
API-authored roster IDs can be saved through Client.download_roster(roster_id, new_path) or authenticated GET /v1/rosters/<sha256>/content. The endpoint returns exact verified ROS bytes, Content-Length, SHA ETag and a safe filename. The SDK independently checks format/size/hash and atomically creates a new .ros file; existing destinations are refused. No local path/query or native game load is inferred. Export does not require the optional writer if the roster is already registered.