College Football 27 · AutoProgress · release 0.2.1, 22 September 2026 · verified on the 2026-09-22 title update

AutoProgress
Method Book

Every formula the DLL runs, every number we measured while building it, and every assumption we made along the way. Written so a curious player can follow it and a sceptical one can check it.

1. What it changes

The game runs CPU player development once a year, at Training Results. AutoProgress hooks three points in that pass and nothing else: which skill group a CPU player buys with his points, whether a development trait can fall as well as rise, and how far each player's skill ceilings sit from the players like him, with his program's quality and his own production on the field weighing on that last one. The user's own progression screen, recruiting, and the season itself are untouched.

ChangeVanilla behaviourWith AutoProgressMeasured effect
Skill-point spendingA random affordable skill groupGroups weighted by overall gained per point, discounted by room leftAbout +2.3 overall per CPU player per offseason: 4.6 gained from his points against 2.3 with the game’s random pick and 4.8 for a perfect picker (section 2)
Development traitCan only riseCan fall after a poor season, by the game's own season scoreAbout 50 falls a season against 100 to 150 rises
Skill ceilingsFixed when the player is generatedRe-rolled yearly around the average of players like him; 8% bust; cuts fund breakoutsPlayers at 90+ doubled and players under 65 doubled after three seasons, league average up half a point
Program qualityNo effect on developmentCoach, facilities and upgrades nudge ceiling targets up to ±10 levels within a trait; producers are shielded from the downsideTop-quarter programs grow 1.3 overall more than bottom-quarter ones over three seasons

2. Smart spending

At Training Results the game gives each CPU player a number of skill points and spends them one purchase at a time. For each purchase it lists the skill groups he can still afford and can still level, then picks one at random. Buying a level in a group raises every skill in that group by 2, up to the group's ceiling, so which group he buys decides how much his overall rating moves.

The DLL replaces the random pick with a weighted one. Each candidate group gets a weight from how much overall it buys per point, and the pick is drawn in proportion to those weights. The game's own random number is still consumed, so the rest of the game's random sequence is exactly as in vanilla.

