OPENHOOPS / 2K14Final team archive · November 9, 2026Reference libraryChecksums

Dated source reference. Current release status: handoff state. Full original relative paths are in the developer archive.

Live event meaning and reliability — OpenHoops

Storage cleanup72: bulky raw captures are now verified lossless archives, with an index and explicit restore helper. Historical capture contents and verification scope are unchanged.

Earlier API checkpoint (October15,2026): 0.2.0.dev36, HTTP v1, snapshot ABI14/event ABI4, college profile4/court definition3. Use the current contract, verification ledger and remaining scope. Research milestones below retain their original versions, results and uncertainties; their former next-step statements are historical. Latest natural college OT/export and18 both-end release-contact cases passed; rare live exceptions remain unverified. No public release or extra live smoke is required for this documentation pass.

Current scope (October13,2026): Windows-first deep editing and a public extension API; Mac/Linux compatibility follows later. Earlier native-port goals and next-step proposals in this evidence history are superseded. Technical observations and verification limits remain as recorded.

The event layer needs more than memory addresses. This investigation establishes several credit/retraction contracts, separates physical ball attachment from team context and game phase, and captures regulation completion plus rematch. It also finds concrete failures in naive polling: delayed team totals, pre-commit callbacks, notifications without a counter change, reused player pointers, a halftime-dependent possession byte and mixed old/new match state. Legal live/dead ball, all foul/violation names, and a complete ordered event journal remain unfinished. No external event API was built.

User priority: nail down meanings and reliability before exposing an API. This report supersedes the event assumptions and next-step recommendation in LIVE-DATA.md. Coverage and unresolved tests lists the remaining work, without treating candidate addresses as supported features.

Evidence and scope

Supported supplied ISO executable, x86 image base 0x400000, SHA-256 7cb5fdaa30586558a3146dcf9023880b1cceeaabfe0f92f967f461c4644f3704. Addresses below are virtual addresses in this build, not portable ABI or offsets valid for other releases. The on-disk protected text differs from initialized runtime text; analysis uses the archived initialized analysis/offline-bootstrap/text.bin at 0x401000 and rdata.bin at 0xEC2000. Exact source hashes and generated artifact hashes are in the manifest.

Reference: original game running on the Windows 11 Dell, PID 13108, Spurs away / Heat home, CPU versus CPU, six-minute quarters, external-mode/portable-saves v0.2. Adapter SHA-256 003a0c821b81f4abd0851b276fa7f5854de849c07ddb1723d9bd3259a0d2daf4. This pass used read-only process memory, UI controls, static disassembly, read-only Ghidra overlay projects and local captured-x86 tests. No new injected DLL, installed community mod, game-memory write or debugger attachment. This is Windows reference research, not native-engine or cross-platform validation.

Five new five-minute recordings contain 13,530 samples. They cover late Q2, Q3, Q4 and regulation completion, with gaps, pauses and overlapping game-04/game-05 recordings. They are not 25 minutes of continuous unique gameplay. Additional final-screen, rematch and paused-rematch captures contain 10, 1,085 and 46 samples. Requested sampling interval was 100 ms; actual mean intervals were 110.82–111.00 ms, maximum 122 ms, recorded memory-read duration at most 4 ms. Multiple blocks are read sequentially: neither a sample nor adjacent samples promise an atomic simulation frame.

Evidence levels are deliberately separate:

  • Live/UI: visible result and captured state; can establish a counter value but not every cause.
  • Static: exact runtime assembly and callers; Ghidra pseudocode is a navigation aid, not authoritative calling-convention recovery.
  • Bounded execution: 959 captured-x86 cases in Unicorn 2.1.4, synthetic object memory and explicitly mocked dependencies. Establishes listed local contracts, not 959 live plays or complete engine correctness.
  • Observation checks: 89 checks covering capture receipts, final UI, period scoring, rematch and paused state. Not a measure of event-stream completeness.

Primary attribution is the supplied NBA 2K14 runtime. Community leads (Elan/Sigil/GreenMeter/Blaze) remain attributed in the previous survey and its manifest; no new community source is represented as independent proof here. Discovery disassemblies can include padding, table data and heuristic function starts. The originally guessed 0x8BE180 block routine was wrong; 0x8BE240 is the relevant entry. Failed Ghidra output for 0x880CB0 is retained; exact assembly and bounded execution support that gate.

