Repository navigation
Extract Final Stand technology choices (data/fs-technologies.json) - #103
Merged
Merged
Conversation
Final Stand hands the player a "Technology Choice" at the start of the match and at the start of every wave: three technologies to pick one from - a new unit for the Barracks, a new player ability or a passive bonus. Adds scripts/xml-to-json/fs_technologies.py, which writes data/fs-technologies.json. It needs two archives: - ReferenceAttributes.sga (xml/attrib) for the pool - one technology list per faction plus a shared "common" list the game adds to all of them - and for every technology its ui text, upgrade_type tags and THRESHOLD_MIN / THRESHOLD_MAX - Data.sga for scar/hoff/hoff_technologymenu.scar, which holds the order of the twelve picks and the bucket each one draws from. This is game script, not attributes, so Data.sga is now downloaded and unpacked by the workflow as well (~32 MB). The pools are exported denormalized per race (the common list already merged in, with a source field saying where an entry came from) so the website does not have to join anything. Text stays as locstring IDs like fs-perks.json does. The instance parent chain merging fs_perks.py used is moved to hoff_instances.py so both scripts share it; the perk export is unchanged, byte for byte. Regenerating data/fs-perks.json against the current xml did pick up five stale modifier values. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new extractor for Final Stand (HOFF) “Technology Choices” and wires it into the GitHub Actions workflow, while refactoring shared instance-parent-chain merging logic out of fs_perks.py into a reusable helper module.
Changes:
- Add
scripts/xml-to-json/fs_technologies.pyto export Final Stand technology choice pools + pick schedule intodata/fs-technologies.json. - Factor shared raw instance loading/merging + UI/custom-property parsing into
scripts/xml-to-json/hoff_instances.pyand updatefs_perks.pyto use it. - Update workflow/docs to download + unpack
Data.sga, run the new extractor, and ignore the unpackedgame-data/directory.
Reviewed changes
Copilot reviewed 7 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/xml-to-json/hoff_instances.py | New shared helpers for loading/merging instance variants and parsing UI/custom properties. |
| scripts/xml-to-json/fs_technologies.py | New Final Stand technology choices exporter (attributes + Data.sga scar script parsing). |
| scripts/xml-to-json/fs_perks.py | Refactor to reuse shared instance utilities; perks output logic remains. |
| README.md | Document new fs-technologies.json export and required Data.sga unpack steps. |
| data/fs-perks.json | Regenerated perks output reflecting upstream data drift. |
| .gitignore | Ignore unpacked game-data/ directory used by the new exporter/workflow. |
| .github/workflows/extract-data.yaml | Workflow now downloads/unpacks Data.sga, runs fs_technologies.py, and cleans up. |
| .github/workflows/coh3-filelist.txt | Add Data.sga to the downloaded file list. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+145
to
+146
| variant = ET.parse(path).getroot().find("variant") | ||
| parent_reference = find_named_value(variant, "instance_reference", "parent_pbg") |
Comment on lines
+309
to
+317
| } | ||
| if "title" in bucket: | ||
| pick["title"] = bucket["title"] | ||
| if "choices" in bucket: | ||
| pick["choices"] = bucket["choices"] | ||
| if comment.strip(): | ||
| pick["note"] = comment.strip() | ||
|
|
||
| picks.append(pick) |
Comment on lines
+324
to
+325
| match = re.search(r"maxOfferingCount\s*=\s*(\d+)", scar) | ||
| return int(match.group(1)) if match else None |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Final Stand hands the player a Technology Choice at the start of the match and at the start of every wave: three technologies to pick one from - a new unit for the Barracks, a new player ability, or a passive bonus. That is the in-match progression, as opposed to the meta progression already exported in
data/fs-perks.json.This PR adds
scripts/xml-to-json/fs_technologies.py, which writesdata/fs-technologies.json(~466 KB).Where the data comes from
ReferenceAttributes.sga->xml/attrib/instances/custom_property_container/hoff/technologies/*.xmlcommonlist the game adds to all of themReferenceAttributes.sga->xml/attrib/instances/upgrade/hoff/**/technology/*.xmlupgrade_typetags,THRESHOLD_MIN/THRESHOLD_MAX, what it unlocksData.sga->scar/hoff/hoff_technologymenu.scarThe pick schedule is game script rather than attributes, so
Data.sgais now downloaded and unpacked by the workflow too (~32 MB, added tocoh3-filelist.txt). It turns out to be fully deterministic:Only the contents of each menu are randomised: a weighted draw over the technologies tagged with that bucket, filtered by
thresholdMin/thresholdMaxon the passive picks.Shape of the JSON
meta-choicesPerPick(3),maxSlots,maxOfferingCount(a technology is only ever offered once), andpicks: the twelve picks with the wave they are handed out at, the category they draw from and the bucket a technology must be tagged with to appear.races- the pool per faction, denormalized: the common list is already merged in and asourcefield says which list an entry came from, so the website does not have to join anything. Per technology:category/buckets/tags,thresholdMin/thresholdMax,weight,ui, the raw customproperties, and thesquad/ability/upgradeit unlocks for cross referencingsbps.jsonandabilities.json.Counts: afrika_korps 74, american 85, british 75, german 76 technologies. All 785 locstring IDs resolve against
data/locales/en-locstring.json, and every pick has at least four candidates for its three slots.Two quirks are flagged in the data and documented in the README:
hoff_technology_ability_rocket_overwatch_gergets"enabled": false- it is in the German list, but itsui_menuis not the technologies menu, so the game never offers it.hoff_technology_passive_reduce_production_fuel_cost("Munitions Focus") gets"category": null- it has noupgrade_typeat all, so no bucket matches it and it can only turn up when the game tops up a menu it could not fill.Refactor
The instance parent-chain merging that
fs_perks.pyused moved to a sharedhoff_instances.py, so the two Final Stand scripts do not carry two copies of it. The perk export itself is unchanged: running the pre-refactorfs_perks.pyand the new one against the samexml/gives byte-identical output.Regenerating
data/fs-perks.jsonagainst the currentxml/did pick up five stale values though (WEAPON_OVERALL_ATTACK_SPEED0.9/0.8/0.7/0.6 -> 1.1/1.2/1.3/1.4 on one perk) - pre-existing drift from a patch, not a result of the refactor.Testing
python scripts/xml-to-json/fs_technologies.py --game-data ./game-dataagainst a locally unpackedData.sgascripts/xml-to-json(the way the workflow invokes them)fs_perks.pyoutput diffed against the original script's output🤖 Generated with Claude Code