FM27: staff attributes are in the ability block (block_40 and three preference slots)
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
Research direction
Start with the staff reader’s _read_ability_block and the Staff.attributes and StaffAttributes definitions mentioned in the issue. Check how block_40 values and preference_slot_4, preference_slot_12, and preference_slot_13 are currently read and exposed, then review the staff reader tests. Done means verified attributes appear on non-human staff with the reported scales, while unconfirmed values remain unset.
Written by the indexing model from the issue text.
Description
No save files or personal details
- I have not posted a save file or a link to one, and removed personal details
What went wrong
This is missing data rather than a wrong value. Staff.attributes holds only adaptability, and the StaffAttributes docstring says the other ratings haven't been found. On one FM27 career, I found them in bytes fmsave already reads in _read_ability_block: the 26 block_40 values, which it range-checks but doesn't ship, and three of the unnamed preference slots.
I got this only by comparing my own saves with the game's screens. In the table, block_40[n] is counted from layout.block_40_offset, and the preference_slot_n values are already in Staff.unknown.
| Staff profile attribute | Where it is | Scale |
|---|---|---|
| Goalkeeping | block_40[1] |
1 to 100 |
| Judging Player Ability | block_40[2] |
1 to 100 |
| Judging Player Potential | block_40[3] |
1 to 100 |
| People Management | block_40[4] |
1 to 100 |
| Motivating | block_40[5] |
1 to 100 |
| Physiotherapy | block_40[6] |
1 to 100 |
| Tactical Knowledge | block_40[7] |
1 to 100 |
| Attacking | block_40[8] |
1 to 100 |
| Defending | block_40[9] |
1 to 100 |
| Fitness | block_40[10] |
1 to 100 |
| Possession | block_40[11] |
1 to 100 |
| Technical | block_40[12] |
1 to 100 |
| Tactical | block_40[13] |
1 to 100 |
| Analysing Data | block_40[18] |
1 to 100 |
| Sports Science | block_40[21] |
1 to 100 |
| Negotiating | block_40[23] |
1 to 100 |
| Judging Staff Ability | block_40[24] |
1 to 100 |
| Set Pieces | block_40[25] |
1 to 100 |
| Authority | preference_slot_4 |
1 to 20 |
| Working With Youngsters | preference_slot_12 |
1 to 20 |
| Determination | preference_slot_13 |
1 to 100 |
I don't know what block_40 positions 0, 14, 15, 16, 17, 19, 20 and 22 hold. Positions 0, 14, 17, 19 and 20 have only about 22 distinct values across all staff, as if stored in steps of 5.
How I checked it
-
Three staff profiles. The game shows words rather than numbers. Dividing the 1 to 100 values by 5 and rounding, 52 labels across the three profiles all fit these bands:
Value Label 1 to 4 Unsuited 5 to 6 Reasonable 7 to 9 Competent 10 to 11 Average 12 to 14 Good 15 and 16 Very Good The bands are only my fit to those labels, and no value above 16 was on those screens.
-
Averages by job, across about 7,800 staff. Physios average 66 on
block_40[6]and most other jobs about 15. Goalkeeping coaches average 58 onblock_40[1]and most others about 13. Analysts average 62 onblock_40[18]and most others about 15 to 30. Sports scientists average 66 onblock_40[21]and most others about 15. -
Known specialists. Two well-known real set-piece coaches read 100 and 90 on
block_40[25], where coaches average 23. -
Stars on the Set Coach Assignments screen. A weighted average of these attributes matches 8 star ratings from that screen. Seven were used to choose the weights, and the eighth I predicted before reading it from the game. On a 1 to 20 scale, with 20 as five stars and rounded to half stars, I used Attacking, Defending or Possession 3, Technical or Tactical 2 and Determination 3; Fitness or Goalkeeping 4 and Determination 1. Other weights fit too, so this is not the game's formula. It does support Determination, because the rating needs it.
How sure each part is
- Authority rests on the three profiles alone. I found it by scanning the bytes around the block for a value that fit all three, so that match could be chance.
- Determination came from the same scan, with the star ratings as extra support.
- Set Pieces and the star weights have no direct check beyond what's above.
- This is one career on one build, and I haven't looked at FM26 or at the human manager, whose object fmsave reads differently.
Still missing: which coach is assigned to which training area. I haven't looked for it. The training rows have only team_id, club_uid, club_name, team_slot, weeks and schedule_library.
What you expected
These rated attributes on Staff.attributes for non-human staff, leaving any as None or unconfirmed until you've checked them. The 1 to 100 values are probably better shipped as stored, since dividing by 5 is only how they seem to display.
I can email cropped screens of the three profiles and the assignments screen with the matching save, if that helps.
fmsave version
0.9.3
Python version
3.12.10
Operating system
Windows 11
Game build (FM26 or FM27)
27.0.0+2493426 (unverified build, partial coverage)
fmsave validate output
The staff readers in full, plus the status of the others. tactics, set_pieces and selections fail for the reasons in #67, and finances and sponsorships also fail on this save, which is separate from this issue.
{
"fmsave_version": "0.9.3",
"python_version": "3.12.10",
"os": "Windows",
"game": "FM27",
"build": "27.0.0+2493426",
"known_build": false,
"staff_readers": [
{
"reader": "staff",
"status": "ok",
"record_count": 7813,
"gates": [
{
"name": "staff_ability_signature",
"observed": 1.0,
"minimum": 0.99,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_preference_slots",
"observed": 0.9998719918074757,
"minimum": 0.99,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_codes_in_set",
"observed": 0.9971838197644649,
"minimum": 0.98,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_block_40_in_range",
"observed": 0.9998719918074757,
"minimum": 0.99,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_person_blocks",
"observed": 0.9998720081914757,
"minimum": 0.99,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_minimum",
"observed": 7813,
"minimum": 1000,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_listed_contracted_here",
"observed": 0.9430210325047801,
"minimum": 0.6,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_unowned_tailed_contracts",
"observed": 0,
"minimum": null,
"maximum": 0,
"passed": true,
"applied": true
}
],
"coverage": {
"uid": 1.0,
"unique_id": 0.997440163829515,
"is_human_manager": 1.0,
"name": 0.9998720081914757,
"first_name": 0.9998720081914757,
"last_name": 0.9998720081914757,
"common_name": 0.06105209266606937,
"full_name": 0.9998720081914757,
"legal_name": 0.4198131319595546,
"birth_date": 0.9998720081914757,
"age": 0.9998720081914757,
"nation_id": 0.9998720081914757,
"club_uid": 1.0,
"club_name": 1.0,
"team_id": 0.9970561884039422,
"team_slot": 0.9970561884039422,
"job": 0.9970561884039422,
"job_code": 0.9970561884039422,
"listed_club_uid": 0.6693971585818508,
"listed_club_name": 0.6693971585818508,
"in_club_lists": 1.0,
"list_indexes": 1.0,
"has_contract": 1.0,
"wage": 0.865736592858057,
"contract_start": 0.865736592858057,
"contract_end": 0.7970049916805324,
"ability_current": 0.9998720081914757,
"ability_potential": 0.9998720081914757,
"ability_potential_range_code": 0.0,
"personality_adaptability": 0.9998720081914757,
"personality_ambition": 0.9998720081914757,
"personality_loyalty": 0.9998720081914757,
"personality_pressure": 0.9998720081914757,
"personality_professionalism": 0.9998720081914757,
"personality_sportsmanship": 0.9998720081914757,
"personality_temperament": 0.9998720081914757,
"personality_controversy": 0.9998720081914757,
"attributes_adaptability": 0.9998720081914757,
"preferences_attacking": 0.9998720081914757,
"preferences_business": 0.9998720081914757,
"preferences_directness": 0.9998720081914757,
"preferences_interference": 0.9998720081914757,
"preferences_patience": 0.9998720081914757,
"preferences_trigger_press": 0.9998720081914757,
"preferences_resources": 0.9998720081914757,
"preferences_buying_players": 0.9998720081914757,
"preferences_mind_games": 0.9998720081914757,
"preferences_flexibility": 0.9998720081914757,
"preferences_hardness_of_training": 0.9998720081914757,
"preferences_squad_rotation": 0.9998720081914757,
"preferences_tempo": 0.9998720081914757,
"preferences_width": 0.9998720081914757,
"unknown_entry_count": 0.9998720081914757,
"unknown_r4": 0.9998720081914757,
"unknown_r5": 0.9998720081914757,
"unknown_r6": 0.9998720081914757,
"unknown_r7": 0.9998720081914757,
"unknown_r8": 0.9998720081914757,
"unknown_r9": 0.9998720081914757,
"unknown_r10": 0.9998720081914757,
"unknown_r11": 0.9998720081914757,
"unknown_r12": 0.9998720081914757,
"unknown_preference_slot_2": 0.9998720081914757,
"unknown_preference_slot_4": 0.9998720081914757,
"unknown_preference_slot_5": 0.9998720081914757,
"unknown_preference_slot_7": 0.9998720081914757,
"unknown_preference_slot_8": 0.9998720081914757,
"unknown_preference_slot_12": 0.9998720081914757,
"unknown_preference_slot_13": 0.9998720081914757,
"unknown_preference_slot_16": 0.9998720081914757,
"unknown_preference_slot_17": 0.9998720081914757,
"unknown_preference_slot_18": 0.9998720081914757,
"unknown_preference_slot_19": 0.9998720081914757,
"unknown_preference_slot_20": 0.9998720081914757,
"unknown_contract_e36": 0.865736592858057,
"unknown_contract_e37": 0.865736592858057,
"unknown_contract_e38": 0.865736592858057,
"unknown_contract_e39": 0.865736592858057,
"unknown_contract_type": 0.8380903622168181
},
"anomalies": {
"untailed_hits": 114283,
"club_record_hits": 22239,
"ambiguous_headers": 0,
"unlocated_persons": 0,
"unresolved_contract_teams": 342,
"repeat_contracts": 0,
"merged_affiliate_pairs": 72,
"human_manager_missing": 0,
"non_staff_tailed_hits": 0,
"offer_records": 114
}
},
{
"reader": "staff_lists",
"status": "ok",
"record_count": 1599,
"gates": [
{
"name": "staff_lists_fit",
"observed": 1.0,
"minimum": 1.0,
"maximum": null,
"passed": true,
"applied": true
},
{
"name": "staff_list_ids_are_staff",
"observed": 0.9998087954110899,
"minimum": 0.99,
"maximum": null,
"passed": true,
"applied": true
}
],
"coverage": {
"club_uid": 1.0,
"club_name": 1.0,
"list_index": 1.0,
"staff_uids": 1.0,
"staff_names": 1.0
},
"anomalies": {
"player_values_in_lists": 6,
"unset_list_values": 0
}
}
],
"other_reader_status": {
"clubs": "ok",
"players": "ok",
"contracts": "ok",
"suspensions": "ok",
"managed_clubs": "ok",
"stages": "ok",
"competitions": "ok",
"nations": "ok",
"fixtures": "ok",
"league_tables": "ok",
"transfer_windows": "ok",
"competition_rules": "ok",
"player_match_stats": "ok",
"player_season_stats": "ok",
"player_season_history": "ok",
"stadiums": "ok",
"finances": "failed",
"sponsorships": "failed",
"affiliates": "ok",
"job_vacancies": "ok",
"injury_types": "ok",
"injuries": "ok",
"training": "ok",
"mentoring": "ok",
"tactics": "failed",
"set_pieces": "failed",
"selections": "failed",
"facilities": "ok"
}
}
- Dominant language
- Python
- Stars
- 28
- Forks
- 8
- Avg merge
- 42m
- Merged PRs (30d)
- 33
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rhiever/fmsave
-
good first issue help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
good first issue help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
training_man: 27.0.1+2494687 writes a non-zero block trailer, training/mentoring readers fail closedOpen
Difficulty 4/5 1-2 days Newbie friendliness 49/100
rhiever/fmsave#82 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
rhiever/fmsave#77 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 43/100
rhiever/fmsave#75 · 2 comments ·
Maintainers usually reply within 1 day
Similar issues
-
needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
gke-labs/kube-agents#5671 ·
Maintainers usually reply within 1 day
-
docs(auth): Method B step 4 should tell users to tick "Use this client for an AI-powered agent"Opendocumentation next-release v0.9.6
Difficulty 1/5 Under an hour Newbie friendliness 88/100
khuisman/mcp-gee-sweet#1446 ·
Maintainers usually reply within 1 day
-
model:sonnet runtime:claude security size:S
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
FootprintAI/Containarium#3671 ·
Maintainers usually reply within 1 day
-
HolidayBase: reassigning `language` does not re-initialize translations or re-populate holidaysPossibly taken @shikhabhadoria claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
vacanza/holidays#4116 · 1 comment ·
Maintainers usually reply within 1 day
-
bug CLI exec
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day