The basketball phase controller is [0x146B734], observed object 0x146B940, vtable 0xF406DC. It has two twelve-byte records at +0x14 and +0x20: state ID, float elapsed time, transition stamp. +0x2C selects current, +0x30 previous, and +0x34 indicates initialized history. These are only two records, not a retained event journal.

0x412120 takes manager in ECX and requested ID in EDX. It calls vtable +0x5C before 0x411F90 commits the new record. A callback therefore sees the old active state. The history writer toggles records, resets elapsed time and stamps the outgoing record from 0x146B628. Re-entering the same ID still performs a transition/reset; suppressing equal IDs would discard meaningful calls. This generic routine serves other objects too; tracing it requires filtering the actual manager. Some direct/inlined writers may bypass this entry.

Dispatch at 0x8CC230, table 0x8CCD2C:

ID Best-supported meaning Evidence / limits
0 Default/uninitialized path Unresolved
1–3 Startup/setup paths Static; previous-slot IDs 2/3 also seen during rematch
4 Startup/setup path One sampled rematch state
5–6 Default/mode-dependent Unresolved
7 Pre-play 76 rematch samples, followed by active play
8 Inbound administration Live + 0x8BD350; clock can run
9 Jump-ball-related candidate Static lead; full legal semantics unconfirmed
10 Active-play processing Also final-FT flight and paused game; not a live-ball boolean
11 Expired-period settling Live Q3/Q4 and bounded gate tests
12 Between-period / postgame presentation Both observed; not uniquely game-over
13 Special period/dead-ball path Static lead
14 Free-throw administration Live; can leave before final FT is credited
15 Shares FT handler Static only
16 Clock-stopped path Static only
17 Timeout-administration candidate Live correlation + static; controlled timeout still needed
18 Stoppage/adjudication Live + transition side effects; not a whistle-reason enum
19 Postgame menu in tested mode Final screenshot/capture and rematch

Entering 8 calls 0x8BC4C0, which establishes inbound context. 0x1DEE874 is an inbound substage 0–3, not a foul/inbound reason. 0x8BD350 handles initialization, waiting and release processing; 0x8A11F0 moves substage 1/2 to 3. Leaving phase 8 clears 0x1DEE614; entering 10 clears 0x1DEE610. Transition 10→18 stops the clock through 0x880460 and performs additional bookkeeping.

Three counterexamples prevent an oversimplified live/dead label:

  1. After a made basket phase 8 can have a running game clock. The stopping branch depends on period/time (last minute in Q1–Q3; last two minutes in Q4/OT under the inspected standard path) and a rule selector at 0x1E85DB0.
  2. Final FT flight can be phase 10 with free throws remaining already zero, before its point is credited. Neither phase exit nor remaining-count zero means the whole FT transaction is finished.
  3. In rematch-paused.jsonl, all 46 samples remain phase 10, game/shot timer flags both 1, while game clock 227.282958984375, shot clock 4.8730082511901855 and phase elapsed 19.620363235473633 stay unchanged for 4.997 seconds. Pause is a separate layer.

A future semantic trace must represent phase, pause, physical ball state, legal status, and clock advancement independently. A legal live/dead transition has not yet been recovered comprehensively.

Ball control, possession context and halftime

Field / function Established meaning Limit
[0x1CE716C] + 0 Current attached-object owner May be null in flight or a non-player actor; not legal team possession
Actor +4 Linked attached-object list Must identify object type, not assume every first attachment is a ball
Object +0x14 == 1 Ball object type used by 0x6B47F0 lookup Validated synthetic lookup/attachment contract
Ball +0x110 Last detached owner 0x7A1D80 copies current owner here when detaching; not current holder
Ball +0x120 Shooter/binding context Can remain stale after possession changes
Ball +0x1BC Physical-state enum 0–9 Distinct from phase and legal live/dead
[0x1DEE620], [0x1DEE624] Offensive/defensive contextual team roots Swap with context; neither fixed home/away nor universal control
0x1DEE908 Inbound team context candidate Entry handler supports interpretation; all assignments not mapped
Byte 0x14F5655 Previously advertised possession candidate Fixed home/away interpretation is disproved

