Stargazer's own contract (file layout, lints, retirement rules) lives in
../stargazer/docs/architecture/SEASONAL.md and is referenced here, not copied; this page adds the repo
names, commands and ordering that Stargazer's public docs deliberately leave out. What each phase takes from this
repo's extract and feed is in Integrations §2.2.
The expected shape of every cutover is structural first, text later: the game flips seasons before the upstream text feed carries the new season's skill text, so ids, names, stats and portraits ship at the flip and the text follows once the feed catches up. Seasonal targeting (Spirit Marks, artifact arrows) unlocks later still.
Four phases, each shipped on its own. Rough sizes are for a typical season (12 artifacts, 5 phantimals).
Phase 1, at the season flip. Most of the work; mostly mechanical.
Phase 2, when the text feed catches up. Small: rebuild the feed, npm run import:seasonal, fix any data file a lint flags, deploy.
Phase 3, targeting, per unit. Tiny each time: an artifact rule is on as soon as it's added; a
phantimal's Spirit Mark is written any time, then switched on with "targeting": true when it unlocks
in-game.
Phase 4, after the phase 1 deploy. Tiny: git rm the old season's icons in chaldea, and delete its calibration samples and rounds here, screenshots included.
| Repo | Role |
|---|---|
afkj-parser (this repo) | Extracts from the local game install (artifact icons and
stats, phantimal portraits with companions, locale strings) and builds the feed Stargazer's importers read
(out/api/). Converts the season's icons for the image host (tools/seasonal_icons.py)
and holds the match-screenshot import's calibration (calibration/). |
chaldea | Image host: publishes dist/ on push. Stargazer fetches
https://chaldea.tmdict.com/img/seasonal/{artifact,phantimal}/<slug>.webp. |
AFKJourneyData | Game-data dump, committed per game build; afkj-parser's fallback source when an extract fails. |
stargazer | Consumer. Hand-curates ids, names, ranges, targeting; imports text. |
| Phase | When | Repos | Result |
|---|---|---|---|
| 1. Structural | the game flips seasons | afkj-parser, chaldea, stargazer | new units placeable with portraits, names and stats; text shows "Skill details not yet available" |
| 2. Text | the feed carries the season | afkj-parser, stargazer | skill, effect and charm text live |
| 3. Targeting | artifact mechanics known; phantimal targeting unlocks in-game (weeks later) | stargazer | artifact arrows / Spirit Marks drawn |
| 4. Retire | after the phase 1 deploy | chaldea, afkj-parser | the previous season's icons and calibration screenshots removed |
Close the game first: the extractor refuses to run while it is open. The locales come from the game's CDN, all 17 of them, after the extractor checks that the CDN serves the installed build (if not: open the game, let it update, close it, re-run).
cd afkj-parser
./afkj-extract --locales-from cdn # tables + all 17 languages
./afkj-parse build # the feed in out/api/; ends with PASSED, diff in out/build/diff.md
./afkj-extract --only artifacts --only phantimals # out/images/{artifacts,phantimals}/<slug>.png + manifest.csv
The feed carries everything Stargazer's structural files need (ids, stats, factions, ranges, en/zh names);
Stargazer writes them in 3.3. In out/images/phantimals/manifest.csv, role is
main for the phantimal and sub for a companion, which belongs to the main of
the same camp (3.3, step 4).
cd afkj-parser
python3 tools/seasonal_icons.py # writes ../chaldea/dist/img/seasonal/{artifact,phantimal}/<slug>.{png,webp,avif}
cd ../chaldea
git add dist/img/seasonal && git commit -m "season N icons" && git push
The host publishes dist/ on push. Confirm every slug before Stargazer deploys, or new units render as
broken images on first load. From chaldea:
for f in dist/img/seasonal/{artifact,phantimal}/*.webp; do
p=${f#dist/}
printf '%s %s\n' "$(curl -sI "https://chaldea.tmdict.com/$p" | head -1 | awk '{print $2}')" "$p"
done | grep -v '^200' # prints nothing when all are live
chaldea's dist/_headers already sends Access-Control-Allow-Origin: * on
/img/*, which the canvas image export needs.
Follow SEASONAL.md, "Season cutover", phase 1. In file terms:
targeting. It stops on an id that would
change within a season.crit in S8): here, its game code and stargazer key in
feed/contract.py STAT_KEYS (until then the build fails F15) and a rebuild; in
Stargazer, an ArtifactStatKey + PERCENT_STAT_KEYS decision
(src/lib/types/artifact.ts) and src/locales/game/<key>.json; then re-run
step 2.src/lib/skills/seasonal/phantimal.ts: empty spiritMarks, one
createCompanionSkill({ raisesCapacity: false, companionImageModifier: '<companion slug>',
companionRange }) per phantimal with a sub row. Its season-specific cases go in the
season N block of tests/unit/skills/phantimal.test.ts.ARTIFACT_TARGETING entries and test cases.tests/unit/lib/seasonal.test.ts.Done when the data-contract tests in tests/unit/lib/seasonal.test.ts pass (they fail on a forgotten
pin, a skill file or artifact rule left from the previous season), every icon URL returns 200, and the new units'
modals show "Skill details not yet available".
out/build/diff.md lists what changed since the last import (the
new season's artifacts, phantimals and charms should be there). If the extract fails,
./afkj-parse build --source ../AFKJourneyData builds from the dump. A glossary keyword the new text
uses for the first time fails F15 until it has a name in feed/contract.py
KEYWORD_NAMES: the English word players read, in lowercase with hyphens.src/data/seasonal/charm/tags.json
against the new ids (s9-1 …). out/internal/keys.json says which charm each id is, and
the Debuff, Haste and Temp Buff tools read the tags through it.Done when no file in src/locales/seasonal/phantimal/ still has "skills": [], every
seasonal artifact has a file in src/locales/seasonal/artifact/effects/, and
tests/unit/charms.test.ts runs its per-charm checks instead of skipping them. Artifacts, phantimals and
charms can land in the feed on different builds; repeat the phase for each.
ARTIFACT_TARGETING (with its
artifact's name and tests) once the mechanic is known, and it draws from that deploy on. Deleting
the rule at the next cutover turns it off.spiritMarks (composed onto the companion skill for a
phantimal that has one) whenever convenient; it stays hidden, even with the Skills toggle on, until
"targeting": true is set in that phantimal's structural file. Flip it when the mechanic goes live.
The flag has no UI.After the phase 1 deploy nothing requests the old slugs (reused ids resolve to the new season's content; stale
saved teams show an "S{n}" placeholder), and a retired season never returns. Delete its artifact and phantimal icons
in chaldea, and its calibration screenshots and rounds here: they show its artifacts, and the new season's
calibration starts from fresh screenshots. Git history keeps the icons and the rounds' records; the screenshots are
out of git and go for good. Hero portraits, costume references, frames,
star rows, Stargazer's bundled hero descriptors and the permanent artifacts carry over untouched, and so does
calibration/replay/: Supreme League maps change on their own schedule, and its README covers a map
change. calibration/roster/ stays too: the roster screen shows heroes, not a season's artifacts.
cd chaldea
git rm dist/img/seasonal/artifact/<old-slug>.* dist/img/seasonal/phantimal/<old-slug>.*
git commit -m "retire season N" && git push
cd ../afkj-parser
git rm -r calibration/match/samples calibration/match/rounds
rm -rf calibration/match/samples calibration/match/rounds # the screenshots and pages git ignores
git commit -m "retire season N calibration"
The live slugs are exactly Stargazer's src/data/seasonal/{artifact,phantimal}/*.json names plus each
companion's companionImageModifier; anything else under chaldea's dist/img/seasonal/ can be
deleted. Until the old calibration is removed, npm run check in calibration/ stops on its
artifact labels.