weight(group) = ( value(group) / cost(group) × headroom(group) + floor ) ^ exponent value overall gained per level for this player's archetype (measured on sim saves, else from EA's own overall formula) cost skill points the next level costs (the game's number) headroom share of the group's skills that can still move before hitting their tier ceiling (1.0 = all, 0.0 = none) floor 0.02, keeps every group possible exponent 2 (shipped): leans hard to value without being greedy; 1 = proportional, 0 = vanilla

The value tables come from two views of a player: what raises his overall rating, and what matters on the field for his position. The shipped table blends them 75/25 toward overall.

Value tables regenerated 20 September 2026, re-measured 21 September. The overall formulas the tables are built from were replaced by a fit that is 99.97% exact against the game’s own overall rating over 11,730 rostered players (the earlier fit was 51.5% exact and overfit for 19 archetypes). For 20 of the 60 archetypes that changed which skill group is worth most. One Training Results was then run on the new tables from the 2028 signing-day save (7,771 players scored with the same tool as the chart above): the hook’s picks bought 4.61 overall per player from the offseason’s points, against 2.34 for the game’s random pick on the same save and 4.82 for a perfect picker, so 96% of the optimum, and the share of points wasted on groups worth under 0.15 overall per level fell from 14% on the old tables to 10%. The chart above still shows the old-table simulation; its shape holds, its bars sit a little low.

the game's own pickerAutoProgress tablesa perfect picker
Overall rating gained per player from one offseason's points, simulated on a real save's rosters (progression-efficiency.js). The overall table was also measured in game at 4.12, within 3% of its simulated 4.00.

Pace: the same picks, more or less fuel

Better picks mean faster growth. Measured one offseason from the same save, the whole player table grew +1.20 overall per player in vanilla and +2.40 with the hook. That is what we call human pace: CPU teams developing the way a person who reads the skill trees would. Two companion mods scale the game's offseason experience table down so the CPU keeps its smarter picks but grows closer to vanilla speed.

vanilla picksAutoProgress picks
One offseason from the same 2028 save. Left bars: overall gained per player. Right labels: players at 90+ afterwards, whole table. Growth is close to linear in the experience scale: about +2.0 per unit of scale plus +0.4.

3. Development traits

A player's development trait (Normal, Impact, Star, Elite) sets how many points he gets. The game rolls a rise once a year from his season score, a 0 to 100 figure it computes from his stats for his position. The chance is the score minus a threshold for his current tier, scaled by a coach bonus:

rise chance = ( seasonScore − threshold[tier] ) × ( 100 + coachBonus ) / 100 roll 0..100 at or below the chance = one tier up threshold vanilla 25 / 50 / 75 for Normal / Impact / Star (shipped tuning 14 / 29 / 43, see below)

Nothing in the game ever lowers a trait. The DLL adds the mirror image, using the game's own season score, random number generator and field setter:

fall chance = ( floor[tier] − seasonScore ) × scale / 100 rolled only if the player did NOT just rise floor 25 / 50 / 50 for Impact / Star / Elite: the score a player needed to rise INTO that tier (Elite held to Star's bar) gates no roll with a season score below 1 (0 means no season on record: bench, redshirt, incoming recruit) no roll for players in their first season (the class year at roll time is already next season's)

The gates came from the first run: 286 falls, of which 247 were players with a season score of 0, who had not played, not played badly. With the gates the validated run gave 109 rises and 47 falls; across a three-season line the counts were 97/54, 132/45 and 143/57.

Rise tuning: holding the Star and Elite pools steady

With smarter spending, players outgrow their traits. Following each tier through a season (who left, who arrived, who was promoted or demoted) showed the Star pool needs about 57 promotions a season to hold its size and was getting 16; Elite needs about 10 and was getting 1. The thresholds are data in the game's expression file, so we lowered them and measured, one Training Results from the same save each time:

vanilla 25/50/75trial thresholdsshipped 14/29/43target (steady pools)
Promotions in one Training Results by threshold set. Star to Elite is sharply non-linear: 55 gave 2, 47 gave 5, 43 gave 14, 40 gave 17. The 13/29/47 run's 202 is an outlier against 14 and 15 both giving about 153, which is why tuning stopped here: single-run noise is about ±13 on a count near 170 and ±3 near 10. The standalone tuning mod stacked on Sean's play mod gave 169 / 58 / 7.

4. Three seasons, measured

Before the rise tuning and the spread existed, the smart-spending hook and the fall roll ran for three seasons from the same 2028 save, against a vanilla line from the same save. These counts are the whole player table, recruits included, so they are larger than the rostered-only figures later on.

vanillaAutoProgress, human pace
Left: players rated 90 or better at each Training Results. Right: mean overall. Under-65 players fell 2,079 → 1,972 on the hook line. Star and Elite pools shrank (930 → 783 and 231 → 193) as players outgrew their traits, which is what the rise tuning in section 3 corrects.

5. The Development Spread

Every player has six skill ceilings, one per skill group, 0 to 20 each, 120 in total. A ceiling is a hard stop: once a group reaches it, no more levels can be bought there. The game fixes them when a player is generated, and they track his recruiting stars closely, so a five-star almost never busts and a two-star almost never breaks out. The spread re-rolls them once a year so that surprises in both directions happen at realistic rates, without moving any group's average.

This is the Dynasty Development app's roster pass, ported line for line into the DLL and checked against the app player by player (section 8). It runs league-wide at the first development-trait roll of Training Results, which sits before any team spends a point. Here is the whole computation for one player, in order.

  1. Peers. His cell is players with the same development trait, position and class year; his group is the same trait and position. The anchor is his cell's average original ceiling, shrunk toward the group when the cell is thin:
    anchor = ( n × cellMean + 20 × groupMean ) / ( n + 20 ) n = players in the cell
  2. Star billing. Within a cell, higher-star players sit higher. The part of his ceiling explained by his star rating is measured jointly with the cell means (25 alternating passes: cell means net of star offsets, then star offsets net of cell means) and only half of it is kept, so the spread never widens the gap between star ratings:
    own = offset[stars] − mean of offset over the cell's rated players (0 for a player with no rating) target = anchor + 0.5 × own + 1.6 × ( original − anchor − own )
    Everything not explained by stars is stretched by 1.6 away from the anchor.
  3. Luck. A draw fixed by who he is, so it never re-rolls from season to season:
    h = SHA-1( seed + "|" + key ) key = PresentationId, birthdate and position; seed 27 u = max( first 32 bits of h / 2^32, 1e-9 ) v = next 32 bits / 2^32 luck = sqrt( −2 ln u ) × cos( 2π v ) a standard normal raw = target + 5 × luck
  4. Re-centre. Shrinkage leaves each cell slightly off its true mean, and the stretch multiplies that; each cell is pulled back onto its original average, so no trait, position or class moves as a whole.
  5. Program bias. Every program is scored once per pass from the game's own records: the Team row, the head coach it points at, and its purchased-upgrade row. Each part is a percentile rank across the league's programs (ties share the average rank):
    score = 0.40 × upgrades + 0.25 × coachLevel + 0.15 × coachPrestige + 0.20 × facilities upgrades = mean rank of OffseasonProgression, IncreasePracticeWTRecovery, ReducePracticeInjury, SeasonHealthImpact coachLevel = rank among CPU-coached programs; a user coach gets the share of CPU coaches below him, capped at 0.90 facilities = FacilitiesLevel / 4
    Within each development trait the players are ranked by their program's score and the rank is mapped to ±10 levels, added to raw. The talent shield blunts the downside for players who have already produced: a player's production rank within his position (section 6a) sets a shield strength of (rank − 0.6) / 0.4, clamped to 0..1, and a negative bias is multiplied by 1 − 0.9 × strength; players without a season are shielded by how far their ceiling already sits above their anchor instead. The trait's mean bias is then restored on the unshielded, so the bias moves players relative to each other and never a trait as a whole.
  6. Ceiling. A player pushed past 120 hands the excess to cell-mates with room, in proportion to their room, up to 8 rounds. The cell's total is preserved exactly.
  7. Back into six groups. The rounded total is split across his groups in his original proportions, water-filled at 20 per group: a group that would overflow is pinned at 20 and the remainder re-spread, with largest-remainder rounding so the six always sum to the total.
  8. Busts. A second fixed draw (SHA-1("bust|seed|key")) below 0.08 marks him a bust: his ceiling becomes the lower of what the stretch gave him and his overall minus 25, so he stops growing near where he is. Same odds at every star rating. It exists because a five-star's 112-of-120 ceiling never binds otherwise.
  9. Breakout fuel. The levels the pass cut fund skill points for the players it sent furthest above their billing:
    budget = round( 0.4 × levelsCut + 1.0 × bustLevels ) skill points, 20 per player, biggest boom first boom = raw − ( anchor + 0.5 × own ) his own share, star-neutral by construction
    Skill points granted here are spent minutes later in the same Training Results.
  10. Originals. Every season re-derives from the player's original ceilings, never from last year's stretched ones, so nothing compounds. The DLL records a player's originals the first time it sees him and never again, in one file shared by all dynasties (section 7 explains why that is the only safe rule).

6. Spread results in game

Port first, game second. The C port was run on the same inputs as the app's own pass and agreed on every one of 7,528 rostered players and 5,058 recruits, ceilings and skill points alike. In the game, two runs on the 2028 save and a second season on top verified the reads (every dumped value equal to the save), the writes (every ceiling in the post-run save equal to what the DLL wrote, apart from the game's own raises) and the store.