0x401CD0 writes the owner pointer and links the object into the actor's attachment list. Across the five recordings, current active owner versus first linked ball agrees 7,563/7,565 times. The two disagreements are consistent with sequential reads crossing an attachment change; they demonstrate that this capture method does not provide atomic ownership. They are not silently discarded. Use object type/list traversal and coherent frame sampling before making a stronger guarantee.

The community byte requires an explicit correction. Among phase-10 samples with an attached roster player:

Period Holder team Byte Samples
Q2 Home 0 655
Q2 Away 255 634
Q3 Home 255 862
Q3 Away 0 987
Q4 Home 255 1,270
Q4 Away 0 1,431
Q4 Home 0 1

Q4 counts include overlapping recording time. This strongly suggests a relationship to changing court orientation, but the writer and exact intended meaning remain unproven. It is not safe to export 0=home/255=away. Physical attachment, team control, inbound entitlement, shot origin and last touch must remain different concepts. Loose-ball recovery is not automatically a credited rebound; a null owner during a pass is not automatically a change of possession.

Period-finish physical-state gate at 0x880CB0: states 0/2/8 permit completion; 3/4/9 block it; 1 uses owner/null plus 0x7D1D90(owner); 5/6/7 use trajectory. Only state 1 has strong attached-ball identification. Other labels in semantic-map.json remain candidates. The trajectory path compares height, or predicted apex using vertical velocity, with 298.7040100097656; the captured coefficient is -0.001079223700799048. These are raw engine units, not a declared real-world conversion.

Notification contracts and event ordering

The existing stat callback table at 0x1DE1D3C has count 0x1DE1D48 and capacity three, already occupied by 0x8C8C10, 0x874090, 0x6CBEF0. 0x884990 refuses a fourth entry. Blindly appending a callback will not work.

Common fanout 0x883E00 takes four stack arguments (mode, user/controller index, statID, delta), with actor in EDI, and returns with RET 16. Its individual callbacks receive ECX=mode, EDX=user/controller index, stack=statID, delta, actor, and use RET 12. Corrected during API engine integration by exact disassembly in analysis/api-engine/stat-commit.asm; the earlier wording conflated the fanout and callback ABIs. Mode 1 add, 2 subtract, 3 reset. Actor identity still accompanies CPU events with user index -1. Controller aggregate slots are 0x1DE1E18 + user*0x58 + statID*4; these are not the player box score.

  • 0x8847A0: add notification occurs before controller aggregate update. Player box-score writes may already have occurred in the caller.
  • 0x884820: subtract notification occurs even when amount exceeds current aggregate; in that case subtraction is refused, not saturated to zero.
  • 0x8848B0: reset fans out inline, bypassing 0x883E00.
  • 0x884750: assist/stat-2 notification also fans out inline, bypassing 0x883E00.
  • Rebound retraction callers can notify subtraction when their player counter is already zero and unchanged.

Thus a callback is a notification of an operation, not a guaranteed committed statistical change. A listener that blindly adds/subtracts every delta will drift. Hooking only the common fanout misses known paths. No live callback hook trace was installed in this pass; instruction-level local order is established by bounded execution, but whole-game ordering, callback loss and all bypasses remain unmeasured.

Stat ID Meaning / confidence
0 / 1 / 2 Defensive rebound / offensive rebound / assist
3 / 4 Block / steal — distinguish them; exact helpers write +0x242 / +0x240
5 / 6 Personal foul / player turnover; team-only turnover path bypasses 6
7 / 8 Three made / three attempted
9 / 10 Two made / two attempted
11 / 14 Free throw made / attempted
12 / 13 Shot-context counters +0x30 / +0x32; exact basketball names unresolved
15 / 16 Consecutive FG / consecutive three made candidates
17 Consecutive FT made
18 / 19 Paint points / fast-break points
20 / 21 Action-type-5 made-shot count / action-type-1 scoring points; names unresolved

The final UI's dunk count agrees with +0x30 in this one fixture, but that does not prove the entire ID-12 contract. Do not call ID20 “dunks” based on the numeric action type alone.

Credits, corrections and attribution

