# 05 — Dynasty Calendar, Stages & Programmatic Stage Detection

Documentation suite for **CoachCarouselRecruitTool**. Synthesized 2026-07-14. Source tags as in doc 01
(`[schema] [dump] [diff] [carousel] [fc] [web]`). Everything here is observed on real saves unless tagged
UNVERIFIED.

---

## Table of contents

1. [SeasonInfo — the calendar singleton](#1-seasoninfo--the-calendar-singleton)
2. [Field inventory](#2-field-inventory)
3. [Stage & week-type enums](#3-stage--week-type-enums)
4. [Observed values across five real saves](#4-observed-values-across-five-real-saves)
5. [Period flags at the two stages we care about](#5-period-flags-at-the-two-stages-we-care-about)
6. [The dynasty-year timeline model](#6-the-dynasty-year-timeline-model)
7. [Which stage is the coach carousel?](#7-which-stage-is-the-coach-carousel)
8. [Which stage is the first offseason (transfer-portal) recruiting stage?](#8-which-stage-is-the-first-offseason-transfer-portal-recruiting-stage)
9. [Programmatic detection recipes](#9-programmatic-detection-recipes)
10. [Gotchas](#10-gotchas)
11. [Sources](#sources)

---

## 1. SeasonInfo — the calendar singleton

- Table `SeasonInfo`, uniqueId **3123991521**, tableId 4141, exactly **1 record**, 59 fields. Unique by name.
  Reached directly or via `Franchise.SeasonInfo`. `[dump §1,§11][schema §4]`
- The KivJoy carousel tool performs zero stage checks; force-commit reads only `CurrentWeek`. Our tool should
  be the first to gate properly on this table. `[carousel §5][fc §3.1]`

## 2. Field inventory

### 2.1 Ints — [schema §4]

`BaseCalendarYear` (13b, = 2026 in this dynasty, constant) · `CurrentSeasonYear` (0–4095; **=
BaseCalendarYear + CurrentYear**, verified in all saves `[diff §1.1]`) · `CurrentYear` (7b, dynasty year,
0-based) · `CurrentWeek` (5b 0–31, 0-based) · **`CurrentOffseasonStage` (4b 0–15)** ·
`OffseasonNumStages` (= 9) · `NumberOffseasonAdvances` (0–9; advances within the current offseason, reset to
0 by preseason) · `CurrentFreeAgencyStage` / `TotalFreeAgencyStages` (= 5; NFL-side leftovers) ·
`HSRecruitingCurrentMaxTopSchools` (= 10) / `HSRecruitingNextStopMaxTopSchools` ·
`RegularSeasonLastWeekScheduled` · `RegularSeasonWeekConferenceChampionship` · `PostSeasonNumWeeks` ·
`PreseasonWeekCount` · `MaxYears` (= 30) · `BaseSuperBowlNumber` · `NflseasonWeekCount`.

### 2.2 Enums — [schema §4]

- `CurrentStage : SeasonStage` (2 bits)
- `CurrentWeekType : SeasonWeekType` (4 bits)

### 2.3 Bools (period gates) — full list [schema §4]

Recruiting/portal group (the ones our tool gates on, **bold** = observed true at the portal stage):
**`IsRecruitingPeriodActive`**, **`IsCommittmentPeriodActive`** (sic, double-t), **`IsSigningPeriodActive`**,
**`IsScholarshipPeriodActive`**, **`IsVisitingPeriodActive`**, **`IsPitchingPeriodActive`**,
**`IsScoutingPeriodActive`**, **`IsTransferPortalNewlyAvailable`**, **`IsTransferSignPeriodActive`**.

Carousel/staff group: `IsCarouselPeriodActive`, `IsStaffHiringPeriodActive`,
`IsStaffHiringCreateOfferPeriodActive`, `IsStaffHiringEvaluateOfferPeriodActive`,
`IsCoachDemandReleasePeriodActive`.

Others: IsAnnualAwardPeriodActive, IsGoalsPeriodActive, IsLeagueStarted, IsTradingActive (def true),
IsDraft*/IsFreeAgent*/IsReSign*/IsPracticeSquad*/IsRelocation* (NFL-side), IsPlayerDemandReleasePeriodActive,
IsGraduatingSeniorNILExclusionPeriodActive, IsRebuildingPeriodActive, IsWeeklyAwardPeriodActive,
IsFantasyDraftActive, IsLiveSeasonsLeague, IsCombineComplete, IsProDayComplete,
IsFacilitiesIncludedInFreeingUpBudgetPeriodActive (def true), IsSalaryCapRosterSizeIncActive.

**There is NO offseason-stage enum** — `CurrentOffseasonStage` is a plain int cursor over
`OffseasonNumStages` (9). The transfer-portal window is signposted by the two portal booleans. `[schema §4]`

## 3. Stage & week-type enums

```
SeasonStage    (2b): PreSeason = 0, NFLSeason = 1, OffSeason = 2
SeasonWeekType (4b): PreSeason = 0, RegularSeason = 1, BowlSeason1 = 2 (alias FirstPlayoff_),
                     BowlSeason2 = 3, BowlSeason3 = 4, NationalChampionship = 5 (alias LastPlayoff_),
                     OffSeason = 6, Invalid = 8
```
`[schema §4,§10]` — Note the college save reuses the Madden-named `NFLSeason` member for the entire regular +
post season; `CoachTransactionHistoryEntry.SeasonStage` stamps confirm it live (`'NFLSeason'` on all real
carousel rows). `[dump §6]` In-season `CurrentStage`/`CurrentWeekType` values were never directly observed in
our saves (all were PreSeason or OffSeason) — the in-season pairing (NFLSeason × RegularSeason/BowlSeason*)
is inferred from the enum + history stamps; UNVERIFIED by direct observation.

## 4. Observed values across five real saves

All the same dynasty (user coach "Ryan VanVuren", Coach row 433). `[diff §0]`

| Save | CurrentStage | CurrentWeekType | Week | SeasonYear | Year | OffseasonStage | OffseasonAdvances | User team |
|---|---|---|---|---|---|---|---|---|
| DYNASTY-QQ | PreSeason | PreSeason | 0 | 2026 | 0 | 0 | 0 | Arizona State (TeamIndex 4) |
| DYNASTY-QQ-AUTOSAVE | PreSeason | PreSeason | 0 | 2026 | 0 | 0 | 0 | Arizona State (identical clone) |
| DYNASTY-QQ2b | PreSeason | PreSeason | 0 | 2028 | 2 | **9** | 0 | Arizona State |
| DYNASTY-QQ2b-PIPELINES | PreSeason | PreSeason | 0 | 2028 | 2 | 9 | 0 | Arizona State |
| DYNASTY-QQ2 | **OffSeason** | OffSeason | **1** | **2029** | **3** | **2** | **2** | **LSU (TeamIndex 43)** |

Full 60-field SeasonInfo dumps for every save: `research/dumps/save-diff.json → snapshots.<save>.calendar`
and `research/dumps/*/SeasonInfo.3123991521.sample.json`. `[diff §1]`

Key semantics extracted:

- `CurrentSeasonYear = BaseCalendarYear + CurrentYear` (2026 + 3 = 2029) — holds in all saves. `[diff §1.1]`
- `CurrentOffseasonStage`: **0** = dynasty has never entered an offseason; **2** = transfer-portal stage;
  **9** (= OffseasonNumStages) = offseason completed — **the counter parks at 9 through the following
  preseason** and does not reset to 0 until (UNVERIFIED exactly when) the next offseason begins. `[diff §1.1]`
- `NumberOffseasonAdvances` tracks advances within the current offseason only; reset to 0 by preseason. `[diff §1.1]`
- `IsAnnualAwardPeriodActive` flips true at the first offseason and stays true thereafter (useless as a
  window signal). `[diff §1.1]`

## 5. Period flags at the two stages we care about

### 5.1 Offseason stage 2 (transfer portal) — QQ2, verbatim [diff §1.2]

TRUE: `IsCommittmentPeriodActive, IsTransferPortalNewlyAvailable, IsTransferSignPeriodActive,
IsVisitingPeriodActive, IsSigningPeriodActive, IsScholarshipPeriodActive, IsRecruitingPeriodActive,
IsPitchingPeriodActive, IsScoutingPeriodActive, IsAnnualAwardPeriodActive, IsTradingActive, IsLeagueStarted`

FALSE (notably): `IsCarouselPeriodActive, IsStaffHiringPeriodActive, IsStaffHiringCreateOfferPeriodActive,
IsStaffHiringEvaluateOfferPeriodActive, IsGoalsPeriodActive, IsCoachDemandReleasePeriodActive`

### 5.2 Preseason (QQ, QQ2b) [diff §1.2]

All the recruiting/transfer flags above are **false**; `IsGoalsPeriodActive` and
`IsFacilitiesIncludedInFreeingUpBudgetPeriodActive` are **true**.

## 6. The dynasty-year timeline model

Assembled from SeasonInfo values + CoachTransactionHistoryEntry stamps + table lifecycles: `[dump §6][diff]`

```
Dynasty year N
├─ PreSeason (CurrentStage='PreSeason', week 0)
│    · fresh recruit class present, all RecruitStage='Top10'; boards wiped (RecruitTarget empty)
│    · previous year's CoachTransactionHistoryEntry rows STILL readable
├─ NFLSeason (CurrentStage='NFLSeason', weeks 0..20)
│    · regular season through week RegularSeasonLastWeekScheduled (15 in yr 0; 14 in yrs 2–3!)
│    · conference championships (16 in yr 0; 15 later), then bowls/playoffs (PostSeasonNumWeeks=4)
│    · CAROUSEL WINDOW ≈ weeks 15–20: JobOpening + StaffPersonContractOffer rows live;
│      CoachTransactionHistoryEntry written (bulk at week 18); README lore says "weeks 14–16" [carousel §5]
├─ OffSeason (CurrentStage='OffSeason', CurrentOffseasonStage 0..8, weeks within stage)
│    · by stage 2: carousel fully resolved (Team.HC/OC/DC + Coach rows final; no Pending statuses);
│      JobOpening/offer tables freed; recruiting + portal flags all true  ← OUR TOOL'S WINDOW
│    · stage counter parks at 9 when done
└─ Year N+1 PreSeason (boards wiped, new class generated, CurrentYear++)
```

## 7. Which stage is the coach carousel?

- The carousel runs **in-season, during CurrentStage='NFLSeason' weeks ~15–20** (bowl/playoff weeks), NOT in
  the offseason: every real CoachTransactionHistoryEntry row is stamped `SeasonStage='NFLSeason'`,
  `SeasonWeek` 15–20 with the bulk at 18. `[dump §6]`
- The StaffHiringEval lifecycle handlers (`HandleChampionshipWeekStart`, `HandleRegularBowlWeekStart`,
  `HandleNationalChampionshipStart`, `HandleOffseasonStart`, `HandleStaffHiringWeekAdvance`) corroborate:
  carousel activity spans bowl-season week starts into offseason start. `[schema §2]`
- `SeasonInfo.IsCarouselPeriodActive` is the designed live flag, plus `IsStaffHiringPeriodActive` /
  `IsStaffHiringCreateOfferPeriodActive` / `IsStaffHiringEvaluateOfferPeriodActive` for sub-phases. All were
  false in our five saves (none captured in-window) — **their in-window values are UNVERIFIED**; stage-1 code
  analysis of the schema says they flip true during the window. `[diff §1.3][schema §4]`
- Pragmatic in-window detector that does not depend on the unverified flags:
  `JobOpening` non-empty row count > 0 (or `StaffPersonContractOffer` pending rows with valid Team refs).
  `[dump §10][diff §2.4]`
- KivJoy README operational lore: "launch during Weeks 14–16 in-season, or offseason weeks when coaching
  changes occur." `[carousel §5]`

## 8. Which stage is the first offseason (transfer-portal) recruiting stage?

- **`CurrentStage='OffSeason'` + `CurrentOffseasonStage=2`**, with the recruiting/portal flag set of §5.1
  (`IsTransferPortalNewlyAvailable` + `IsTransferSignPeriodActive` + `IsRecruitingPeriodActive` +
  `IsCommittmentPeriodActive` + `IsSigningPeriodActive` + `IsScholarshipPeriodActive` all true) — verified
  live on QQ2. `[diff §1.2][dump §11]`
- UNVERIFIED: whether stages 0–1 ever show recruiting/portal flags true (no save at stages 0–1 was
  available); "stage 2 = first recruiting stage" is consistent with `NumberOffseasonAdvances=2` at week 1 of
  the window, but the exact stage-0/1 content is unobserved. `[diff remaining-unknowns]`
- The portal stage lasts multiple weeks (`CurrentWeek` increments within it; force-commit's README targets
  "week 4 of the transfer portal period"). `[fc §3.1][web §2]`
- At week 1 of the stage, transfer players had NOT yet materialized as `Class=Transfer_*` Recruit rows —
  plan tool behavior for both "portal empty" and "portal populated" states. `[diff §3]`

## 9. Programmatic detection recipes

```js
const si = file.getTableByUniqueId(3123991521);      // SeasonInfo
await si.readRecords();
const r = si.records[0];

const cal = {
  stage:            r.getValueByKey('CurrentStage'),            // 'PreSeason' | 'NFLSeason' | 'OffSeason'
  weekType:         r.getValueByKey('CurrentWeekType'),
  week:             r.getValueByKey('CurrentWeek'),             // 0-based
  dynastyYear:      r.getValueByKey('CurrentYear'),             // 0-based
  displayYear:      r.getValueByKey('CurrentSeasonYear'),       // BaseCalendarYear + CurrentYear
  offseasonStage:   r.getValueByKey('CurrentOffseasonStage'),   // 0 never; 2 portal; 9 completed (parks!)
  offseasonAdvances:r.getValueByKey('NumberOffseasonAdvances'),
  carouselFlag:     r.getValueByKey('IsCarouselPeriodActive'),  // in-window value UNVERIFIED
  portalOpen:       r.getValueByKey('IsTransferPortalNewlyAvailable')
                 && r.getValueByKey('IsTransferSignPeriodActive'),
};

// GATE 1 — "run the consequence engine now" (the transfer-portal stage):
const isPortalStage =
  cal.stage === 'OffSeason' &&
  cal.offseasonStage === 2 &&
  r.getValueByKey('IsTransferSignPeriodActive') === true;      // verified-live combination [diff §6a]

// GATE 2 — "the carousel is running right now" (for optional in-window snapshots):
const jobOpening = file.getTableByUniqueId(263453863);
await jobOpening.readRecords();
const liveOpenings = jobOpening.records.filter(x => !x.isEmpty).length;
const isCarouselWindow = cal.carouselFlag === true || liveOpenings > 0;   // flag UNVERIFIED, rows proven

// GATE 3 — "has a carousel EVER run in this dynasty" (first-run sanity):
const carouselEverRan = jobOpening.header.nextRecordToUse > 0;            // 0 only if never [diff §6b]
```

Dynasty identity for the local DB: `String(Franchise.LeagueID)` (uid 2226370608, record 0) +
`CurrentSeasonYear`. `[pipe §2,§3]`

## 10. Gotchas

1. **Season-structure fields are NOT constants**: `RegularSeasonLastWeekScheduled` = 15 and ConfChamp week =
   16 in year 0, but **14/15 in years 2–3 of the same dynasty**. Never hardcode. `[diff §1.3]`
2. `CurrentOffseasonStage` parks at 9 through the next preseason — `stage===2` alone is safe only combined
   with `CurrentStage==='OffSeason'`. `[diff §1.1]`
3. There is no offseason-stage enum; don't look for one. `[schema §4]`
4. `IsCarouselPeriodActive` is false at the portal stage — carousel and portal never overlap in observed
   data; the tool must not expect to see JobOpening rows at its own run-time. `[dump §11]`
5. Autosaves are logical clones — reading either gives the same calendar. `[diff §5]`
6. `CurrentWeekType` mirrored `CurrentStage` in every observed save; whether they ever diverge (e.g.
   NFLSeason × BowlSeason3) is UNVERIFIED but likely in-season. `[diff §1.1]`
7. The `Is*PeriodActive` flags for staff hiring have finer granularity (CreateOffer/EvaluateOffer) that maps
   onto the StaffHiringEval handler sequence — potentially useful to distinguish early vs late carousel if we
   ever snapshot in-window (UNVERIFIED live values). `[schema §2,§4]`

## Sources

- `research/notes-save-diff.md` + `research/dumps/save-diff.json` (five-save calendar comparison; flag dumps)
- `research/notes-save-dump.md` + `research/dumps/*/SeasonInfo.3123991521.sample.json` (SeasonInfo field
  dumps; CoachTransactionHistoryEntry timeline stamps)
- `research/notes-schema.md` (full SeasonInfo 59-field inventory; SeasonStage/SeasonWeekType enums;
  StaffHiringEval lifecycle handlers)
- `research/notes-carousel-tool.md` (KivJoy README window lore; SeasonInfo bonus observations)
- `research/notes-force-commit.md` (CurrentWeek usage; "week 4 of transfer portal" precedent)
- `research/notes-web.md` (SeasonInfo field list corroboration [CODE via table-map.json])
- `research/notes-pipeline-tool.md` (LeagueID dynasty keying)
