# CFB27 Schema Bundle Mining — C27_468_2.gz (authoritative table/field/enum reference)

Source: `E:/Games/EA SPORTS College Football 27.SteamGG.NET/Mods/CoachCarouselRecruitTool/reference/force-commit-recruits/engine-data/C27_468_2.gz`
Decompressed working copy: `<scratchpad>/schema_C27_468_2.xml` (it is JSON despite the .xml habit-name)

Companion machine-readable outputs (both regenerated by `<scratchpad>/extract.js`):
- `research/schema-all-tables.txt` — ALL 3,498 schemas, sorted, `name<TAB>fieldCount<TAB>base<TAB>assetId`
- `research/schema-relevant-tables.json` — 1,635 relevant tables with complete field defs + 236 fully-membered enums (`{_meta, tables:{name:{base,assetId,numMembers,fields:[...]}}, enums:{name:{bits,members:[...]}}}`)

## 1. Bundle format (verified)

- gzip → 32,108,835-byte JSON document. Top-level keys: `meta`, `schemas`, `schemaMap`.
- `meta` = `{"major":468,"minor":2,"gameYear":27}`.
- `schemas` = array of 3,498 schema objects: `{assetId, name, numMembers, base, attributes:[...], originalAttributesOrder?, ownerAssetId?}`. No duplicate names.
- `schemaMap` = name→schema lookup of the same objects (3,482 keys; a handful of schemas are absent from the map).
- Attribute keys observed (with counts across the whole bundle): `name`/`type` 27,199; `index` 27,075; `default` 12,670; `minValue`/`maxValue` 6,809; `enum` 2,082 (inline full enum defs); `maxLength` 1,963 (strings); rare: `idx` 12, `final` 8, `const` 3, `guid` 2.
- **Attribute lists are pre-flattened**: `Coach` (base `CoachingStaffPerson`) lists all 137 members directly; no base-chain walking is needed. `numMembers == attributes.length` in every checked case.
- **Enums exist only inline** on attributes (`enum:{_name,_assetId,_members:[{_name,_index,_value,_unformattedValue}],_maxLength}`). `_maxLength` is the storage bit width; `_value` is the stored number; multiple `_name`s can alias one `_value` (e.g. `First_`/`Top10` = 0). 434 distinct enums in the bundle; 236 are used by the relevant table set.
- **Bit widths**: enums carry `_maxLength` explicitly. For ints the schema only has min/max; the JSON's `bits` for ints is DERIVED as `ceil(log2(max-min+1))` (matches every stage-1 live-save observation, e.g. `OfferIndex` 0–6 → 3b — note stage-1 said "17b" for OfferIndex which was a misread; schema says max 6). Live-save offset tables remain authoritative for exact packing.
- Some attributes' `default` contains **embedded game-script bodies** (EA's schema-hosted logic, e.g. `Team.GetTeamStatsForStage`). Searched them for `Decommit|JobOpening|IsCarouselPeriodActive` — no hits; the interesting numeric behavior lives in native code + the Tuning tables below.
- The bundle contains far more than save tables: `Reaction` subclasses (495), `Event` subclasses (315), UI forms, tuning tables, manager modules. Only a subset are instantiated as tables in a dynasty save, but tuning/manager tables (e.g. `StaffHiringTuning`, `RecruitingTunables`) DO appear in saves and are readable.

## 2. Coach-carousel machinery (verified from schema)

### JobOpening (assetId 8624654, 11 members) — one row per open job
| # | Field | Type | Bits | Notes |
|---|-------|------|------|-------|
| 0 | ContractOfferList | StaffPersonContractOffer[] | 32 ref | ordered candidate list |
| 1 | Filled | bool | 1 | def False |
| 2 | FinalContractProgramPoints | int 0–2000 | 11 | |
| 3 | HighestOfferedProgramPoints | int 0–2000 | 11 | |
| 4 | InterestedUserTeamsList | Team[] | 32 ref | |
| 5 | IsEmergentJobOpening | bool | 1 | def False |
| 6 | Position | enum CoachPosition | 8 | def First_ |
| 7 | PrevCoach | ref Coach | 32 | who vacated |
| 8 | Reason | enum CoachLeaveReason | 3 | def None |
| 9 | SelectedCoach | ref Coach | 32 | who was hired |
| 10 | Team | ref Team | 32 | |

### StaffPersonContractOffer (assetId 6378749, 14 members)
| # | Field | Type | Bits | Notes |
|---|-------|------|------|-------|
| 0 | AdjustedStaffPersonInterestInOffer | int 0–100 | 7 | |
| 1 | BaseStaffPersonInterestInOffer | int 0–280 | 9 | |
| 2 | ContractExpectationsByYear | enum[] | — | array of ContractExpectations |
| 3 | ContractPosition | enum CoachPosition | 8 | def Invalid_ |
| 4 | ExpectedContractProgramPoints | int 0–2000 | 11 | |
| 5 | ExperiencePoints | int 0–50000 | 16 | |
| 6 | Length | int 0–15 | 4 | contract years |
| 7 | OfferedContractProgramPoints | int 0–2000 | 11 | |
| 8 | OfferIndex | int 0–6 | 3 | rank in group (schema says 3 bits, not 17) |
| 9 | StaffPerson | ref **StaffPerson** (base class) | 32 | see §7 StaffPerson family |
| 10 | StaffPersonTeam | ref Team | 32 | |
| 11 | Status | enum ContractOfferStatus | 3 | def Pending |
| 12 | Team | ref Team | 32 | hiring school |
| 13 | TeamInterestInStaffPerson | int 0–280 | 9 | schema max 280 (UI shows /100) |

### Carousel owner module: StaffHiringEval (114 members; a Module — one row in save)
Key data fields (rest are script/request plumbing):
- `JobOpenings : JobOpening[]` — **the master list of open jobs** (this is how the game reaches JobOpening rows; our tool can too, instead of scanning the table raw).
- `OutstandingStaffPersonOffersList : StaffPersonContractOffer[]` — league-wide outstanding offers.
- `ChanceCoachWaits` int def 33, `ChanceToIncreaseOffer` def 25, `ChanceToWithdrawOffer` def 25, `PercentageToIncreaseOffer` def 5 — CPU offer-loop behavior.
- Refs to `StaffHiringTuningRef`, `SeasonInfo`, `Franchise`, `CoachRetirementEvalRef`, `CoachManagerRef`.
- Lifecycle handlers named in schema: `HandleChampionshipWeekStart`, `HandleNationalChampionshipStart`, `HandleOffseasonStart`, `HandleRegularBowlWeekStart`, `HandleSeasonBegin`, `HandleStaffHiringWeekAdvance`, `HandleStaffHiringEvaluateOffersStart`, `ReleaseStaffWithNoOutstandingOffers`, `RemoveOutstandingOffers`, `ProgressCoachContracts`, `UpdateContractOffers` — i.e. the carousel is driven from bowl-season week starts through offseason start, matching the observed weeks 14–16 window. (Function bodies are native/script; not all inspectable.)