Player stats begin at actor +0xC7C. Team aggregates are home 0x1DE2188, away 0x1DE260C, stride 0x484. Scoring period groups stride 0x5C; OR/DR period groups stride 4; other counting stats stride 0x1C. Slot zero is total. Periods at least 5 clamp to slot 5 in inspected helpers, so these groups aggregate overtime instead of preserving separate OT1/OT2 records.

Rebounds

0x8BF610 credits OR, player counter +0x228, stores roster node in 0x1DEE420, then notifies stat1 and rebuilds team totals. 0x8A3C20 credits DR +0x22A, stores 0x1DEE424, then notifies stat0; it does not itself perform the same team rebuild. Node identity is not the actor pointer; use node +0x30 to resolve actor.

0x8BF700 retracts OR; 0x8A3B80 retracts DR. Tests across periods 1–8 and initial values 0/1/3 establish conditional decrement and notification even at zero. No live retraction was observed in these captures; code-supported possibility is not falsely presented as a recorded correction.

Bosh's Q4 OR in game-04 seq1296 is credited at clock 78.7443. Shot clock rose 8.3190→24.0 at seq1291, about 554 ms before the OR credit. Therefore waiting for the OR counter to change is too late for authoritative clock-reset timing. Wade's Q3 OR is independently attributed at game-03 seq17; no recent reset appears in that capture window. This is not evidence that no reset occurred earlier.

Assists

0x99FF00 uses remembered scorer 0x1E694A8, passer 0x1E694A4, FT exclusion 0x1DEE900, and timing context 0x1E694AC through 0x404870/0x41C330 before awarding. Exact eligibility curve, thresholds and pass/dribble invalidation remain open; “last passer gets assist” would be an unsupported simplification.

The successful path increments assister AST with 0x99FD90 (+0x248), adds shot points to scorer +0x246 with 0x99FD10, and to assister +0x24A with 0x99FD50, then notifies through 0x884750. These counter helpers were executed across periods 1–8 and two/three-point amounts. The entire eligibility function was not natively reconstructed/tested. +0x246 is assisted scoring points, and +0x24A is points produced by assists; neither is fast-break points.

Steals and blocks

0x8A3A80 is steal credit through helper 0x8836A0, writing +0x240 and context 0x1DEE430. The earlier block interpretation of this helper is wrong.

0x8BE240 commits pending block attribution from 0x1DEE470 (blocker) / 0x1DEE474 (victim), writes output pointers, clears pending context, and stores blocker/victim nodes at 0x1DEE41C/0x1DEE448, actors at 0x1DEE404/0x1DEE408, and tick at 0x1DEE410. Helper 0x8836D0 writes +0x242. 0x8A39A0 retracts; 0x8C8B80 checks same-tick context against 0x146B628. A first apparent block can therefore need correction. Context globals are sticky; a pointer not changing does not mean no second event involving the same player.

Fouls and turnovers

0x8BF490: ECX committer, EDX other actor, stack flag/type. Stores nodes at 0x1DEE43C/0x1DEE440. In the inspected stats-enabled path it credits PF and stat5. Flag results: 0x1DEE3D4 = (flag != 0 || type == 5); 0x1DEE3D8 = (type == 1); 0x1DEE3DC = (type == 2). Numeric type1 avoids the team-foul increment at teamstats +0x3B8. Ninety-six synthetic combinations establish those numeric branches, not sixteen proven valid named foul types. Type1 suggests an offensive-foul category but has not been correlated sufficiently to publish that name as fact. Bonus, shooting, technical, flagrant, double/off-ball fouls and disqualification require controlled fixtures.

0x8BFAE0 credits player turnover +0x24C and stat6, except numeric type13 uses the team-only +0x3D0 path without the ordinary player callback. That path is statically established but was not observed as a live team-only turnover in the tested match. Do not require every team turnover to sum to a player turnover or have a steal counterpart.

Four distinct observed turnover episodes have player-versus-team disagreement lasting approximately 3.99–4.32 seconds between first and last discrepant samples. game-04/game-05 capture the same last episode twice. Exact seq/UTC intervals are in observation-validation.json. This is much longer than a sequential-read skew: player and team tables have different update timing. It does not justify rewriting a valid player credit or synthesizing another turnover when the team table catches up.

