Skip to content

Extract Final Stand technology choices (data/fs-technologies.json) - #103

Merged
petrvecera merged 2 commits into
masterfrom
feat/fs-technologies
Aug 29, 2026
Merged

petrvecera merged 2 commits into
masterfrom
feat/fs-technologies

Conversation

@petrvecera

Copy link
Copy Markdown
Member

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 writes data/fs-technologies.json (~466 KB).

Where the data comes from

Source What it gives us
ReferenceAttributes.sga -> xml/attrib/instances/custom_property_container/hoff/technologies/*.xml the pool: one list per faction plus a shared common list the game adds to all of them
ReferenceAttributes.sga -> xml/attrib/instances/upgrade/hoff/**/technology/*.xml each technology: ui text and icon, upgrade_type tags, THRESHOLD_MIN / THRESHOLD_MAX, what it unlocks
Data.sga -> scar/hoff/hoff_technologymenu.scar the order of the twelve picks and the bucket each one draws from

The pick schedule is game script rather than attributes, so Data.sga is now downloaded and unpacked by the workflow too (~32 MB, added to coh3-filelist.txt). It turns out to be fully deterministic:

1 unit(bucket0)  2 passive  3 ability1  4 unit1   <- first boss wave
5 passive        6 ability2 7 unit2               <- second boss wave / bronze
8 passive        9 ability3 10 unit3              <- third boss wave / silver
11 passive       12 ability4                      <- final boss wave / gold

Only the contents of each menu are randomised: a weighted draw over the technologies tagged with that bucket, filtered by thresholdMin / thresholdMax on the passive picks.

Shape of the JSON

  • meta - choicesPerPick (3), maxSlots, maxOfferingCount (a technology is only ever offered once), and picks: 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 a source field 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 custom properties, and the squad / ability / upgrade it unlocks for cross referencing sbps.json and abilities.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_ger gets "enabled": false - it is in the German list, but its ui_menu is not the technologies menu, so the game never offers it.
  • hoff_technology_passive_reduce_production_fuel_cost ("Munitions Focus") gets "category": null - it has no upgrade_type at 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.py used moved to a shared hoff_instances.py, so the two Final Stand scripts do not carry two copies of it. The perk export itself is unchanged: running the pre-refactor fs_perks.py and the new one against the same xml/ gives byte-identical output.

Regenerating data/fs-perks.json against the current xml/ did pick up five stale values though (WEAPON_OVERALL_ATTACK_SPEED 0.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-data against a locally unpacked Data.sga
  • Both scripts run from the repo root and from scripts/xml-to-json (the way the workflow invokes them)
  • fs_perks.py output diffed against the original script's output

🤖 Generated with Claude Code

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>
Copilot AI lite review requested due to automatic review settings August 29, 2026 18:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py to export Final Stand technology choice pools + pick schedule into data/fs-technologies.json.
  • Factor shared raw instance loading/merging + UI/custom-property parsing into scripts/xml-to-json/hoff_instances.py and update fs_perks.py to use it.
  • Update workflow/docs to download + unpack Data.sga, run the new extractor, and ignore the unpacked game-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
@petrvecera
petrvecera merged commit f52091e into master Aug 29, 2026
1 check passed
@petrvecera
petrvecera deleted the feat/fs-technologies branch August 29, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants