The URL scheme was solved on 2026-09-25 by reading the downloader Lua statically; afkj_extract.cdn implements it (./afkj-extract --only loc --locales-from cdn) and tools/luadis.py re-traces it.
All 17 languages download from the game's public, unauthenticated CDN with no install and no traffic capture.
The scheme came from reading Src/App.luae and Src/Framework/Init.luae statically:
GET <cdn>mystical/global/mystical.codee (CDNs from Game.jsone's cdn_list:
https://igame-cdn-first.farlightgames.com/, …-second…). A .jsone frame around Lua
bytecode that sets ed.packageInfo: latest_pkgs[platform], headers_info[platform]
(files_info_offset, files_info_size, header_info_hash), latest_version.
The tool reads its constants only and never runs it.<cdn>pkg/AFKArena.DataPackage.PublicRelease.Build<N>.<platform>.pkg
(Build14368.iOS: 37.8 GB). Every file is an HTTP Range read of it (206).files_info_size bytes are afk.bin, byte-identical to the container's. Its records
are [u32 n][name][u16][u32 size][u64 offset][u64 xxh64]. The "byte number" from the lead is this
offset; the earlier "u32 number + u32 0" reading was the two halves of it.LanguageDb<LANG>.bytes are stored raw at [offset, offset+size). The FR and EN downloads
were byte-identical to the game's; all 17 (478 MB) took 78 s.xxHash64 (seed 0) verifies each step: afk.bin against header_info_hash, each file against its
record. Per update only the build number, the table location and the offsets change. Used by the
loc stage's --locales-from cdn (which make feed runs).
For future reference: the same package holds igame.pkg and every other file the
extractor reads, so a CdnSource behind the Source interface could run with no install at
all. Not planned; local game files remain the source.
After a game update, with the game closed, from the afkj-parser folder:
build ends with PASSED or FAILED and a one-line summary of what
changed since the last import.out/build/diff.md (or ./afkj-parse diff);
./afkj-parse diff --hero <slug> shows one hero's changed texts with the changed words marked.
A fix goes into config.json or the parser, then ./afkj-parse build again, with no
re-extract.DATA_FEED_DIR in its local .env.local.out/ except the
baseline, so the next update extracts again.The gates and the diff are described in Model & feed §6 and §7; the commands in CLI §10.
The cross-repo procedure is the season cutover runbook. What each phase takes from this repo:
| Phase | From afkj-parser | Into |
|---|---|---|
| 1, structural | --only artifacts, --only phantimals (icons, images §6) | tools/seasonal_icons.py, into chaldea's dist/img/seasonal/ |
| 1, structural | The feed (./afkj-parse build): artifact ids and stats, phantimal camp slots, factions (HeroTagID), attack ranges and damage types, en/zh names | stargazer npm run import:structure, which writes its season data and name files |
| 2, text | ./afkj-parse build after the extract (make feed): the feed in out/api/, checked by its gates and the diff (section 2.1) | stargazer npm run import:seasonal, then ./afkj-parse accept |
import:structure writes the season's artifact data files ({id, name, season, stats}), the
artifact en/zh name files and the phantimal data files ({id, name, season, range, damage, faction,
qualifyingFactions}). An artifact's id is 6 plus its rank by fairyId in the season set; a
phantimal's id is its campSlot; range is its attackRange; damage
is physical for a damageType of AD and magic for AP;
faction is the feed's, and a camp 5 phantimal's qualifyingFactions is
["hypogean","celestial"]; season
is seasonType − 100; English names drop " Spell". It keeps curated keys (targeting, an
existing qualifyingFactions), refuses to change an id within a season, and removes the previous season's
files. On the Season 8 feed it reproduces all 29 committed files byte for byte.
Lessons from Season 8: phase 1 took factions from CampPet.Camp and three of five were wrong; a
phantimal's hidden sixth skill (IsShow false) looked like missing text until checked in game; and an
Awakening Lv2 whose text keys ship empty belongs on config.json's awakeningLv2Blocklist,
which the model gate's M6 suggests.
expectations.diff.attentionShare (10%) and the count expectations in
config.json.import:structure writes the new
season's files (so far run only on Season 8, where it changed nothing), chaldea's icons, and whether stargazer's
phases 1 and 2 merge now that the extract carries text on day one.For when the local machine and its game install are out of reach, for example travelling with a tablet. A Claude Code cloud session (claude.ai/code: a machine that starts from a fresh clone of each repo) builds the feed from the fallback source, imports it into stargazer and pushes a branch there. The steps were run locally from fresh clones on 2026-10-11 (build 14368); they have not been run in a cloud session yet, see the list at the end of this section.
| While away | Why |
|---|---|
| Updates: hero, artifact, phantimal and charm text and numbers | The fallback carries the same tables and text. For build 14368 the feed built from it, imported into a fresh clone of stargazer, changed 0 files. |
| Waits: portraits and season icons | Art comes only from the install (Images). |
| Waits: the filter check (make audit) | It reads the heroes' battle scripts in the install. |
| Waits: a new hero's page | Stargazer adds a hero by a hand-written character file and a portrait. Until then its importer lists the hero as available in the feed and writes nothing for it. |
Differs: buffs.json | No scriptBuffs, and 14 fewer records in build 14368 (Model §3.10). It is one of our internal files; stargazer never sees it. |
Data 14368). A fresh clone has no baseline, so the diff line reads "no baseline yet" and passes;
the gates run as usual. A failed gate is fixed in config.json or the parser and committed here, then
build again..env.local, so the variable is given on the command line (or set once in the cloud environment).AGENTS.md), commit, push a branch and open a pull request, where
the changes are read before they go live. Stargazer is public: the branch name, commit message and pull request
describe the content change only, and never name this repo, the fallback or this routine.
git grep -i "afkj-parser\|afkj-data-viewer\|AFKJourneyData" there must find nothing.out/ is discarded with it, so there is no
baseline to keep (Model §7.4).What is lost without a baseline is the diff gate's warnings (Model §7.1): heroes with many changed levels (D2), a source older than the last import (D3) and removed heroes (D4). Stargazer's diff shows the first as large changes to one hero's files and the second as text going back to an earlier wording; its importer reports the third.
Back home. The baseline is still the one accepted before leaving, so the first make feed lists every change since then, most of it already live. Import as usual: stargazer's diff then shows only what is new since the last update made while away. Then ./afkj-parse accept, and make images and make audit for any hero that arrived meanwhile.
Backup: the local machine itself. A Remote Control session
(claude remote-control, started inside tmux from this folder before leaving) runs on the
local machine and is driven from the tablet. The install updates only when the game is opened, so this path builds
from the fallback too: git -C ../AFKJourneyData pull, then section 2.1 with
./afkj-parse build --source ../AFKJourneyData in place of the extract. accept works as usual;
the first diff after the source changes, in either direction, also lists the buffs.json difference
above (Model §7.4). This path depends on that machine staying up: as set up on
2026-10-11, after a power cut it stays off, and after any restart it waits at the disk-unlock screen for a password
typed at the keyboard.