---
title: Build owner profiles as the game path engine's foundation
type: task
status: planned
description: >-
  Rebuild the game path engine on stored, dated per-owner profiles (coach, callers, units, player
  groups, players, team, game) holding identity, tendency, variation and filed reads, with personnel
  and formation stages and play outcomes split into owned parts.
base_uri: user:task/league/build-game-path-owner-profile-foundation.md
created_at: '2026-11-03T05:50:20.393Z'
entity_id: 88b81e31-8579-492c-aa7a-1615f3cabb13
observations:
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
  - ████████████████████████████████████████████████
owner_identity_uri: █████████████████████████
priority: High
public_read: true
relations:
  - relates [[██████████████████████-███-████-████-█████████]]
  - relates [[█████████████████████-███-████-█████-████-█████████-████████]]
tags:
  - user:tag/league-xo-football.md
  - user:tag/nfl-betting.md
updated_at: '2026-11-04T18:10:20.418Z'
visibility_analyzed_at: '2026-11-05T23:48:27.244Z'
---

# Build owner profiles as the game path engine's foundation

Paths in the plan are relative to `repository/active/league/` unless they start with a user-base directory. The operator's rulings that set this plan are the 2026-11-03 `[decision]` observations on this task and on [[██████████████████████-███-████-████-█████████]].

## Overview

- **Goal:** the game path engine draws every snap from stored, dated profiles of the people and groups who produce it — head coach, offensive and defensive play caller, offense and defense unit, player group, role, player, team and game — while still re-drawing real plays. The profile steers the draw and never replaces an owner's own plays.
- **Outcome:** each forecast and review is specific to this game's owners in their current context; any owner's profile is readable as of any week; filed game-plan reads land on the stage they adjust; personnel, formation, protection, contact and yards after catch are drawn on purpose rather than carried by a whole borrowed play.
- **Impact:** the betting lane's tree of paths ([[█████████████-█████████████-████-████-██-█-████-██-████████]]) reads forks and branches from an engine that reflects the staff's plan, the room and the opponent's current unit, which is where the read's edge lives.
- **Ordering:** follows [[█████████████████████-███-████-█████-████-█████████-████████]], whose last step (wiring filed game-plan reads) moves here, and absorbs the engine task's "Filed game-plan reads and new-regime extensions" item and [[user:task/league/condition-game-path-player-yards-on-the-opposing-defense.md]]. The held branches `engine/pass-rate-dispersion` and `engine/lineup-by-call` are folded into the dial and lineup steps. Week 9 runs on current master.

## Background

- **Stages are labels over a monolith.** `libs-shared/nfl-game-path-simulation/simulate-game-path.mjs` holds all eleven stages in one closure (1,771 lines at `541d0dc29`, `wc -l`); `game-path-stage-pieces.mjs:14-26` gives each stage one piece; only the routing weight and the player-yards source list are read from rules, and `conditioning_axes` is validated but never read.
- **Path-level draws share one stream.** Unit rates, form games and per-player resampling draw from the path's single `random` before any stage runs (`simulate-game-path.mjs:208-300`), and a player's own plays are resampled lazily the first time any stage asks for him (`:216-235`).
- **Profiles are throwaway.** `build-game-path-simulation-profile.mjs` rebuilds an offense-only profile in every run; the run store writes `GAME_PATH_RUN_PROFILE_TABLES` Parquet (`game-path-run-store.mjs:34-57`) that only a spec reads. No profile table exists. Game-plan reads enter only through `--simulation_profile_edit` (`run-game-path-kickoff-forecast.mjs:118,131,190`).
- **Seats.** `GAME_PATH_ADMISSION_SEATS` (`libs-server/nfl-game-path-simulation/build-game-path-pool.mjs:49-87`) scopes the offense to the target season and the defense and head coach to 2016 onward; the kicker reads his career for any team; special teams read the union of all admissions; a backup quarterback widens to the caller across seasons. `resolve-game-regime.mjs` already defines each game's callers and starting quarterback.
- **Reviews and extensions.** A settled review computes its own `input_hash` and records child runs (`review-settled-game-paths.mjs:789-803,853-909`). An extension rebuilds its context from `data_snapshot` (`extend-game-path-review.mjs:219-228`), copies the parent's hash (`:290-313`) and must reproduce 50 paths (`:240-263`); it refuses on any mismatch or new `path_summaries` key (`:79-89,254-263`).
- **One real play is the outcome.** Formation is not loaded; personnel is loaded but unread; air yards, yards after catch, yards after contact, tacklers and timing are not loaded; the drawn pressure event has no consumer. The explorer drill-down reads one source play per snap role (`read-game-path-explorer.mjs:930-990`).
- **Existing thin-widening pieces.** `draw_own_rate` (`simulate-game-path.mjs:170`) draws each path's rare-event rate from the unit's own k of n; `build-game-path-draw-tables.mjs` computes the per-team rates and look tables a profile would hold.
- **Lineup** is an independent draw per tracked back, receiver and tight end, with no personnel count; linemen and defenders are not tracked.
- **Data (regular season, production, 2026-11-03).** The population query is query A in the Notes. 2025 and 2026 carry personnel and formation on every play, a charted role for every on-field player, pressure allowed per blocker and pressure per rusher, first contact and missed tackles per defender, yards after contact on almost every run and yards after catch on every catch; time to throw covers about 88 percent of dropbacks and time to pressure about 40 percent; broken tackles on catches cover 23 percent of 2026 catches; `yards_blocked` is empty in 2026.

