Native rule SDK — experimental C ABI1 / transport2 candidate
Team build 2026.10.20-team1 ships the accepted native70d hook and API70e broker. See current verification for exact original/college game evidence and limits. This C ABI remains experimental; main snapshot ABI26 / event ABI4 are unchanged.
OpenHoops supports independent applications and native modules. A client supplies an ordinary match request through the same API as other clients. A module registers one identified extension through the plain C interface in openhoops_rules.h. Game pointers and executable-specific addresses are private to the adapter.
Supported surfaces in this candidate
| Surface | Contract |
|---|---|
| Full shot clock | Configuration callback before the first active-clock copy;1–120 seconds |
| Minimum shot clock | Configuration callback;1–120, no greater than the full amount; college frontcourt minimum is distinct from the optional rim amount; without a separate rim claim the legacy shared value remains |
| Period and overtime | Frozen whole-minute durations: regulation60–1200seconds, overtime60–300seconds; native clocks/profile elapsed time share the selected values |
| Backcourt | Frozen1–120seconds, native selectors0/1; special60-second selector2 stays native and cannot claim an override |
| College rim reset | Frozen1–120seconds, no greater than full; separate from the frontcourt minimum |
| College bonus | One owner claims both one-and-one/double thresholds; positive integers or INT32_MAX disabled sentinel, one threshold no greater than double unless disabled |
| FT count | Native-transaction decision before initial award publication,1–3; original conditional/retained award class remains |
| Two-/three-point classification | Native-transaction boolean at eligible field-goal release/shooting-foul classification; score/FT consumers share the native flag |
| Personal foul threshold | Configuration callback selecting5 or6; original mode exceptions remain in effect |
| Rule permissions | Synchronous decisions for advertised permission IDs, cached once per native simulation step |
| Ball state | Copied, profile-specific state with independent validity and evidence for each field family |
| Observations | Committed counter/phase records, ball revisions, decisions and lifecycle notifications |
Read the host masks during initialization. Enum presence does not make a decision available. Original NBA bonus, independently mutable late-bonus policy, arbitrary scoring values and live reconfiguration remain unavailable. College remains the built-in policy owner; it exposes the coherent threshold bundle and separate rim parameter, while retaining its award/continuation/counter behavior. College owns defensive-three-second and back-to-basket permissions and does not offer them to another owner. These candidate capabilities have focused headless scope, not complete live or rulebook certification.
The original foul-setting domain includes disabled/mode values0–4. A callback may keep its original baseline, but an override must select5 or6. Configured constants stay applied if their supplying module later fails. A detected change to a selected frozen constant suspends the requested match; the adapter does not rewrite accumulated rule state during live play.
Build and select a module
The independent C example supplies18/9 clock amounts and disables the shot-clock permission during phase10. It demonstrates custom mechanics and does not claim the unchanged college profile. Build build.cmd in an x86 MSVC developer prompt. It uses /MT and an explicit export definition; no additional module runtime is needed.
Add an entry to launch_configuration.extensions before creating the owned child:
{
"id": "org.openhoops.example.rules",
"path": "C:\\MyApp\\mods\\example.dll",
"sha256": "REPLACE_WITH_THE_DLL_SHA256",
"configuration": {},
"required": true
}
The broker validates and freezes the DLL/configuration separately from the original game files. The adapter checks their hashes again and holds read-only file handles for the process lifetime. Build modules as self-contained DLLs with system dependencies; this candidate stages the selected DLL, not an arbitrary dependency/assets tree. A DLL is trusted native code, not a sandbox. Returning SEH faults are contained; hangs, arbitrary memory writes or corrupting the process cannot be safely preempted.
Initialization runs explicitly outside DllMain. Registration is transactional, one DLL registers one extension, and each mutable decision/permission has one owner. Observers may coexist. Conflicts and admission/initialization failures refuse startup. required:false permits the documented runtime fallback; it does not bypass pre-start validation. Registration closes before play and modules remain loaded until process exit.
Callback behavior
Callbacks run on the established game thread. Do not call the game, retain supplied pointers, perform network/file/UI work, wait, allocate substantial resources or start asynchronous adjudication. Copy any configuration needed after initialization into your own bounded storage. Callback context and output are copied and validated. The host preserves native integer registers, flags and floating-point state and provides a defined callback floating-point environment.
Configuration callbacks execute before play. Permission callbacks execute at the original permission-query boundary during the complete native simulation dispatch. Each permission has one stable answer per evaluation epoch; later consumers in that epoch receive the cached answer. UI/render queries can read an existing answer but cannot initiate a runtime callback. Other-thread/pre-match queries retain the original answer and are counted separately. If the native boundary stack discards an abandoned simulation step, the adapter invalidates continuity and clears that step’s cache; an optional policy can resume with a fresh evaluation epoch. Required policies suspend under the normal fault contract. This is a global permission contract, not a per-player foul adjudication callback.
OH_SCOPE_NATIVE_TRANSACTION uses evaluation_epoch as the adapter's native transaction identity, separate from a simulation-step epoch. FT-count decisions occur after the native candidate's shooter/kind/count stores and before the original publisher runs. The copied context names shooter and known foul committer plus an explicit transaction reason. It does not mislabel the previous FT award as the new committed award. Count selection changes both total and remaining together; conditional one-and-one behavior and retained-possession administration remain with the original policy. Unsupported/unmapped candidates degrade or suspend according to requiredness rather than claiming application.
Classification runs only inside an eligible release or shooting-foul transaction, not generic AI/render geometry queries or ordinary pass registration. The result is the native two-/three-point boolean, not an arbitrary point value. A selected shooting-foul answer is retained for the connected continuous-motion release. KEEP permits the native answer at each connected consumer; it does not change the native value. Accepted control, settlement, contact, match reset and continuity gaps end this pending classification. Decisions and application/fault receipts carry their scope, reason, actor identities and transaction correlation.
Current field-goal release decisions run immediately before the existing official-ball detach-to-flight operation, using the matching attached/registered shooter and the native flag supplied by animation registration as baseline. Repeated preparation registrations can precede a canceled shot or pass and invoke no policy. A new attempt's copied decision context does not claim the previous attempt identity. Application is checked against the native flight state/shooter/flag before publishing the committed attempt. Free-throw release retains the native one-point path. Physical-state writers outside the recovered boundaries remain a coverage limit; a numeric sample alone does not make a transition authoritative.
Use the context validity masks before consuming clocks, counters, players, positions or actors. Position coordinates apply to on-court players when OH_CONTEXT_POSITIONS is valid. Ball fields additionally have valid_fields and inapplicable_fields; unknown values are not zero/false observations. Return KEEP when your policy cannot decide from the available context. Native menu/settings observations show the baseline settings; runtime permission receipts identify the extension's effective answer.
A valid over-budget answer applies to its current decision and remains cached for that epoch. The host reports the timing fault and disables future callbacks. Exceptions, invalid results, changed context, reentry or changed FP controls reject the current answer and use baseline. Required runtime failure finishes the native boundary, freezes subsequent simulation, persists failure and closes only the broker-owned game. It must not produce an ordinary successful requested-rules result. Optional failure continues under explicit degraded provenance. Observer-only failure is reported separately from changed basketball policy; successfully frozen configuration remains in effect.
State and event meanings
Physical holder, presentation phase, player control, team control, NBA possession, entitlement and legal live status are separate surfaces. The interpreter consumes accepted native transitions. Sampling can detect an uncovered change and invalidate fields; it cannot turn a physical attachment or phase into a positive legal-state claim.
Each state family carries its own evidence sequence and reason. Pending attempts survive a whistle or expired clock until their native outcome is established. Ordinary passes do not create shot attempts. Restart disposal, release, legal control, throw-in completion and AP consumption remain distinct. FT award creation is captured from both original award helpers before their caller changes phase. It does not establish dead-ball status by itself, and unrelated physical movement does not erase award identity. FT awards retain their identity through completed attempts. Ordinal becomes known at connected disposal; progress clears a stale ordinal, and remaining zero does not erase the settled award receipt. Only a restart nested in that native FT settlement supplies following-restart/retained-team evidence. Accepted live control closes FT administration, preventing a later unrelated inbound from attaching to it. Uncovered transitions still leave the affected subfields unknown. NCAA alive and separate NBA-possession terminology are inapplicable. These are documented engine/profile interpretations, not full rulebook certification or exhaustive anatomical/boundary verification.
Event stages distinguish record storage, a committed native write and a completed transaction. An adjudication-record observation alone does not establish completion of every caller's adjudication. Unresolved actor roles and record kinds stay unresolved. Stat observations correspond to the existing engine-counter records; their event_watermark identifies that source stream. Do not add both streams as separate statistical credits. Existing scoped retraction provenance remains available through the ordinary engine-event API.
The HTTP reader names ten connected record kinds, including kicked ball and double dribble. Those two handlers have captured-instruction guard/attribution checks; upstream detection is not independently certified. Actor roles are supplied only for the connected handler paths: violating player for those two violations, and committer/fouled player for reach-in, blocking and shooting fouls. Out-of-bounds attribution stays unresolved because native logic can substitute the recorded actor. A record name, permission ID, foul-credit subtype and referee signal are separate domains.
Native observers can use the header's OHRuleKnownRecordNameV1 and OHRuleKnownRecordActorRoleV1 helpers for the same vocabulary without making an HTTP call. They return null for an unresolved name/role. These are local string helpers; shared observations contain no game pointers.
HTTP snapshots sample the main and rule mappings independently. Their run/match identities and semantic revisions are checked, but the merged positions, counters and legal state do not form one atomic game transaction. Use copied synchronous callback context or the committed rule event revision when that distinction affects a policy decision.
Public data and results
GET /v1/ball-state: known/unknown/inapplicable values, match epoch, revisions and evidence.GET /v1/rules: masks, module identities/hashes, frozen values and integrity/fault status.GET /v1/snapshot: additivelegal_ballandrule_runtimefields. Existing physical-holder fields retain their meaning.- The ordinary event/SSE stream carries
rules.ball_state,rules.decision,rules.lifecycleand correlation-onlyrules.observation_contextevents. Cursors and capture gaps are durable. - Final results freeze module/configuration hashes, requiredness, frozen values, callback faults and transport gaps. Extension-modified profiles do not inherit the stock college profile's verification claim. Historical results retain their original contents.
Result summaries retain the first128 fault receipts for their match epoch and an explicit omitted count. Module call/fault counters and transport-gap history cover the process lifetime; the latter is also bounded to128 entries with an omitted count. The full durable event log retains the remaining observations. A disabled owner's later baseline answers are fallback decisions, not repeated occurrences of the original callback fault. Managed startup refusals are also returned to the owning request through a child-specific receipt; the game does not wait behind an unattended error modal.
Independent consumers still receive and acknowledge the same immutable results. Selecting a module does not require a first-party application, a new GUI or presentation assets.