same run, no spreadwith the spread
One Training Results from the same 2028 save, rostered players, same mod and same DLL settings otherwise. Both tails widen; the mean rose 0.4 because fuel bought slightly more growth than the cuts removed.
Rostered players, one stepno spreadspread
mean overall after75.7676.17
growth per player, mean / spread (sd)4.32 / 3.214.72 / 3.95
gained 8 or more1,2571,764
gained nothing or lost1,1271,467
busts / fuelled players / fuel budget–538 / 1,734 / 34,680
fuelled players' growth vs the rest–+8.59 vs +3.71

Two seasons

Rostered players, after season twohuman-pace line, no spreadspread on
mean overall76.3876.63
players at 90+100180
players under 65150219
Star / Elite players324 / 88345 / 98
mean ceiling total85.381.4

The league average drifts up about a fifth of a point a season at the shipped fuel rate of 0.4 points per level cut. That is a choice, not an accident: 0.2 would hold the average flat at the cost of fewer breakouts, and it is one line in the ini.

6a. Production ranks, read in game

The shield needs to know who actually played. The DLL reads it the way the app does: each player's SeasonStats array points at his rows in the four season stat tables (offence, defence, offensive line, kicking); the counting stats are weighted in the spirit of fantasy scoring (passing yards 0.04, touchdowns 4 to 6, tackles 1, sacks 4, interceptions 6, drops and fumbles negative, and so on), divided by games played, and ranked within his position. Linemen barely register in the individual tables, so their score is the unit's output from the team's season stats (yards per carry, red-zone conversion, two-point tries, penalties and fumbles lost per game) plus their own sacks allowed and pancakes per hundred snaps, measured against the league median and shrunk toward neutral for players with few snaps. Every player row with a stat array is ranked, graduated seniors included, because the app ranks against everyone. On the 2030 save this ranked 8,727 players from 19,535 stat rows in 15 ms, identical to the app for every one of the 4,867 ranked players in that pass.

Three seasons, the whole pass