### StaffHiringTuning (base BaseTuning, 120 members) — carousel dials (all league-editable in save)
Highlights (name : range = default):
- `MaxJobCandidates` 0–12 = **6** (explains the 6-slot StaffPersonContractOffer[] rows); `MaxOffersPerCoach` 0–10 = **4** (so Coach.NumContractOffers 4-bit/max-12 is safe in practice).
- `CandidateCutoffScore` = 140; `InterestedThresholdForOffer` = 70; `JobInterestCutOff` = 70; `CoachInterestThreshold` = 0.
- Firing: `CoachFiringStatusLevel` = JobSecurityStatus:HotSeat; `HC_FiredRangeMin/Max` 5/15; `Coord_FiredRangeMin/Max` 5/15; thresholds `HC/OC/DC_SafeThreshold` 90, `_LowThreshold` 80, `_HotseatThreshold` 70.
- Leaving: `CoachLeavingOddsSpline`; `LeaveForNFLMax` 5; `LeaveForNFLPrestigeOddsThreshold` 80; `RetireMax` 5.
- **`PoachThresholdHC` 0–5 = 2, `PoachThresholdCoord` 0–5 = 1** — engine's own "coach poached to a better job" gates (useful analog for our follow-coach rule).
- Interest shaping: `BetterJobInterestModifier` 0, `WorseJobInterestModifier` 50, `ContractLengthInterestMultiplier` -10, `PrestigeCompareScoreSpline(+Buffer 50)`, `TeamPrestigeToInterestPointsSpline`, `JobSecurityToInterestPointsSpline`, `SalaryOfferRatioToCoachInterestSpline` (on StaffHiringEval), `UserExpressedInterestScoreBonus`, `UserPrestigeBonus`.
- Contract points economy: `ContractPoints_WinGame` 20, `_ConfChamp` 40, `_NatChamp` 100, `_NewCoach` 40, `_NewContractBonus` 40; `MaxExpectedContractPoints` 230; multipliers ThisYear×5 / LastYear×2 / TwoYearsAgo×1; `CoordinatorRoleModifier` 0.7; `CoordPrestigePenalty` 30.
- Extensions: `ShortTermExtensionLength` 1 @ SafeForNow; `LongTermExtensionLength` 2 @ Safe; `CPUAutoExtendContractYearsRemainingThreshold` 2.

### Carousel/staff-hiring event & reaction schemas (signal names, 0-data Event subclasses unless noted)
`CoachCarouselStartEvent` (carries `SeasonInfoUpdateEvent`), `CoachCarouselEndEvent`, `StaffHiringPeriodStartEvent/EndEvent`, `StaffHiringEvaluateOffersStartEvent`, `StaffHiringHireCoachesEvent`; reactions `CoachCarousel_PostSeasonWeekStartReaction`, `CoachCarousel_PostSeasonWeekEndReaction`, `CoachCarousel_RegularWeekStartReaction`, `CoachCarousel_UserRegisterReaction/UserUnregisterReaction`, `StaffHiringHireCoachesReaction`, `StaffHiringPeriodEndReaction`. UNVERIFIED (needs live carousel save): exact tick when JobOpening rows are created/cleared; the reaction names imply creation at post-season week starts and teardown by `StaffHiringPeriodEndEvent`/`CoachCarouselEndEvent` before offseason recruiting stages.

## 3. Recruiting board & commitment machinery

### Recruit (assetId 6716442, 15 members)
| # | Field | Type | Bits | Notes |
|---|-------|------|------|-------|
| 0/1 | AlternatePosition1/2 | enum DraftPositionE | 5 | def Invalid_ |
| 2 | Class | enum RecruitingClass | 4 | def Transfer_Junior |
| 3 | CommitScore | int 0–1023 | 10 | |
| 4 | NationalRank | int 0–4500 | 13 | |
| 5 | Player | ref Player | 32 | |
| 6 | PositionRank | int 0–4000 | 12 | |
| 7 | ProductionGrade | int 0–127 | 7 | |
| 8 | QualityModifier | enum GemBust | 3 | def NORMAL |
| 9 | RecruitStage | enum RecruitStage | 4 | def **Invalid** |
| 10 | RecruitStageAdvance | enum RecruitStageAdvance | 3 | def **Invalid** |
| 11 | StateRank | int 0–4000 | 12 | |
| 12 | SurnameAudioID | int 0–32767 | 15 | |
| 13 | TopSchoolsList | ProspectTargetSchool[] | 32 ref | |
| 14 | TotalScholarshipOffers | int 0–63 | 6 | |

### RecruitTarget (assetId 6716466, 20 members) — one row per school-board slot
ActivePitches:ActiveRecruitingPitch[] (32 ref) | CommittedWeekNumber int 0–31 (5b) | ContactFriendsAndFamily/ContactHighSchoolCoaches/SearchSocialMedia/SendTheHouse/VisitRecruitsSchool bool (1b each) | CurrentNILOffer int 0–1023 (10b) | CurrentScholarshipBonus int **-200–50** (8b) | NILExpectation int 0–1023 (10b) | OriginalNILExpectation int 0–510 (9b) | ProspectHoursSpentCurrent int 0–127 (7b) | ProspectInfluenceDelta int -200–1023 (11b) | ProspectInfluenceTotal / ProspectInfluenceTotalLastWeek int 0–1023 (10b) | Recruit ref Recruit | ScheduledVisit ref ActiveVisitInfo | ScholarshipStatus enum ScholarshipStatus (3b, def Invalid) | SwayPitch enum RecruitingPitchType (5b) | UnlockedIntelBitfield int 0–16383 (14b).