### Assumptions

- Per-player charting covers every on-field player in 2025 and 2026 (checked: per-role counts in `view_nfl_player_play_charting`, 2026-11-03).
- No engine commits landed on master after `541d0dc29` (checked 2026-11-03: `git log 541d0dc29..master` on both engine directories is empty). The `██████████████████████████████` lane may still edit engine files; the precondition step checks again.
- The per-run profile Parquet has no reader except `test/libs-server.nfl-game-path-run-store.spec.mjs` (checked by code search, 2026-11-03).
- Charting keeps covering every on-field player through 2026 (unchecked; vendor feed).
- Yards after contact in 2025 and 2026 comes from one source (unchecked; the column-loading step checks the source mix and the pending rename in [[██████████████████████-█████-█████-███████-███████-██-█████████]]).
- `nfl_game_coaches` names a settled game's callers correctly; future rows are projections (query-trap sheet `nfl-game-coaches.md`).

## Design

- **Owners.** The closed set of owner kinds is head coach, offensive play caller, defensive play caller, offense unit, defense unit, player group, role, player, team and game. A kind's rule scope and stored profile open only when a measure lands on it; no empty profile is stored.
- **Context per measure.** Each measure declares the conditions that produce it (for example caller, quarterback and room for target share; carrier and line for yards after contact) and its minimum evidence, set from its mechanism with the reason recorded and never iterated against coverage. The producing conditions compile into the game-sample documents the pool already reads, so admission has one source. A wider admission needs a named-mechanism extension, filed as a read. Unit execution measures admit games by the members of the producing group.
- **Curation.** The builder reads the operator's curation through `load-game-sample-curation.mjs` and records each dropped game and reason inside the profile content. It does not read `opponent_baseline` decisions, which compute a mean over opponents.
- **Profile content.** Identity (structural, a question across contexts and never a number), tendency (measured weights from own plays with the admitted games per measure), variation (dial spreads) and citations of filed reads.
- **Roles.** Archetype definitions in `libs-shared/nfl-player-archetypes/` are shared across teams and identity only. A role in a unit is measured from that unit's own snaps by whoever filled it, and supplies plan-driven parts (usage, on-field rate per personnel, alignment, route, depth, run concept), never player-driven parts (catch, yards after catch or contact, broken tackles). Roles cover the line and the defense.
- **Player groups** are keyed by exact sorted membership, derived per as-of (line five, quarterback with each main target, backfield pair, front, secondary) or named in a filed read; a thin group gives way to its members' own profiles.
- **Combination rule.** A stage draws from its main owner's own plays, tilted by the other owners' measured shifts (log odds for yes-or-no outcomes, location for yards). An opponent's shift is measured per outcome part (completion against coverage and yards after catch on a pass, stuffed or not and yards before contact on a run), against each faced offense's own plays in its other games, never a league baseline, and each path draws it from the opponent seat's own resampled games. Each defensive pathway enters once: a part's shift replaces any narrowing or rate that already carries the defense on that part (`_against_look`, the defense's event rates) or is measured net of it, never both. A single mean-yards shift per call is not a part: it mixes completion with yards after catch and moves mostly incompletions and the long tail (operator, 2026-11-04). A game condition is declared per measure as producing or not; where producing, it filters the owner's own plays, and thin evidence widens or refuses. Officials are not modelled.
- **Dials** generalize `draw_own_rate`: each owner's variation is drawn once per path, and again at an in-game context break. With n admitted games and n at or above the measure's minimum, the spread is drawn from its own sampling distribution given n, so few games give a wide, heavy-tailed spread; below the minimum the dial refuses and the run flags the families it steers as carrying unmeasured variation. No prior is borrowed. Dials replace game form once the usage dials exist.
- **Random streams** are keyed by fixed names: one setup stream per path, one per stage under the final stage names, one per owner dial and one per player resample. Adding a stage, a dial or a player leaves every other draw unchanged.
- **Stages** become modules in the order decision, offense personnel and formation, defense answer, play call, lineup, coverage and rush, protection, event, routing, outcome parts, resolution, tempo. Each declares its main owner and tilting owners. The lineup seats today's players into the drawn personnel's exact counts; personnel is drawn only from games the available room could field.
- **Outcome parts.** Run: yards before contact (offense's own runs of the concept and gap, tilted by the defense) then yards after contact and broken tackles (carrier's own, tilted by the defense's tackling). Pass: protection (line group against rush group), throw and catch (quarterback and target group, clean or pressured, against coverage), yards after catch (receiver's own, tilted by the defense). A thin part draws the whole real play. Resolution composes the parts into one play with its flags and records one source play per part.
- **Storage.**
  - `nfl_game_path_owner_profile_contents`: `game_path_owner_profile_content_hash` text key (sha256 of the canonical JSON as built), `game_path_owner_profile_content` jsonb (CHECK `jsonb_typeof = 'object'`), `created_at`. Readers use the stored hash, never one recomputed from jsonb.
  - `nfl_game_path_owner_profiles`: `game_path_owner_profile_id` uuid key, `game_path_owner_kind` text (CHECK on the closed set, equal to `owner-kinds.mjs` by spec), `game_path_owner_key` text (format declared per kind), `nfl_team`, `season_year`, `season_type`, `profile_as_of` timestamptz, `profile_builder_version` text, `game_path_owner_profile_content_hash` (foreign key to contents), `created_at`; unique on kind, key, as-of and builder version. Unchanged owners point at the same content row.
  - `nfl_game_path_simulation_run_owner_profiles`: `game_path_simulation_run_id` (ON DELETE CASCADE) and `game_path_owner_profile_id` (indexed), key on the pair. A review's children and an extension inherit the parent's citations through `review_` and `extends_game_path_simulation_run_id` and load the cited snapshots, never rebuilding as of today.
  - `nfl_game_path_game_plan_reads`: append-only, `game_path_game_plan_read_id` uuid key, `esbid` (foreign key to `nfl_games`, indexed), `game_path_owner_kind`, `game_path_owner_key`, `game_plan_read_rules` jsonb, `recorded_by varchar(120)`, `recorded_at`, `supersedes_game_path_game_plan_read_id`; a partial unique on the live read per game and owner. Operator-filed and not re-derivable, so it is backed up.
  - The contents, profiles and citations tables are derived and stay out of the backup.
- **Local-first, narrowly.** The builder reads its inputs from Postgres, so it cannot run without it. Once a snapshot is computed it is written as `<run store root>/owner-profiles/<hash>.json` through a `.partial` file and a rename, then upserted; a failed upsert leaves the file and a visible backlog that the next build drains.
- **Snapshots on demand.** A run builds or reuses each snapshot as of its target's kickoff, cached by owner, as-of and builder version; `scripts/build-nfl-game-path-owner-profiles.mjs --backfill` fills past kickoffs for baselines and replays. No scheduled job is needed.
- **Open reviews across output changes.** Old runs stay readable but cannot be extended after an output-changing step. Each such step lands only after the current week's reviews are extended, and open reviews are re-run with `scripts/review-nfl-game-paths.mjs --esbid <esbid> --write` afterwards.
- **The gate guards and never tunes.** A change lands when its specs, the replay gate and the forward gate pass (operator, 2026-11-04, replacing the two-seed "no worse and no narrower" rule).
  - **Forward gate.** The change and master are scored game by game on each family's log score, the score the betting lane grades curves on, over the forward backtest weeks at seeds 7 and 11. A family's verdict is the paired score difference, clustered by game, read against its null band: worse or better only beyond the band, inconclusive inside it. A change lands when no family it touches is worse.
  - **Null band.** The same paired difference between master and model-neutral reruns of master (a pick that reads the mirrored uniform: the same draws and distribution, different plays), measured on the same weeks and re-measured when the instrument or the weeks change. A move inside the band is noise, never a fail.
  - **Coverage guardrail.** Each family's outside share is read on both sides of 0.20 within its binomial error clustered by game; a family that leaves that range, too wide or too narrow, blocks the change. Coverage describes the instrument and is never the verdict.
  - Refusal mass from a ruled retirement counts as widening. No weight is fitted to pooled outcomes, and what a change measures and its minimums are fixed before it is scored.
- **File organization.** New `libs-shared/nfl-game-path-profiles/` (owner kinds, measure registry, content schema, measures, builder) and `libs-server/nfl-game-path-profiles/` (input loaders, writer, reader); new `libs-shared/nfl-game-path-simulation/stages/` with one module per stage; `simulate-game-path.mjs` becomes the runner. `build-game-path-draw-tables.mjs` reads its measures from the snapshot.
- **Knowledge Base Impact.** [[████████████████████-████-████-█████████]] is rewritten to the new shape, defines owner, context, profile snapshot, dial, role and player group, and records the review lifecycle across output changes; the retired names listed in the last step are searched out. The archetype and opposing-defense tasks close; the engine task's absorbed and stale items are removed. No new scratch area: gate rows use `scratch/league/game-path-explorer/coverage/` on ████████████.
- **Shape.** A parent task holding the design, with one child task per phase, each blocked by the one before (operator, 2026-11-03).

## Notes

- **Forward gate runs.** Weeks 3 through the last settled week, master and the change, at seeds 7 and 11, with the null band's master reruns measured once per instrument and weeks: `node scripts/report-nfl-game-path-coverage.mjs --mode forward --week <weeks> --seed <7|11> --rows_out scratch/league/game-path-explorer/coverage/<label>-seed<7|11>.json`, with `--null_rotation 1`, `2` and `3` for master's model-neutral reruns, then `--compare --baseline <master seed files> --change <change seed files> --null <null rerun files>`. One engine job at a time on ████████████. Two seeds barely narrow the team families' error, which comes from too few games rather than from path draws (2026-11-04: an outside-share change under about 0.10 on 82 team-games is undetectable), so evidence grows by weeks, not seeds.
- **Query A** (data population per season), recorded so a later session can reproduce the Background figures:
  `WITH s AS (SELECT p.* FROM nfl_plays p JOIN nfl_games g ON g.esbid = p.esbid WHERE p.season_year IN (2024,2025,2026) AND g.season_type = 'REG' AND p.play_classification = 'live_play' AND NOT coalesce(p.is_special_teams_play,false) AND (p.is_qb_dropback OR p.is_rushing_play) AND NOT coalesce(p.is_two_point_conversion_attempt,false)) SELECT season_year, count(*), count(offense_formation), count(time_to_throw) FILTER (WHERE is_qb_dropback), count(time_to_pressure) FILTER (WHERE is_qb_dropback), count(yards_after_catch) FILTER (WHERE is_completion), count(yards_after_any_contact) FILTER (WHERE play_type='RUSH'), count(broken_tackles_receiving) FILTER (WHERE is_completion), count(yards_blocked) FROM s GROUP BY 1 ORDER BY 1;`
- Schema work follows `docs/guides/schema.md`: the DDL goes in `private/db/adhoc/YYYY-MM-DD-<slug>.sql` with `STATUS: PENDING`, is applied by the operator-gated path, and is confirmed in production before any commit that references it ships.


## Plan

Each phase is its own task, done in order; the knowledge base updates close the parent.

### Phases

- [x] **Move game path stages into modules with fixed-key random streams** — [[user:task/league/build-game-path-engine-stage-modules-and-streams.md]]. Verify: the child task reads completed.
- [x] **Scope every game path seat to its current context** — [[user:task/league/scope-game-path-seats-to-current-context.md]]. Verify: the child task reads completed.
- [ ] **█████ ███ ████ ████ █████ ███████ █████ ███ █████ █████████ █████** — [[██████████████████████-████-████-█████-███████-█████-███-█████-████████]]. Verify: the child task reads completed.
- [ ] **Draw game path owner dials per path** — [[user:task/league/draw-game-path-owner-dials.md]]. Verify: the child task reads completed.
- [ ] **Draw game path personnel, roles and a constrained lineup** — [[user:task/league/draw-game-path-roles-personnel-and-lineup.md]]. Verify: the child task reads completed.
- [ ] **Split game path play outcomes into owned parts** — [[user:task/league/split-game-path-outcomes-into-owned-parts.md]]. Verify: the child task reads completed.

### Knowledge Base Updates

- [ ] **Rewrite the engine system doc** — [[████████████████████-████-████-█████████]] to the new shape, with the new terms and the review lifecycle across output changes. Verify: `rg "simulation_profile_edit|GAME_PATH_RUN_PROFILE_TABLES|GAME_PATH_ADMISSION_SEATS|FORM_GAME_COUNT|open_field_player_yards|form games|archetype fallback"` across `text/league/`, `docs/` and the league code returns nothing outside history.
- [ ] **Close the absorbed work** — close the archetype task with its last step moved here; close the opposing-defense task as absorbed; remove the engine task's "Filed game-plan reads and new-regime extensions" item and its stale per-path carry and target-share item. (no verify — mechanical step)