0x8A3D60 rebuilds team totals and applies score adjustment at +0x444, clamping the resulting score nonnegative. Different modes can therefore invalidate a strict player-score-sum assumption. This match's final score did reconcile; that does not eliminate special-mode adjustments.

Shots, free throws and scoring categories

0x8BE770 (made two) and 0x8BEF10 (made three) update points and made/attempt counters before the tested notification prefix. 0x8BE3E0 FT path emits streak ID17 before points/FTM, then FTA14/FTM11 after those writes. 0x8BE550 is another FT path, not fully tested. 0x8C8220 handles miss/attempt finalization with foul-dependent suppression; 0x8C8880 is made-shot completion/streak/context handling, not simply a miss routine. One basketball shot may generate multiple notifications and later accounting decisions; grouping by adjacent callbacks or a single counter is insufficient.

Correction to prior survey: fast-break points are player/team +0x2A, helper 0x8835C0, not +0x1A. The earlier two snapshots coincidentally supported the wrong value. Final UI shows SA4/MIA6 fast-break points; +0x2A agrees while +0x1A yields SA6/MIA4. The latter agrees with points off turnovers in the final UI. Second-chance +0x2C, helper 0x8835F0, agrees with SA0/MIA4. Full causal attribution of those latter two categories remains incomplete. Paint points +0x18 are 24/24. The decoder is corrected; older historical decoded files/validation are retained as earlier-version evidence, not silently treated as corrected outputs.

Clock meanings and reset causes

Game clock float 0x1DEE638, shot clock 0x1DEE654; rounded remaining values 0x1DEE5F0/F4 are not configured period/shot-clock duration. Timer flags 0x1DEE640/65C describe timers, not legal ball state or application pause.

This configuration has float 0x1DEE884 = 360 (period duration), 0x1DEE888 = 24 (full shot clock), 0x1DEE88C = 14 (conditional minimum). 0x880190 raises the current shot clock to the last value only if lower, refreshes rounded time and does not start the timer. Fifteen local cases cover below/equal/above-target behavior. Do not infer a modern 14-second OR rule merely because this constant exists: the observed Bosh OR reset to 24 before credit.

0x8A1070 swaps contextual team roots using opponent +0x34, resets from 0x1DEE888 (with a near-period-end +1 branch), resets backcourt timing through table 0xF35874 / selector 0x1E85D3C, and emits multiple side effects. Calling this a clean possession setter would hide those dependencies.

Direct writer leads to 0x1DEE654: 0x8801B4, 0x89F311, 0x89F644, 0x8A1146, 0x8A15C2, 0x8A2262, 0x8A23A1, 0x8CBF87, 0xA3254D, 0xA3259D, 0xD1909F. Calls to the conditional raise at 0x79DAF6/0x79DE49 remain unnamed causes. These are discovery leads, not a complete reset-reason map. Rim touch, foul, timeout, possession change, inbound violation and OR need separate writer/branch correlation. Reconstructing a full reset policy from positive clock jumps would conflate causes.

End of period, final result and rematch

Normal readiness 0x8A01B0 requires phase not12, 0x880CB0 ball gate, no pending 0x1DEE974, and settle timer 0x1DEE97C > 0.1. A separate presentation-mode branch differs. 0x8BC690 normal timed-game branch checks period≥4, clock≤0 and nonzero score margin, then readiness, sets game-finish latch 0x1DEE930 and invokes completion effects. Special modes remain outside the 432 tested combinations.

The common expired-clock path in 0x8CC230 enters11, freezes time at zero, settles pending ball behavior, increments the raw period and sets transition flag 0x1DEE5F8. This is why raw period5 can mean regulation has just ended, not OT is playing. 0x8802E0 deliberately reports raw period minus1 and zero time while that flag is set.