### UserRecruitTarget (base **RecruitTarget**, 23 members) — ANSWERS the "user board" open question
The human player's board rows live in a **separate table of subclass UserRecruitTarget** = all 20 RecruitTarget fields **plus**:
- `IsFavorite : bool`
- `ImmediateRecruitingFeedback : RecruitingActionFeedbackEntry[]`
- `RecruitingFeedback : RecruitingActionFeedbackEntry[]`
This is exactly why force-commit-recruits sees board-array element refs whose tableId differs from the RecruitTarget table: the user school's `RecruitingBoard.Recruits` slots point into the UserRecruitTarget table instead. The shared base fields can be edited identically; only resolve the ref's actual target table instead of assuming RecruitTarget. (Editable-in-practice: UNVERIFIED until we write one on a live save.)

### RecruitingBoard (4 members) — Team.RecruitingBoard target
`RecruitingHoursAssigned` / `RecruitingHoursProcessed` / `RecruitingHoursTotal` int 0–4095 (12b) | `Recruits : RecruitTarget[]` (32 ref). So the canonical chain is **Team → RecruitingBoard → RecruitTarget[] row** — the force-commit tool's "RecruitTarget[] row index == TeamIndex" observation is an implementation coincidence of contiguous allocation; the schema-sanctioned route is via Team.RecruitingBoard.

### ProspectTargetSchool (assetId 6716465, 2 members) — CONFIRMED literal schema name
`TeamId : int 0–2047 (11b)` (stores Team.TeamIndex, NOT the Team-table row) | `TeamInfluence : int 0–65535 (16b)`. Owned by `Recruit.TopSchoolsList` (only owner in the bundle).

### Commitment master lists (Franchise table, 49 members)
- `Franchise.RecruitingClassPlayers : Recruit[]` — the HS/JUCO recruiting class.
- **`Franchise.Transfers : Recruit[]` — the TRANSFER PORTAL master list.** Portal players are ordinary Recruit rows referenced here (with `Class` = Transfer_*). This is the portal container our tool should read/write at the portal stage.
- `Franchise.RecruitingClassCoaches / FreeAgentCoaches / RetiredCoaches / DraftClassCoaches / HallOfFameCoaches : Coach[]`.
- Also: `Teams : Team[]`, `FCSTeams : Team[]`, `HighSchoolTeams : Team[]`, `SeasonInfo`, `LeagueSetting`, `RecruitManager`, `HistoryManagerRef`, `FreeAgents : Player[]`, `RetiredPlayers : Player[]`.
- `Team.CommittedPlayers : Player[]` — per-school list of committed players (a second, denormalized commit signal alongside Recruit.RecruitStage + TopSchoolsList slot 0; keep it in sync when decommitting — UNVERIFIED whether the engine rebuilds it weekly).

### Recruiting stage machinery
- `RecruitingStageDetails` {CanEnterStage(script), GetSchoolPercentageOfCommitScore(script), NextStageRecord ref RecruitingStageDetails, RecruitStage enum} — a linked list of stage nodes; `RecruitManager.FirstRecruitingStageDetails` / `RecruitingBattleStageDetails` are the entry points.
- `RecruitingTunables` (88 members) commit math: `PointsPercentageThresholdCommit`=100, `Top3`=75, `Top5`=50; `TriggerRecruitingBattleWithinCommitThreshold`; `BattleAdditionalCommitScorePercentage`; `HardCommitAdditionalCommitScorePercentage`; stage school counts Top10=10/Top5=5/Top3=3/Battle=2/SoftCommitted=1/HardCommitted=0/Signed=1; `InstantCommitOddsPerStarLevel : int[]`, `InstantCommitBonusPrestige : int[]`.
- Portal tunables inside RecruitingTunables: **`TransferDefaultCommitScore` 0–1023 def 1000**, `TransferPortalActionItemString`, `TransferRecruitScoreTunable : RecruitScoreTunable`, `TransferStartingRecruitDataList : TransferStartingRecruitData[]`, `TransferValueCutOffs : int[]`.
- `TransferStartingRecruitData` {MaxCommitScore 10b, MinCommitScore 10b, RecruitStage enum, StarRating enum ProspectQuality} — how portal entrants get seeded with a starting stage/commit score by star rating.
- Events (names only): `AddToTransferPortalEvent{Recruit}`, `RecruitAddedToBoardEvent`, `RecruitRemovedFromBoardEvent{Recruit}`, `RecruitSignedEvent{Recruit}`, `RecruitStatusUpdateEvent{Recruit}`, `UpdateProspectTargetSchoolEvent`, `EncourageTransfersStart/EndEvent`, `SendPlayerToTransferPortalAction`, `ForceTransferAction`, transactions `ProcessEndOfRecruitingCycleTransaction`, `RecruitingAdvanceBoardsTransaction`, `RecruitGenerationTransaction`.

### Road-to-Glory recruiting (different subsystem — do NOT confuse with dynasty recruiting)
`NarrativePlayer` (73 members) holds the RTG player's own recruitment: `SchoolRecruitingRelationshipList/TopSchoolsList : SchoolRelationship[]`, `SchoolRecruitingOffersList : SchoolOffer[]`, `VerbalCommitTeam : Team`, `TeamSignedWith : Team`, `TotalDecommitCount` 0–7, `IsEnteringTransferPortal : bool`. `SchoolRelationship` (17) has `ScholarshipOfferStatus : ScholarshipStatus`, `ScholarshipBonusTier` (Bronze/Silver/Gold/Platinum), **`WasDecommitted : bool`**, `Team`, TeamNeedScore -3000–200. `SchoolOffer` (7): HasOffer, InterestLevel/OfferInterestLevel 0–65536, OfferType enum RecruitOfferType {Starter=0, SecondString=1, Bench=2, WalkOn=3}, Team. Dynasty Recruit/RecruitTarget rows have NO WasDecommitted flag — dynasty decommit state is only expressible via RecruitStage/RecruitStageAdvance + board/top-school edits.