With every term verified, the full pass (ceilings, busts, fuel, program bias with the production shield) ran three seasons from the same 2028 save. These are measured on the cohort present in the start and every line's save, matched by name, birthdate and position, so recruiting classes that differ between lines do not enter. Growth is a player's overall now minus his overall at the start; Q1..Q4 split the cohort by the program score of the team he was on at the start.

After two seasons, cohort of 4,834no spreadspread, bias on
mean overall78.7079.01
players at 90+ / under 65139 / 58269 / 95
Impact / Star / Elite1,071 / 265 / 701,116 / 264 / 61
growth, mean / spread (sd)7.85 / 4.318.16 / 5.44
gained 8 or more / gained nothing or lost2,547 / 3162,588 / 522
growth at Q1 (worst programs) … Q4 (best)7.85 … 7.487.50 … 8.26
program effect, Q4 minus Q1−0.37+0.77
After three seasons, cohort of 3,044no spreadspread, bias on
mean overall80.4980.97
players at 90+ / under 65162 / 15296 / 35
Impact / Star / Elite750 / 192 / 36786 / 183 / 27
growth, mean / spread (sd)10.49 / 4.5610.96 / 6.06
gained 8 or more / gained nothing or lost2,290 / 642,160 / 150
growth at Q1 … Q410.40 … 10.3210.10 … 11.38
program effect, Q4 minus Q1−0.09+1.28

Both lines were saved at the same point of the season (the first day of the new year) and both ran on the same value tables, the corrected ones, after the no-spread line was re-run on them on 21 September 2026. Two things to read from that. The bias creates the effect it exists for, and it compounds: a player at a top-quarter program grows about 0.8 more than one at a bottom-quarter program after two seasons and 1.3 after three, where without it program quality made no difference (a line with the spread but the bias off, run earlier on the old tables, gave −0.40 after two seasons). And the spread’s own signature holds up over time: nearly twice the players at 90 or better, twice the sub-65 players, a wider spread of individual growth, and a league average about half a point higher, which is the fuel drift. The corrected tables themselves were worth about 0.1 of growth over two seasons on the no-spread line (7.66 against 7.56 at the same after-training-results stage), so the earlier caveat that they might explain the mean gap is settled: they do not.

7. Assumptions and limits

Each entry says what was assumed, why, and what it costs if wrong. Items marked verified were checked in game.