Concrete observed sequence:

  • Q3 expiry: game-03 seq384 phase11, clock0; seq394 raw period4, phase12, transition flag1. seq590 begins Q4 with clock360 and phase8.
  • Regulation final: game-05 seq2429 raw period4, phase11, clock0; seq2430 raw period5, phase12, finished1, winner 0x1D95520 (away), SA50/MIA43.
  • Final menu: final capture phase19, previous12, same score/finished/winner. Phase12 alone is not a final marker, and postgame can proceed to another phase.
  • Final summary and team stats reconcile points, shooting, assists, rebounds, steals, blocks, fouls, turnovers and scoring categories. Quarter scores: SA12/6/14/18, MIA12/6/15/10, OT0. Parker21 points,2 rebounds,4 assists. This is one completed reference match, not a gap-free event trace or guaranteed final-save transaction.

Rematch is especially important for an external dynasty controller. Same PID, all 24 named player actor addresses reused. At rematch seq265, raw period resets5→1, clocks become360/24, finished/winner clear, but phase19 and old score50–43 remain. Seq266 still exposes old score. Seq267 is startup phase4, previous2, score0–0; seq268 phase7, previous3; seq344 active10 with clocks advancing. Previous IDs2/3 prove the polling stream missed intermediate transitions. Actor address and process ID cannot serve as a match ID. Nor can a fall in counters automatically mean a statistical correction: it may be a new match.

The rematch was left paused Q1 3:48, SA4/MIA6. No live OT, tied buzzer, final-basket correction, abort/forfeit, replay, roster reload, save/reload or crash/reconnect contract was established. External result persistence must await those tests; a falling game clock or a period number alone cannot safely finalize a season result.

Reliability requirements established by evidence

These are requirements for later implementation, not an API delivered here:

  1. Preserve raw numeric events/context and independently sampled committed counters. Distinguish add/subtract/reset intent from actual counter mutation.
  2. Observe event entry/exit on the authoritative game thread, retaining nesting/order and post-update evidence. Polling alone loses phases and cannot promise exact shot/rebound/reset chronology.
  3. Separate actor/roster identity from appearance IDs and pointer lifetime; use an explicit match generation because rematch reuses objects. Capture final state before initialization starts overwriting it.
  4. Treat timer state, phase, pause, ball attachment, legal status and team entitlement separately. Unknown legality stays unknown.
  5. Reconcile team totals after their update boundary. A several-second TO lag is real in this match; counters from one sequential sample are not guaranteed mutually consistent.
  6. Model reversible credits with original/correction relationships. Counter decreases and callback subtractions are not self-describing events.
  7. Qualify every field by supported EXE/runtime signature and every semantic label by evidence. A successful isolated helper test does not establish rule correctness or coverage in another build/platform.

Next research remains semantic: controlled foul/violation/FT/timeout scenarios, every clock writer's cause, assist eligibility, retained GameTrack storage, and an authoritative full-match trace including corrections, overtime, restart and abort. The seven records near 0x1DE1D50 and descriptor route 0x940A30 → 0x9436A0 are unproven history/commentary leads, not a recovered event journal. No claim of exhaustive event coverage is made.

Reproduction and evidence navigation

  • Recorder: observe_event_state.ps1, hash-guarded read-only process access. Bounded invocation scripts and original remote receipts in analysis/event-semantics/.
  • Decode: python3 tools/analyze_event_state.py analysis/event-semantics/game-01.jsonl --output analysis/event-semantics/game-01-decoded.jsonl.
  • Aggregate reliability: python3 analysis/event-semantics/analyze_reliability.py.
  • Contracts: .venv/bin/python analysis/event-semantics/verify_native_contracts.py; 959-case results, including explicit mock dependencies.
  • Final/lifecycle checks: python3 analysis/event-semantics/validate_observations.py; 89-check results and lag intervals.
  • Canonical address/enum map: semantic-map.json, generated by build_semantic_map.py; unresolved names intentionally retained.
  • Live findings: reliability-analysis.json, event-examples.json. Capture names are local to this investigation, not the prior analysis/live-data captures.
  • Static evidence: phase-dispatch.txt, state-enter-exit.txt, period-completion-gate.txt, ball-attach-detach.txt, object-attachment.txt, foul-dispatch.txt, offensive-rebound-retract.txt, defensive-rebound-retract.txt, assist-credit.txt, assist-mutators.txt, scoring-ui-correction.txt, and three Ghidra output directories. Failed leads are retained and subordinate to this report.
  • Evidence/version/source hashes: manifest. Search index updated through tools/index_openhoops.py.