## 4. Dynasty calendar — SeasonInfo (assetId 6708685, 59 members, full list)
Ints: `BaseCalendarYear` 0–4096 (13b), `BaseSuperBowlNumber` 0–127, `CurrentFreeAgencyStage` 0–15, `CurrentOffseasonStage` 0–15 (4b), `CurrentSeasonYear` 0–4095 def 2012, `CurrentWeek` 0–31 (5b), `CurrentYear` 0–100 (7b), `HSRecruitingCurrentMaxTopSchools` def 10, `HSRecruitingNextStopMaxTopSchools`, `MaxYears` def 30, `NflseasonWeekCount`, `NumberOffseasonAdvances` 0–9, `OffseasonNumStages` 0–15, `PostSeasonNumWeeks` def 5, `PreseasonWeekCount`, `RegularSeasonLastWeekScheduled` def 14, `RegularSeasonWeekConferenceChampionship` def 16, `TotalFreeAgencyStages` def 5.
Enums: `CurrentStage : SeasonStage (2b) {PreSeason=0, NFLSeason=1, OffSeason=2}`; `CurrentWeekType : SeasonWeekType (4b) {PreSeason=0, RegularSeason=1, BowlSeason1=2(FirstPlayoff_), BowlSeason2=3, BowlSeason3=4, NationalChampionship=5(LastPlayoff_), OffSeason=6, Invalid=8}`.
Bools (period gates): IsAnnualAwardPeriodActive, **IsCarouselPeriodActive**, IsCoachDemandReleasePeriodActive, IsCombineComplete, **IsCommittmentPeriodActive**, IsDraftActive, IsDraftPeriodActive, IsDraftRecapPeriodActive, IsDraftScoutingActive, IsFacilitiesIncludedInFreeingUpBudgetPeriodActive(def True), IsFantasyDraftActive, IsFreeAgentPeriodActive, IsFreeAgentPlayerPeriodActive, IsFreeAgentTeamPeriodActive, IsGoalsPeriodActive, IsGraduatingSeniorNILExclusionPeriodActive, IsLeagueStarted, IsLiveSeasonsLeague, **IsPitchingPeriodActive**, IsPlayerDemandReleasePeriodActive, IsPracticeSquadPeriodActive, IsProDayComplete, IsRebuildingPeriodActive, **IsRecruitingPeriodActive**, IsRelocationPeriodActive, IsReSignPeriodActive, IsSalaryCapRosterSizeIncActive, **IsScholarshipPeriodActive**, IsScoutingPeriodActive, **IsSigningPeriodActive**, **IsStaffHiringCreateOfferPeriodActive**, **IsStaffHiringEvaluateOfferPeriodActive**, **IsStaffHiringPeriodActive**, IsTradingActive(def True), IsTradingPeriodActive, **IsTransferPortalNewlyAvailable**, **IsTransferSignPeriodActive**, **IsVisitingPeriodActive**, IsWeeklyAwardPeriodActive.
There is **no offseason-stage enum**; `CurrentOffseasonStage` is a plain int cursor over `OffseasonNumStages`. The transfer-portal window is signposted by `IsTransferPortalNewlyAvailable` + `IsTransferSignPeriodActive`.

## 5. Coach / staff family

Inheritance: `Person` → `StaffPerson` (22) → `CoachingStaffPerson` (79) → `Coach` (137). Siblings of Coach (UNVERIFIED whether all are instantiated in CFB27 saves): `Owner`, `Scout`, `Trainer`, `GeneralManager`, `PlayerPersonnel` (referenced from Team fields: HeadScout:Scout, HeadTrainer:Trainer, GeneralManager, PlayerPersonnel, Owner). `StaffPersonContractOffer.StaffPerson` is schema-typed to the **base** StaffPerson, so offers CAN legally point at Scout/Trainer rows — validate the ref's target table when reading (StaffHiringEval has GetScout*/GetTrainer* machinery, so non-Coach offers likely occur for support staff).

StaffPerson core (all inherited into Coach at the same names): Age 7b, ContractLength 3b 0–7, ContractSalary 14b 0–16383, ContractStatus enum StaffPersonContractStatus 4b def FreeAgent, ContractYearsRemaining 5b 0–31, FirstName str17, LastName str21, IsCreated/IsLegend/IsUserControlled bool, Level 7b 0–100, Portrait 13b, Position enum CoachPosition def HeadCoach, PresentationId 10b, PrevPosition enum CoachPosition def Invalid_, PrevTeamIndex 8b def 255, Probation bool, SeasonsWithTeam 7b, TeamIndex 8b def 255, ActiveTalentTree ref.

CoachingStaffPerson adds (56 new): AlmaMater int **1100–1300** (8b, an ID range — not a TeamIndex), AssetName str41, AwardPoints, Career* records (CareerStats ref CareerCoachStats, CareerPointsFor/Against 16b, CareerPlayoffsMade, CareerWinSeasons, streaks), CharacterBodyType/CharacterVisuals, DefaultTeamPhilosophy, Defense/OffenseAudibles, DefensivePlaybook/OffensivePlaybook (refs DataType tables), DefensiveScheme/OffensiveScheme (ref Scheme), ExperiencePoints 20b 0–1,000,000, GenericHeadAssetName str33, HasTrait, HatType, Height, HomeState enum StateName, HomeTown ref City, LegacyScore 16b, Personality enum, **PrimaryPipeline enum Pipeline 6b**, SeasonalGoal, SeasonStats, Seas* season records, SpeechId, SuperbowlWinStreak…

Coach adds (58 new): COACH_ADAPTIVE_AI enum, COACH_CONSECTEAMCONTRACTS 5b, position-group ratings COACH_QB/RB/WR/OL/DL/LB/DB/K/P/S 7b, COACH_OFFENSE/DEFENSE/DEFENSETYPE/OFFTENDENCY*/DEFTENDENCY*/RBTENDENCY 7b, COACH_DEMEANOR enum, **COACH_FIREREPORTED bool def True, COACH_RESIGNREPORTED bool def True, COACH_LASTCONTRACTTEAM / COACH_LASTTEAMFIRED / COACH_LASTTEAMRESIGNED int 0–1023 (10b)** (range fits TeamIndex 0–255 with headroom; actual value semantics UNVERIFIED — needs a live fired coach), COACH_PERFORMANCELEVEL 8b, COACH_RETIREYRSLEFT 3b, COACH_SPECIALTY enum CoachSpecialty, COACH_STANCE, COACH_WASPLAYER, CoachBackstory enum, CoachPoints 12b 0–4095, **CoachPrestige enum LetterGrade def Incomplete**, **CoachPrestigeScore 14b 0–10000**, ContractExpectationProgress/CurrentContractExpectation enum ContractExpectations, ContractYearSummaries ref array, **CurrentJobSecurityPercentage 7b 0–100, CurrentJobSecurityPercentageRank 9b 0–500, CurrentJobSecurityStatus / SeasonStartJobSecurityStatus enum JobSecurityStatus**, CurrentStatRankPosition, DominantArchetype enum CoachTalentArcheType, EarnedContractPoints_ThisYear/LastYear/TwoYearsAgo -300–300, IsNIL, Name str18, **NumContractOffers 4b 0–12**, PersuadeAttempts, ProgramPointsBudgetAllocationPosture ref, SpecialtyType enum, YearsCoaching 7b.