Who the spread touches verified
Every row of the player table on a team (team index not 255) that is not an empty row. Recruits, players who have left and stale rows all carry 255, and signed recruits are not moved onto teams until after the pass. Empty rows read as all zeros and team 0 is a real team, which the first run found the hard way (1,702 phantom freshman quarterbacks); rows with no overall, no id and no ceiling are now skipped.
Identity verified
A player is his PresentationId, birthdate and position. The app uses id plus name; the DLL cannot read strings, so a player's luck and bust draw differ from the app's for him, with the same distribution. Of 4,539 players followed from one season to the next, 4,538 kept the same id, birthdate and row.
Originals are written once, for everyone verified
Dynasties started from the same base roster hold the same real players under the same key (1,651 of 1,654 name matches between two of Sean's dynasties carry the same id), and no rule on the ceilings themselves can tell one dynasty's stretched caps from another's: the best cap-pattern rule still misattributed 13% of shared players on real saves. So the first ceilings the DLL sees for a player are his originals for good, in one file. Every dynasty then stretches from vanilla with its own peers. Reload and replay lands on the same numbers. Cost if wrong a player first seen in a dynasty the app has already stretched carries those caps as originals into other dynasties; losing the file costs one re-record from stretched caps.
The game raises some ceilings itself verified
About 3.5% of rostered players gain 1 to 6 ceiling levels at Training Results in vanilla (268 of 7,771 in one run), and a few gain +1 across the board before the roll. The next pass overwrites those, exactly as the app does.
Following a reference verified
A reference field reads as the save file's own packed reference (table id and row) with the sign bit set, minus one; the record it points at comes from slot 37 of the table object's vtable, which is exactly how the game's own ProgressTeam fetches the coach-talents record it hands to the trait roll. Ten references resolved to the save's exact rows in the probe run.
Program score verified
Scored in game from the Team row, its head coach and its upgrade row: 138 of 138 programs identical to the app's score, every input included, on a save taken before Training Results (coach levels rise at Training Results after the roll, so a post-run save disagrees on those teams for a real reason). Cost if wrong a program whose coach or upgrade row does not resolve scores those parts as zero, which is what the app does for a missing row.
Production ranks verified
4,867 of 4,867 ranked players identical to the app, none ranked on one side only. Two things learned on the way: a field descriptor is the schema attribute index itself, and array slots are reached by index through the game's array interface, not through descriptors. Each direct read runs under an exception guard, so a descriptor the getter refuses is counted and skipped rather than ending the game (none was, in any run).
Overflow stat tables
When a season stat table fills, the game allocates one-row tables of the same schema for the rest; 443 linemen sat in such tables after four seasons. At run time those rows come through the same table object as the main table, so they are recognised; if an unknown table object ever appears it is classified by how many field ids it admits, and a defensive-return overflow would then be misread as kicking, adding its snaps and games only.
The pass sees last season's traits
The app runs after its own trait tool; the DLL runs before this offseason's rises and falls, so peer cells use last season's traits. A few hundred changes among 7,500 players barely move a cell mean.
One pass per Training Results verified
The rolls for the whole league arrive as one burst with no gap over 5 seconds inside it across 15 logged runs. A gap over 20 seconds, or a player rolled a second time, starts a new pass. A reload and replay is therefore a second pass, which is correct.
Fuel drift is a setting
At 0.4 points per level cut the league average rises about 0.16 per season over three seasons (0.31 over the first two, 0.48 over three, both lines on one set of value tables). Sean chose to keep it; 0.2 would hold it flat.
Value tables changed mid-series, then settled
The tables the spending hook weighs skill groups by were regenerated from a corrected overall fit before the three-season line with the bias ran. On 21 September 2026 the no-spread line was re-run on the same tables from the same save, three seasons, saved at the same stage; the tables in section 6 are that pair. The tables were worth about 0.1 of growth over two seasons; the rest of the gap is the spread.
Noise
Each promotion is a coin flip: a count near 170 carries about ±13 of run-to-run noise, a count near 10 about ±3. Numbers in this document from single runs should be read with that in mind; the rise-tuning outlier at 13/29/47 is a real example.
Player table size
The table is walked as 17,500 rows, the capacity of every save examined. A smaller live table has not been seen.
Loading verified
Injector only. A drop-in version.dll beside the exe never loads on this game: the exe's static import table holds only its DRM module and every other import is resolved at run time without looking beside the exe. The launcher runs the injector in watch mode, which waits for the game, injects 30 seconds after it appears and repeats on every launch; confirmed in game.
Antivirus
Loading a DLL into another process is the technique some malware uses, and Windows Security flags the injector on that behaviour alone. The source is 120 lines and builds with gcc; the README gives the folder exclusion.
Game updates verified
The DLL finds the game's code by byte signature and checks each call site before patching. After an update it either still matches or logs and does nothing; it has never done anything in between. The 22 September 2026 title update was the first live test: it moved the progression code by about 0x360 bytes, and all three signatures still matched on the new build, both in game (every hook installed) and offline against a fresh memory dump of it. Release 0.2.1 is that build, unchanged.
Coach gating and XP scaling untested
Two further settings ship switched off and are not measured anywhere in this book: coach_gate, which sets the spending exponent from each team's program rank, and xp_gate, which scales each team's offseason skill-point award by the same rank. Both are built and self-tested against fake records, and the XP site is verified byte by byte at startup, but neither has been run for a season in a game. Every number in this book was measured with both at 0.

8. How it was tested

Matched runs. Every in-game comparison starts from the same checkpoint save, advances through one Training Results with one thing changed, and compares the saved results player by player, matching players by name and age rather than by id, because the game hands graduates' ids to new freshmen.

Exactness before plumbing. The spread's maths was validated offline before it ever wrote a ceiling in game: the app's own pass was run for real on a scratch copy of a save, the same inputs were fed to the C port, and every row had to agree, ceilings and skill points included. Every summation runs in table order and every formula keeps the app's operand order, so even the unrounded totals agree. A 628-player fixture is re-checked on every build.

Plumbing after. The DLL dumps every input it read and every value it wrote. A checker compares the inputs with the save the run started from (reads and row alignment), re-runs the port on the dumped inputs (maths), and compares the post-run save's ceilings with what was written (writes), and reports the growth of fuelled players against the rest.

Self-tests. The chooser, the fall roll, the coach-gating maths, the XP scaling and the spread each have a self-test the build script runs, against fake records shaped like the game's. A fourth check, check-signatures, re-verifies all three hook sites against a memory dump of any new game build.

Re-measured after the value-table fix. The spending figures were re-measured on the regenerated tables on 21 September 2026 (section 2), and the no-spread comparison line for the tables in section 6 was re-run on the same tables so that every line in them shares one set.

The same standard for the in-game reads. The program scores and production ranks are logged and dumped by the DLL at the roll; a checker runs the app's own functions on a checkpoint of the save taken before that Training Results and compares every team and every player. Both had to come back identical to one part in a billion before the bias was switched on, and the three-season line above ran only after they did.