## 6. Team (assetId 6498740, 424 members) — coach/recruiting-relevant subset
Coach refs: `HeadCoach`, `OffensiveCoordinator`, `DefensiveCoordinator`, `SpecialTeamsCoach` (all ref Coach); staff refs HeadScout/HeadTrainer/GeneralManager/PlayerPersonnel/Owner; `StaffPersonBlacklist : StaffPerson[]`; `UserCharacter : UserEntity` (user-controlled-school signal); `UserCoachExpressedInterestCount` 0–100.
Recruiting: **`RecruitingBoard : RecruitingBoard`**, `CommittedPlayers : Player[]`, `ContractOfferBlacklist : Player[]`, `LastWeekCommittedRecruits` 0–35, `LastSeasonTransfersLost` 0–30, `LastSeasonTransfersSigned` 0–35, `TopClassRank` 0–250, `TopClassConferenceRank` 0–31, **`MySchoolTrackingTable : MySchoolTrackingTable`**, `SchoolPipelineInfluenceList : SchoolPipelineInfluence[]`, `PipelineInitialInfluence : PipelineValueTable` (43 int columns 0–1000, one per Pipeline region).
Prestige/identity: **`TeamPrestige` int 0–10 (4b)**, `TeamPrestigeBias` 0–50 (6b), `PrestigeDisplay` string maxLen 5 (e.g. "4.5"), `PrestigeRank` 0–255 (8b), `TeamRank`, `TeamIndex` 8b def 255, `YearStartOfFootballProgram` 11b.
Program points: `ProgramPointBudget` 0–30000 (15b), `RemainingProgramPoints` -6000–30000 (16b), `HeadCoachProgramPointBudget`/`OffensiveCoordinatorPointBudget`/`DefensiveCoordinatorPointBudget` 0–2000 (11b), Spent buckets (Recruit/NIL/Staff/Facilities/ProgramPointsSpent 15b), grade fields ProgramPointsBudgetGrade/BrandExposureGrade/ConferencePrestigeGrade/ProgramTraditionsGrade/StadiumAtmosphereGrade (LetterGrade), RolloverProgramPoints, AccumulatedCoachContractGoalsPoints, CoachContractGoalsProgramPoints, contract goals HC/OC/DCContractGoal1–3 (ref CoachContractGoal).
Rosters: `Roster : Player[]`, `PracticeSquad : Player[]`, `DepthChart` ref.
Conference: **no Conference field on Team** (confirmed — membership only via Conference.TeamSlots), but `Conference.ConferenceEnum : CollegeConferences` gives conference identity (see §8).

### MySchoolTrackingTable (35 members) — Team.MySchoolTrackingTable target, ANSWERS open question
Grades (LetterGrade): AcademicPrestigeGrade, AthleticFacilitiesGrade, BrandExposureGrade, CampusLifestyleGrade, ChampionshipContenderGrade, **CoachPrestigeGrade, CoachStabilityGrade**, ConferencePrestigeGrade, ProgramTraditionGrade, StadiumAtmosphereGrade, ProPotentialGrade{QB,RB,WR,TE,OL,DL,LB,DB,K,P}.
Plus raw stats: AthleticFacilitiesScore 0–2000, CampusLifestyleScore 0–134, BrandExposure{GamesOfTheWeek,NationalTV,Streaming}{Played,Wins} 0–255, ChampionshipContender{CurrentYear,YearPlus1..3}Rank 0–140, PlayingStyle{Grade,Rank,Stat}ByPlayerTypeTable (record). These grades feed the 14 RecruitingMotivationType categories (`RecruitingMotivationToMySchoolGradeMapping` schema exists) — CoachPrestigeGrade/CoachStabilityGrade are exactly what a coaching change should dent for our consequence engine.

## 7. Pipelines
`SchoolPipelineInfluence` (assetId 8224453, 3): `InfluenceLevel : PipelineInfluenceLevel (3b) {Unrecognized=0, NicheInterest=1, Respected=2, Popular=3, HouseholdName=4, CulturalPillar=5, Invalid=7}`, `InfluenceValue : int 0–1000 (10b)`, `Pipeline : Pipeline (6b)`.
`Pipeline` enum (6b, 43 real values): Alabama=0, Arizona=1, Arkansas=2, BigApple=3, BigSky=4, CentralFlorida=5, Colorado=6, EastTexas=7, Hawaii=8, Illinois=9, Indiana=10, Iowa=11, Kansas=12, Kentucky=13, Louisiana=14, MetroAtlanta=15, Michigan=16, Minnesota=17, Mississippi=18, Missouri=19, Nebraska=20, Nevada=21, NewEngland=22, NewMexico=23, NorthCarolina=24, NorthFlorida=25, NorthTexas=26, NorthernCalifornia=27, Ohio=28, Oklahoma=29, PacificNorthwest=30, Pennsylvania=31, SouthCarolina=32, SouthFlorida=33, SouthGeorgia=34, SouthernCalifornia=35, SouthwestTexas=36, Tennessee=37, Tidewater=38, Utah=39, WestVirginia=40, Wisconsin=41, International=42, **Invalid=44** (so yes, the save-side enum has an Invalid member; also Last_=42, Count_=43).
`RecruitingTunables` pipeline dials: `InfluenceRequiredPerPipelineLevel : int[]`, `RecruitInitialInfluencePerPipelineLevel : int[]`, `ChanceToSwayBoostFromPipelineInfluenceLevel : int[]`; StaffHiringTuning: `HalfPipelinePoints` 25, `MaxPipelinePoints` 50 (coach PrimaryPipeline matters in hiring interest).

## 8. Conference (base **Enum**, 42 members)
Conference rows double as enum-table entries. Fields beyond stage-1: `ConferenceEnum : CollegeConferences` — **direct conference identity**: {ACC=0, BigTen=1, Big12=2, AAC=3, CUSA=4, Independents=5, MAC=6, MWC=7, Pac12=8, SEC=9, SunBelt=10, None=13, DefunctConference=14, Invalid_=255}. Also ChampionshipDay/GameTime/GameType/Stadium, ConferenceChampTrophyAsset, ConferenceRotation:ScheduleStructureYear[], ConferenceStartWeek, Divisions:Division[], NumConferenceGames, NonconferenceTeamPairs, ProtectedOpponents, `TeamSlots : Team[]`, Name, colors/logos. So team→conference = walk Conference.TeamSlots (as before) but the conference's identity/prestige bucket is machine-readable via ConferenceEnum.

## 9. Player (288 members) — recruiting-relevant fields only (rest matches stage-1)
`AbsoluteTransferChance` int -1–100 (7b, def -1), `Age` 6b, `BaseNILValue` -255–1023 (11b), `CurrentNILCompensation` 10b, `HomePipeline : Pipeline` def Invalid, `IdealRecruitingPitch : RecruitingPitchType` def Invalid, `IsNIL` bool, `JerseyNum` 7b, `Motivation1/2/3 : MotivationType` (**the pro-style enum, see below — NOT RecruitingMotivationType; stage-1 note conflated them**), `OverallRating` 7b 0–100, `PLYR_HOME_STATE : StateName` (6b, 52 members: 50 states + NonUS=50 + INVALID=51), `PLYR_HOME_TOWN` str26, `PLYR_PREVTEAMID` int 0–2047 (11b), `Position : PositionE` (6b), `PrevTeamIndex` 8b, `ProspectStarRating : ProspectQuality` (3b) {ONE_STAR=0..FIVE_STAR=4, Invalid=6}, `RecruitingDealbreaker : RecruitingMotivationType` (4b) def Invalid, `RedshirtStatus` (2b) {Eligible=0, Ineligible=1, Current=2, Previous=3}, `SchoolYear` (3b) {Freshman=0, Sophomore=1, Junior=2, Senior=3, Invalid_=5}, `TeamIndex` 8b def 255, `TraitDevelopment` (3b) {Normal=0, College_Impact/Star=1, College_Star/Superstar=2, College_Elite/XFactor=3, Hidden=4, Invalid_=5}.

## 10. Key enums (full member lists; aliases share values)

- **CoachPosition** (8b): First_/HeadCoach=0, OffensiveCoordinator=1, DefensiveCoordinator=2, NumCollegeCoaches/SpecialTeams=3, Owner=4, Scout=5, Trainer=6, GeneralManager=7, PlayerPersonnel=8, Max_=9, Invalid_=255. (madden-franchise returns the FIRST alias name for a value — hence 'First_' may read back for HeadCoach=0; write either alias.)
- **CoachLeaveReason** (3b): None=0, Fired=1, Retired=2, Pro=3, NewJob=4, ContractEnding=5.
- **ContractOfferStatus** (3b): Accepted=0, Declined=1, Pending=2, Withdrawn=3, NoOffer=4, Invalid_=7.
- **StaffPersonContractStatus** (4b): First_Active/Signed=0, Expiring=1, First_Pending/PendingFire=2, PendingNFL=3, PendingRenewal=4, Last_Active/PendingRetire=5, Last_Pending/PendingHire=6, FreeAgent=7, Retired=8, Deleted=9, None=10.
- **JobSecurityStatus** (3b): Safe=0, SafeForNow=1, Low=2, HotSeat=3, Invalid=4.
- **LetterGrade** (4b): Aplus=0, A=1, Aminus=2, Bplus=3, B=4, Bminus=5, Cplus=6, C=7, Cminus=8, Dplus=9, D=10, Dminus=11, F=12(Last_), COUNT/Incomplete=13.
- **RecruitStage** (4b): First_/Top10=0, Top5=1, Top3=2, Battle=3, SoftCommitted=4, HardCommitted=5, Last_/Signed=6, Count_=7, **Invalid=8**. → ANSWERS open question: there is NO 'None/Open' literal; **uncommitted = Top10/Top5/Top3/Battle**; a virgin row defaults to Invalid. For our decommit writer, drop a recruit back to Top10/Top5/Top3 (Top3 keeps old school re-recruitable with an edge).
- **RecruitStageAdvance** (3b): First_/None=0, Advance=1, **Decommit=2**, InstantCommit=3(Last_), Count_=4, Invalid=5. → Decommit=2 is the engine's own decommit trigger; setting `RecruitStageAdvance='Decommit'` and letting the week advance process it is the low-risk path (engine-side handling UNVERIFIED on live save; the alternative is direct RecruitStage+board edits).
- **ScholarshipStatus** (3b): First_/None=0, **Revoked=1**, New=2, Offered=3, **Committed=4**(Last_), Count_=5, Invalid=6. (sendFreeCommits' "non-Offered" values are None/Revoked/New/Committed; a real commit normally carries Committed — force-commit writing 'Offered' diverges from engine convention, worth testing.)
- **RecruitingClass** (4b): HighSchool=0, JuniorCollege_Sophomore=1, JuniorCollege_Junior=2, JuniorCollege_Senior=3, Transfer_Freshman=4, Transfer_Sophomore=5, Transfer_Junior=6, Transfer_Senior=7, Count_=8, Invalid=9.
- **GemBust** (3b): NORMAL=0, GEM=1, BUST=2(Last_), Count_/HIDDEN=3, Invalid=4.
- **RecruitingPitchType** (5b): CollegeExperience=0, TeamPlayer=1, CampusPersonality=2, ItsGameTime=3, Prestigious=4, StudentOfTheGame=5, HometownHero=6, ProveYourself=7, TheClutch=8, TVTime=9, CoachsFavorite=10, Aspirational=11, ToTheHouse=12, FootballInfluencer=13, TimeToGetToWork=14, Starter=15, Grassroots=16, ConferenceSpotlight=17, SundayBound=18, WorkHorse=19(Last_), Count_=20, Invalid=22. (20 real pitches — force-commit's DEALBREAKER_TO_PITCH covers a 10-pitch subset.)
- **RecruitingMotivationType** (4b) — dealbreakers/motivations, 14 real: AcademicPrestige=0, AthleticFacilities=1, BrandExposure=2, CampusLifestyle=3, ChampionshipContender=4, **CoachPrestige=5, CoachStability=6**, ConferencePrestige=7, PlayingStyle=8, PlayingTime=9, ProPotential=10, ProgramTradition=11, ProximityToHome=12, StadiumAtmosphere=13(Last_), Count_=14, Invalid=15. → **A recruit whose RecruitingDealbreaker is CoachPrestige/CoachStability is the natural priority target for carousel-driven decommits.**
- **MotivationType** (4b, Player.Motivation1-3 — pro/Madden-style): None=1, NoIncomeTax=2, WarmWeatherState=3, BigMarket=4, ChampionshipContender=5, TeamPrestige=6, SchemeFit=7, ToptheDepthChart=8, TeamHasFranchiseQB=9, MentoratPosition=10, HeadCoachHistoricRecord=11, CloseToHome=12, Count=13.
- **ProspectQuality** (3b): ONE_STAR=0, TWO_STAR=1, THREE_STAR=2, FOUR_STAR=3, FIVE_STAR=4(Last_), Count_=5, Invalid=6.
- **PipelineInfluenceLevel** (3b): Unrecognized=0, NicheInterest=1, Respected=2, Popular=3, HouseholdName=4, CulturalPillar=5(Last_), Count_=6, Invalid=7.
- **SeasonStage** (2b): PreSeason=0, NFLSeason=1, OffSeason=2.
- **SeasonWeekType** (4b): PreSeason=0, RegularSeason=1, BowlSeason1=2, BowlSeason2=3, BowlSeason3=4, NationalChampionship=5, OffSeason=6, Invalid=8.
- **CoachExperienceSpeed** (LeagueSetting.CoachXPSpeedSetting): Slowest=0, Slower=1, Normal=2, Faster=3, **Fastest=4**, Count=5. → ANSWERS open question (also TalentProgressSpeed: Slowest=0, Slower=1, Slow=2, Normal=3, Fast=4).
- **CollegeConferences**: see §8.
- **PositionE** (6b): QB=0, HB=1, FB=2, WR=3, TE=4, LT=5, LG=6, C=7, RG=8, RT=9, LE=10, RE=11, DT=12, LOLB=13, MLB=14, ROLB=15, CB=16, FS=17, SS=18, K=19, P=20, LS=21, KR=22, PR=23, KOS=24, 3DRB=25, GAD=26, PWHB=27, SLWR=28, RLE=29, RRE=30, RDT=31, NT=32, SUBLB=33, SLCB=34(Last_), Count_/**HC_CFM=35**, OC_CFM=36, DC_CFM=37, Owner_CFM=38, Invalid_=63. (Coach roles 35–38 sit ABOVE Last_=34 — they are depth-chart-context pseudo values, not expected in Player.Position; offense 0–9 / defense 10–18 rule holds for real positions, with 22–34 special-teams/situational slots to explicitly skip.)
- **DraftPositionE** (5b): QB=0, HB=1, FB=2, WR=3, TE=4, T=5, G=6, C=7, DE=8, DT=9, OLB=10, MLB=11, CB=12, FS=13, SS=14, K=15, P=16, LS=17, R=18, Count_=19, Invalid_=31.
- **StateName** (6b): Alabama=0 … Wyoming=49 (alphabetical), NonUS=50, INVALID=51.
- **VisitActivityType** (4b): AttendLecture=0, TeamWorkout=1, PodcastInterview=2, CampusTour=3, AttendTeamMeeting=4, OneOnOneCoaching=5, TeamDinner=6, TrophyTour=7, AttendPractice=8, AttendPositionMeeting=9, MeetAlumni=10, TeamHistory=11, FamilyVisit=12, Tailgate=13(Last_), Count_=14, Invalid=15.
- **RecruitingActionIntensity** (3b): SoftSell=0, HardSell=1, Sway=2(Last_), Count_=3, Invalid_=4.
- **ScholarshipBonusOfferTier**: Bronze=0, Silver=1, Gold=2, Platinum=3(Last_), Count_=4, Invalid_=5.
- **RecruitOfferType**: Starter=0, SecondString=1, Bench=2, WalkOn=3.

## 11. History / news / transactions
- `TransactionHistoryManager` (Module): `TransactionList : TransactionHistoryEntry[]` (ring buffer, `TransactionListBegin` 0–1023, `TransactionId` 0–16777215), `WeekSummaryList : TransactionWeekSummaryEntry[]`; handlers incl. `HandleUpdateCoachContractStatus` (script body reads `event.NewContractStatus`) and `ClearCoachTransactionHistoryEntries` — the save DOES keep a coach-transaction feed we may be able to read for past coaching changes (row schema is the generic `TransactionHistoryEntry`: **OldTeam ref, NewTeam ref, SeasonStage, SeasonWeek 0–30, SeasonYear 0–100, TransactionId** — no person ref in the base entry; subclass entries like `PlayerPositionChangeHistoryEntry`, `DraftPickTransactionHistoryEntry`, `EditDraftClassTransactionHistoryEntry` exist. UNVERIFIED which subclass, if any, is used for coach moves — inspect a live save's tables named `*TransactionHistoryEntry`).
- `HistoryEntry` (6): CurrentStage/CurrentWeek/CurrentYear, ExperienceValue 0–64000, IsSchemeFit, MiscValue -1000–3000, Person:UserEntity, ProgressionValue, Source(record) — used by `Franchise.FreeAgentHistoryEntries` and `Team.HistoryEntries`.
- `Transaction` subclasses (24, in-memory week-advance jobs, not persisted logs): AdvanceSeasonWeekTransaction, ProcessEndOfRecruitingCycleTransaction, RecruitingAdvanceBoardsTransaction, RecruitGenerationTransaction, DeleteExcessFreeAgentsTransaction, etc.
- News: `NewsManager`, `NewsTuning`, `BreakingNewsEvent/Reaction`, `NewsArticleUpdateEvent`, `NewsTweetUpdateEvent` (transient event plumbing; no obvious persisted news-article table in the schema bundle).

## 12. Relationship graph (refs verified from schema types)

```
Franchise ─┬─ Teams/FCSTeams/HighSchoolTeams : Team[]
           ├─ RecruitingClassPlayers : Recruit[]      ← HS class
           ├─ Transfers : Recruit[]                   ← TRANSFER PORTAL
           ├─ FreeAgentCoaches / RetiredCoaches / RecruitingClassCoaches / DraftClassCoaches : Coach[]
           ├─ SeasonInfo, LeagueSetting, RecruitManager, HistoryManagerRef
           └─ FreeAgents/RetiredPlayers : Player[]

StaffHiringEval ─┬─ JobOpenings : JobOpening[]
                 └─ OutstandingStaffPersonOffersList : StaffPersonContractOffer[]
JobOpening ─┬─ Team → Team          ├─ PrevCoach → Coach
            ├─ SelectedCoach → Coach└─ ContractOfferList → StaffPersonContractOffer[]
StaffPersonContractOffer ─┬─ StaffPerson → StaffPerson (usually Coach; validate table)
                          ├─ Team → Team (hiring)  └─ StaffPersonTeam → Team (current)

Team ─┬─ HeadCoach/OffensiveCoordinator/DefensiveCoordinator/SpecialTeamsCoach → Coach
      ├─ RecruitingBoard → RecruitingBoard ── Recruits → RecruitTarget[] (or UserRecruitTarget[] rows for the user school)
      ├─ CommittedPlayers → Player[]
      ├─ MySchoolTrackingTable → MySchoolTrackingTable (CoachPrestigeGrade/CoachStabilityGrade…)
      ├─ SchoolPipelineInfluenceList → SchoolPipelineInfluence[] → SchoolPipelineInfluence
      └─ Roster → Player[]
Conference ─ TeamSlots → Team[] ; ConferenceEnum : CollegeConferences
RecruitTarget ─ Recruit → Recruit ─┬─ Player → Player
                                   └─ TopSchoolsList → ProspectTargetSchool[] {TeamId=TeamIndex, TeamInfluence}
Coach.TeamIndex / Player.TeamIndex ──(int join)── Team.TeamIndex ; ProspectTargetSchool.TeamId ──(int join)── Team.TeamIndex
```

## 13. Open questions: answered / new

Answered here:
1. **Uncommitted RecruitStage literal**: none exists — uncommitted is Top10/Top5/Top3/Battle; field default is `Invalid`.
2. **RecruitStageAdvance domain**: None=0, Advance=1, **Decommit=2**, InstantCommit=3, Invalid=5 — Decommit is a first-class engine trigger.
3. **ScholarshipStatus domain**: None=0, Revoked=1, New=2, Offered=3, Committed=4, Invalid=6.
4. **ProspectTargetSchool** literally exists (assetId 6716465); TeamId 0–2047, TeamInfluence 0–65535; only owner is Recruit.TopSchoolsList.
5. **User board**: `UserRecruitTarget` subclass table (RecruitTarget + IsFavorite + 2 feedback arrays); reachable via the user Team's RecruitingBoard.Recruits like any other.
6. **MySchoolTrackingTable** structure fully dumped (35 fields incl. CoachPrestigeGrade/CoachStabilityGrade and the ten ProPotentialGrade* columns).
7. **CoachXPSpeedSetting values**: CoachExperienceSpeed Slowest/Slower/Normal/Faster/Fastest (0–4).
8. **Pipeline enum has Invalid** (=44); full 43-region list with indices; PLYR_HOME_STATE = StateName (52 members).
9. **StaffPerson vs Coach in offers**: schema-typed to base StaffPerson — non-Coach staff offers are schema-legal; validate ref target.
10. **Recruits vs players**: separate `Recruit` table wrapping a `Player` ref; transfer portal = `Franchise.Transfers : Recruit[]` with `Class` = Transfer_*.
11. **Transfer portal seeding**: TransferStartingRecruitData (per star rating: stage + commit-score range), TransferDefaultCommitScore=1000.
12. **NumContractOffers overflow**: MaxOffersPerCoach tuning default 4 vs field max 12 — practical overflow unlikely; library clamping still UNVERIFIED.
13. **Coach pseudo-positions**: HC_CFM/OC_CFM/DC_CFM/Owner_CFM are PositionE 35–38 (above Last_), used in depth-chart/CFM contexts, not Coach.Position (which is CoachPosition).
14. **Team prestige semantics**: TeamPrestige int 0–10 (stars×2), PrestigeDisplay is a 5-char STRING (UI text), PrestigeRank 0–255 rank, TeamPrestigeBias 0–50 modifier. Use TeamPrestige for the follow-coach rule; engine's own analogs: StaffHiringTuning.PrestigeCompareScoreSpline + PoachThresholdHC/Coord.

Still open (need live carousel-window save):
- Exact tick when JobOpening rows are created/cleared (reaction names suggest post-season week starts / StaffHiringPeriodEnd; unproven).
- Whether SelectedCoach is set before or after Filled flips; IsEmergentJobOpening trigger (name suggests mid-carousel openings created when a coach leaves to fill another job).
- Whether the engine reads OfferIndex vs array order when resolving hires.
- Whether writing `RecruitStageAdvance='Decommit'` on a committed recruit cleanly decommits on week advance (and what it does to TopSchoolsList/board rows).
- COACH_LASTTEAMFIRED/RESIGNED value semantics (TeamIndex vs other id).
- Whether coach moves land in TransactionHistoryEntry rows (and under which subclass).
- Engine tolerance for TeamInterestInStaffPerson > 100 (schema max 280).

## 14. Gotchas for our tool
- Alias-first enum reads: madden-franchise formats enum values to member names and may pick an alias (`First_` for CoachPosition 0, `First_Active` for ContractStatus 0, `Top10`↔`First_`). Compare by underlying value or accept alias sets, never exact-match a single name for aliased values.
- `Invalid` members frequently sit OUTSIDE the bit range implied by real members (e.g. RecruitStage Invalid=8 in 4 bits, Pipeline Invalid=44 in 6 bits — fits; but CoachPosition Invalid_=255 needs the full 8 bits). Always use the enum `_maxLength`, not member count, for width.
- `PrestigeDisplay` is a string — don't parse it as a number for logic; use TeamPrestige.
- Recruit default `Class` is Transfer_Junior and default RecruitStage/StageAdvance are Invalid — a blank Recruit row is NOT a valid HS recruit; set all three when minting rows.
- `CurrentScholarshipBonus` can be negative (-200..50); `ProspectInfluenceDelta` too (-200..1023). Don't clamp at 0 when writing.
- The recruiting-relevant JSON (`schema-relevant-tables.json`) has `bits` DERIVED for ints; for byte-exact packing read the live table's offsetTable.
