@lycoristech/azurdata 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +123 -0
  3. package/data/.gitkeep +0 -0
  4. package/data/id-map.json +3530 -0
  5. package/data/ship-list.json +4478 -0
  6. package/data/ships.json +143282 -0
  7. package/data/version.json +5 -0
  8. package/dist/cjs/client/AzurData.js +69 -0
  9. package/dist/cjs/client/fetcher.js +121 -0
  10. package/dist/cjs/client/index.js +7 -0
  11. package/dist/cjs/client/ships.js +80 -0
  12. package/dist/cjs/config.js +14 -0
  13. package/dist/cjs/index.js +6 -0
  14. package/dist/cjs/ingest/files.js +134 -0
  15. package/dist/cjs/ingest/index.js +18 -0
  16. package/dist/cjs/ingest/upstream.js +12 -0
  17. package/dist/cjs/normalize/construction.js +186 -0
  18. package/dist/cjs/normalize/identity.js +131 -0
  19. package/dist/cjs/normalize/index.js +25 -0
  20. package/dist/cjs/normalize/misc.js +31 -0
  21. package/dist/cjs/normalize/retrofit.js +21 -0
  22. package/dist/cjs/normalize/ship.js +406 -0
  23. package/dist/cjs/normalize/skills.js +194 -0
  24. package/dist/cjs/normalize/skins.js +141 -0
  25. package/dist/cjs/normalize/slots.js +35 -0
  26. package/dist/cjs/normalize/stats.js +182 -0
  27. package/dist/cjs/output/index.js +20 -0
  28. package/dist/cjs/output/writeIdMap.js +52 -0
  29. package/dist/cjs/output/writeShipList.js +31 -0
  30. package/dist/cjs/output/writeShips.js +33 -0
  31. package/dist/cjs/output/writeVersion.js +23 -0
  32. package/dist/cjs/package.json +1 -0
  33. package/dist/cjs/schema/output/index.js +17 -0
  34. package/dist/cjs/schema/output/ship.js +169 -0
  35. package/dist/cjs/schema/raw/attributeInfoByType.js +11 -0
  36. package/dist/cjs/schema/raw/equipDataByType.js +12 -0
  37. package/dist/cjs/schema/raw/fleetTechGroup.js +10 -0
  38. package/dist/cjs/schema/raw/fleetTechShipClass.js +15 -0
  39. package/dist/cjs/schema/raw/fleetTechShipTemplate.js +20 -0
  40. package/dist/cjs/schema/raw/index.js +38 -0
  41. package/dist/cjs/schema/raw/shipDataBlueprint.js +11 -0
  42. package/dist/cjs/schema/raw/shipDataBreakout.js +17 -0
  43. package/dist/cjs/schema/raw/shipDataByStar.js +10 -0
  44. package/dist/cjs/schema/raw/shipDataByType.js +13 -0
  45. package/dist/cjs/schema/raw/shipDataCreateExchange.js +10 -0
  46. package/dist/cjs/schema/raw/shipDataGroup.js +22 -0
  47. package/dist/cjs/schema/raw/shipDataStatistics.js +24 -0
  48. package/dist/cjs/schema/raw/shipDataStrengthen.js +12 -0
  49. package/dist/cjs/schema/raw/shipDataTemplate.js +26 -0
  50. package/dist/cjs/schema/raw/shipDataTrans.js +13 -0
  51. package/dist/cjs/schema/raw/shipSkinTemplate.js +23 -0
  52. package/dist/cjs/schema/raw/shipSkinWords.js +11 -0
  53. package/dist/cjs/schema/raw/shopTemplate.js +14 -0
  54. package/dist/cjs/schema/raw/skillDataDisplay.js +11 -0
  55. package/dist/cjs/schema/raw/skillDataTemplate.js +17 -0
  56. package/dist/cjs/schema/raw/transformDataTemplate.js +18 -0
  57. package/dist/cjs/schema/raw/voiceActorCn.js +11 -0
  58. package/dist/cjs/translate/enums.js +131 -0
  59. package/dist/cjs/translate/index.js +19 -0
  60. package/dist/cjs/translate/lookups.js +63 -0
  61. package/dist/cjs/translate/strings.js +98 -0
  62. package/dist/client/AzurData.d.ts +25 -0
  63. package/dist/client/AzurData.d.ts.map +1 -0
  64. package/dist/client/AzurData.js +63 -0
  65. package/dist/client/AzurData.js.map +1 -0
  66. package/dist/client/fetcher.d.ts +25 -0
  67. package/dist/client/fetcher.d.ts.map +1 -0
  68. package/dist/client/fetcher.js +116 -0
  69. package/dist/client/fetcher.js.map +1 -0
  70. package/dist/client/index.d.ts +4 -0
  71. package/dist/client/index.d.ts.map +1 -0
  72. package/dist/client/index.js +3 -0
  73. package/dist/client/index.js.map +1 -0
  74. package/dist/client/ships.d.ts +20 -0
  75. package/dist/client/ships.d.ts.map +1 -0
  76. package/dist/client/ships.js +74 -0
  77. package/dist/client/ships.js.map +1 -0
  78. package/dist/config.d.ts +5 -0
  79. package/dist/config.d.ts.map +1 -0
  80. package/dist/config.js +12 -0
  81. package/dist/config.js.map +1 -0
  82. package/dist/index.d.ts +3 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/ingest/files.d.ts +25 -0
  87. package/dist/ingest/files.d.ts.map +1 -0
  88. package/dist/ingest/files.js +108 -0
  89. package/dist/ingest/files.js.map +1 -0
  90. package/dist/ingest/index.d.ts +3 -0
  91. package/dist/ingest/index.d.ts.map +1 -0
  92. package/dist/ingest/index.js +3 -0
  93. package/dist/ingest/index.js.map +1 -0
  94. package/dist/ingest/upstream.d.ts +4 -0
  95. package/dist/ingest/upstream.d.ts.map +1 -0
  96. package/dist/ingest/upstream.js +6 -0
  97. package/dist/ingest/upstream.js.map +1 -0
  98. package/dist/normalize/construction.d.ts +30 -0
  99. package/dist/normalize/construction.d.ts.map +1 -0
  100. package/dist/normalize/construction.js +184 -0
  101. package/dist/normalize/construction.js.map +1 -0
  102. package/dist/normalize/identity.d.ts +47 -0
  103. package/dist/normalize/identity.d.ts.map +1 -0
  104. package/dist/normalize/identity.js +129 -0
  105. package/dist/normalize/identity.js.map +1 -0
  106. package/dist/normalize/index.d.ts +10 -0
  107. package/dist/normalize/index.d.ts.map +1 -0
  108. package/dist/normalize/index.js +10 -0
  109. package/dist/normalize/index.js.map +1 -0
  110. package/dist/normalize/misc.d.ts +15 -0
  111. package/dist/normalize/misc.d.ts.map +1 -0
  112. package/dist/normalize/misc.js +29 -0
  113. package/dist/normalize/misc.js.map +1 -0
  114. package/dist/normalize/retrofit.d.ts +14 -0
  115. package/dist/normalize/retrofit.d.ts.map +1 -0
  116. package/dist/normalize/retrofit.js +19 -0
  117. package/dist/normalize/retrofit.js.map +1 -0
  118. package/dist/normalize/ship.d.ts +67 -0
  119. package/dist/normalize/ship.d.ts.map +1 -0
  120. package/dist/normalize/ship.js +369 -0
  121. package/dist/normalize/ship.js.map +1 -0
  122. package/dist/normalize/skills.d.ts +20 -0
  123. package/dist/normalize/skills.d.ts.map +1 -0
  124. package/dist/normalize/skills.js +192 -0
  125. package/dist/normalize/skills.js.map +1 -0
  126. package/dist/normalize/skins.d.ts +14 -0
  127. package/dist/normalize/skins.d.ts.map +1 -0
  128. package/dist/normalize/skins.js +139 -0
  129. package/dist/normalize/skins.js.map +1 -0
  130. package/dist/normalize/slots.d.ts +14 -0
  131. package/dist/normalize/slots.d.ts.map +1 -0
  132. package/dist/normalize/slots.js +33 -0
  133. package/dist/normalize/slots.js.map +1 -0
  134. package/dist/normalize/stats.d.ts +19 -0
  135. package/dist/normalize/stats.d.ts.map +1 -0
  136. package/dist/normalize/stats.js +180 -0
  137. package/dist/normalize/stats.js.map +1 -0
  138. package/dist/output/index.d.ts +5 -0
  139. package/dist/output/index.d.ts.map +1 -0
  140. package/dist/output/index.js +5 -0
  141. package/dist/output/index.js.map +1 -0
  142. package/dist/output/writeIdMap.d.ts +16 -0
  143. package/dist/output/writeIdMap.d.ts.map +1 -0
  144. package/dist/output/writeIdMap.js +47 -0
  145. package/dist/output/writeIdMap.js.map +1 -0
  146. package/dist/output/writeShipList.d.ts +9 -0
  147. package/dist/output/writeShipList.d.ts.map +1 -0
  148. package/dist/output/writeShipList.js +26 -0
  149. package/dist/output/writeShipList.js.map +1 -0
  150. package/dist/output/writeShips.d.ts +15 -0
  151. package/dist/output/writeShips.d.ts.map +1 -0
  152. package/dist/output/writeShips.js +28 -0
  153. package/dist/output/writeShips.js.map +1 -0
  154. package/dist/output/writeVersion.d.ts +11 -0
  155. package/dist/output/writeVersion.d.ts.map +1 -0
  156. package/dist/output/writeVersion.js +18 -0
  157. package/dist/output/writeVersion.js.map +1 -0
  158. package/dist/schema/output/index.d.ts +2 -0
  159. package/dist/schema/output/index.d.ts.map +1 -0
  160. package/dist/schema/output/index.js +2 -0
  161. package/dist/schema/output/index.js.map +1 -0
  162. package/dist/schema/output/ship.d.ts +977 -0
  163. package/dist/schema/output/ship.d.ts.map +1 -0
  164. package/dist/schema/output/ship.js +167 -0
  165. package/dist/schema/output/ship.js.map +1 -0
  166. package/dist/schema/raw/attributeInfoByType.d.ts +25 -0
  167. package/dist/schema/raw/attributeInfoByType.d.ts.map +1 -0
  168. package/dist/schema/raw/attributeInfoByType.js +9 -0
  169. package/dist/schema/raw/attributeInfoByType.js.map +1 -0
  170. package/dist/schema/raw/equipDataByType.d.ts +31 -0
  171. package/dist/schema/raw/equipDataByType.d.ts.map +1 -0
  172. package/dist/schema/raw/equipDataByType.js +10 -0
  173. package/dist/schema/raw/equipDataByType.js.map +1 -0
  174. package/dist/schema/raw/fleetTechGroup.d.ts +19 -0
  175. package/dist/schema/raw/fleetTechGroup.d.ts.map +1 -0
  176. package/dist/schema/raw/fleetTechGroup.js +8 -0
  177. package/dist/schema/raw/fleetTechGroup.js.map +1 -0
  178. package/dist/schema/raw/fleetTechShipClass.d.ts +49 -0
  179. package/dist/schema/raw/fleetTechShipClass.d.ts.map +1 -0
  180. package/dist/schema/raw/fleetTechShipClass.js +13 -0
  181. package/dist/schema/raw/fleetTechShipClass.js.map +1 -0
  182. package/dist/schema/raw/fleetTechShipTemplate.d.ts +79 -0
  183. package/dist/schema/raw/fleetTechShipTemplate.d.ts.map +1 -0
  184. package/dist/schema/raw/fleetTechShipTemplate.js +18 -0
  185. package/dist/schema/raw/fleetTechShipTemplate.js.map +1 -0
  186. package/dist/schema/raw/index.d.ts +23 -0
  187. package/dist/schema/raw/index.d.ts.map +1 -0
  188. package/dist/schema/raw/index.js +23 -0
  189. package/dist/schema/raw/index.js.map +1 -0
  190. package/dist/schema/raw/shipDataBlueprint.d.ts +25 -0
  191. package/dist/schema/raw/shipDataBlueprint.d.ts.map +1 -0
  192. package/dist/schema/raw/shipDataBlueprint.js +9 -0
  193. package/dist/schema/raw/shipDataBlueprint.js.map +1 -0
  194. package/dist/schema/raw/shipDataBreakout.d.ts +61 -0
  195. package/dist/schema/raw/shipDataBreakout.d.ts.map +1 -0
  196. package/dist/schema/raw/shipDataBreakout.js +15 -0
  197. package/dist/schema/raw/shipDataBreakout.js.map +1 -0
  198. package/dist/schema/raw/shipDataByStar.d.ts +19 -0
  199. package/dist/schema/raw/shipDataByStar.d.ts.map +1 -0
  200. package/dist/schema/raw/shipDataByStar.js +8 -0
  201. package/dist/schema/raw/shipDataByStar.js.map +1 -0
  202. package/dist/schema/raw/shipDataByType.d.ts +37 -0
  203. package/dist/schema/raw/shipDataByType.d.ts.map +1 -0
  204. package/dist/schema/raw/shipDataByType.js +11 -0
  205. package/dist/schema/raw/shipDataByType.js.map +1 -0
  206. package/dist/schema/raw/shipDataCreateExchange.d.ts +19 -0
  207. package/dist/schema/raw/shipDataCreateExchange.d.ts.map +1 -0
  208. package/dist/schema/raw/shipDataCreateExchange.js +8 -0
  209. package/dist/schema/raw/shipDataCreateExchange.js.map +1 -0
  210. package/dist/schema/raw/shipDataGroup.d.ts +79 -0
  211. package/dist/schema/raw/shipDataGroup.d.ts.map +1 -0
  212. package/dist/schema/raw/shipDataGroup.js +20 -0
  213. package/dist/schema/raw/shipDataGroup.js.map +1 -0
  214. package/dist/schema/raw/shipDataStatistics.d.ts +103 -0
  215. package/dist/schema/raw/shipDataStatistics.d.ts.map +1 -0
  216. package/dist/schema/raw/shipDataStatistics.js +22 -0
  217. package/dist/schema/raw/shipDataStatistics.js.map +1 -0
  218. package/dist/schema/raw/shipDataStrengthen.d.ts +31 -0
  219. package/dist/schema/raw/shipDataStrengthen.d.ts.map +1 -0
  220. package/dist/schema/raw/shipDataStrengthen.js +10 -0
  221. package/dist/schema/raw/shipDataStrengthen.js.map +1 -0
  222. package/dist/schema/raw/shipDataTemplate.d.ts +115 -0
  223. package/dist/schema/raw/shipDataTemplate.d.ts.map +1 -0
  224. package/dist/schema/raw/shipDataTemplate.js +24 -0
  225. package/dist/schema/raw/shipDataTemplate.js.map +1 -0
  226. package/dist/schema/raw/shipDataTrans.d.ts +37 -0
  227. package/dist/schema/raw/shipDataTrans.d.ts.map +1 -0
  228. package/dist/schema/raw/shipDataTrans.js +11 -0
  229. package/dist/schema/raw/shipDataTrans.js.map +1 -0
  230. package/dist/schema/raw/shipSkinTemplate.d.ts +97 -0
  231. package/dist/schema/raw/shipSkinTemplate.d.ts.map +1 -0
  232. package/dist/schema/raw/shipSkinTemplate.js +21 -0
  233. package/dist/schema/raw/shipSkinTemplate.js.map +1 -0
  234. package/dist/schema/raw/shipSkinWords.d.ts +25 -0
  235. package/dist/schema/raw/shipSkinWords.d.ts.map +1 -0
  236. package/dist/schema/raw/shipSkinWords.js +9 -0
  237. package/dist/schema/raw/shipSkinWords.js.map +1 -0
  238. package/dist/schema/raw/shopTemplate.d.ts +43 -0
  239. package/dist/schema/raw/shopTemplate.d.ts.map +1 -0
  240. package/dist/schema/raw/shopTemplate.js +12 -0
  241. package/dist/schema/raw/shopTemplate.js.map +1 -0
  242. package/dist/schema/raw/skillDataDisplay.d.ts +25 -0
  243. package/dist/schema/raw/skillDataDisplay.d.ts.map +1 -0
  244. package/dist/schema/raw/skillDataDisplay.js +9 -0
  245. package/dist/schema/raw/skillDataDisplay.js.map +1 -0
  246. package/dist/schema/raw/skillDataTemplate.d.ts +61 -0
  247. package/dist/schema/raw/skillDataTemplate.d.ts.map +1 -0
  248. package/dist/schema/raw/skillDataTemplate.js +15 -0
  249. package/dist/schema/raw/skillDataTemplate.js.map +1 -0
  250. package/dist/schema/raw/transformDataTemplate.d.ts +67 -0
  251. package/dist/schema/raw/transformDataTemplate.d.ts.map +1 -0
  252. package/dist/schema/raw/transformDataTemplate.js +16 -0
  253. package/dist/schema/raw/transformDataTemplate.js.map +1 -0
  254. package/dist/schema/raw/voiceActorCn.d.ts +25 -0
  255. package/dist/schema/raw/voiceActorCn.d.ts.map +1 -0
  256. package/dist/schema/raw/voiceActorCn.js +9 -0
  257. package/dist/schema/raw/voiceActorCn.js.map +1 -0
  258. package/dist/translate/enums.d.ts +34 -0
  259. package/dist/translate/enums.d.ts.map +1 -0
  260. package/dist/translate/enums.js +126 -0
  261. package/dist/translate/enums.js.map +1 -0
  262. package/dist/translate/index.d.ts +4 -0
  263. package/dist/translate/index.d.ts.map +1 -0
  264. package/dist/translate/index.js +4 -0
  265. package/dist/translate/index.js.map +1 -0
  266. package/dist/translate/lookups.d.ts +37 -0
  267. package/dist/translate/lookups.d.ts.map +1 -0
  268. package/dist/translate/lookups.js +61 -0
  269. package/dist/translate/lookups.js.map +1 -0
  270. package/dist/translate/strings.d.ts +40 -0
  271. package/dist/translate/strings.d.ts.map +1 -0
  272. package/dist/translate/strings.js +92 -0
  273. package/dist/translate/strings.js.map +1 -0
  274. package/package.json +37 -0
@@ -0,0 +1,5 @@
1
+ {
2
+ "generatedAt": "2026-04-09T22:17:20.993Z",
3
+ "shipCount": 746,
4
+ "schemaVersion": "0.0.1"
5
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AzurData = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const ships_js_1 = require("./ships.js");
10
+ const fetcher_js_1 = require("./fetcher.js");
11
+ function resolveThisDir() {
12
+ // CJS: __dirname is injected by Node
13
+ // ESM under tsx/ts-node: __dirname is polyfilled
14
+ // Pure ESM: falls back to cwd
15
+ try {
16
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
17
+ // @ts-ignore __dirname exists in CJS but not in ESM type defs
18
+ if (typeof __dirname === "string")
19
+ return __dirname; // eslint-disable-line no-undef
20
+ }
21
+ catch { /* not CJS */ }
22
+ return process.cwd();
23
+ }
24
+ class AzurData {
25
+ ships;
26
+ /** Sync constructor — loads baked data from disk. No network. */
27
+ constructor(options = {}) {
28
+ const dataDir = options.dataPath ?? AzurData.resolveDefaultDataPath();
29
+ const shipsJson = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dataDir, "ships.json"), "utf8"));
30
+ const idMapJson = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dataDir, "id-map.json"), "utf8"));
31
+ this.ships = new ships_js_1.Ships({ data: shipsJson, idMap: idMapJson });
32
+ }
33
+ /**
34
+ * Async factory — fetches latest data from GitHub, caches locally,
35
+ * falls back to baked data if offline.
36
+ *
37
+ * ```ts
38
+ * const azur = await AzurData.init({ fetchLatest: true });
39
+ * ```
40
+ */
41
+ static async init(options = {}) {
42
+ const { fetchLatest = true, ...fetchOpts } = options;
43
+ if (fetchLatest) {
44
+ const fetched = await (0, fetcher_js_1.fetchLatestData)(fetchOpts);
45
+ if (fetched) {
46
+ const instance = Object.create(AzurData.prototype);
47
+ instance.ships = new ships_js_1.Ships({
48
+ data: fetched.ships,
49
+ idMap: fetched.idMap,
50
+ });
51
+ return instance;
52
+ }
53
+ }
54
+ // Fallback to baked data
55
+ return new AzurData();
56
+ }
57
+ static resolveDefaultDataPath() {
58
+ const thisDir = resolveThisDir();
59
+ let candidate = thisDir;
60
+ for (let i = 0; i < 5; i++) {
61
+ const dataPath = node_path_1.default.resolve(candidate, "data");
62
+ if (node_fs_1.default.existsSync(node_path_1.default.join(dataPath, "ships.json")))
63
+ return dataPath;
64
+ candidate = node_path_1.default.resolve(candidate, "..");
65
+ }
66
+ return node_path_1.default.resolve(process.cwd(), "data");
67
+ }
68
+ }
69
+ exports.AzurData = AzurData;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fetchLatestData = fetchLatestData;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const config_js_1 = require("../config.js");
11
+ const DEFAULT_CACHE_DIR = node_path_1.default.join(node_os_1.default.homedir(), ".azurdata");
12
+ const DEFAULT_MAX_AGE = 6 * 60 * 60 * 1000; // 6 hours
13
+ /**
14
+ * Fetch latest data from GitHub, with local cache and baked-data fallback.
15
+ *
16
+ * Flow:
17
+ * 1. If cache exists and is fresh (< maxAge) → load from cache
18
+ * 2. Else fetch version.json from remote → compare with cache
19
+ * 3. If remote is newer → fetch ships.json + id-map.json, write to cache
20
+ * 4. If any fetch fails → fall back to cache, then to null (caller uses baked data)
21
+ */
22
+ async function fetchLatestData(options = {}) {
23
+ const cacheDir = options.cacheDir ?? DEFAULT_CACHE_DIR;
24
+ const maxAge = options.maxAge ?? DEFAULT_MAX_AGE;
25
+ const baseUrl = options.dataUrl ?? config_js_1.DATA_BASE_URL;
26
+ // Check cache freshness
27
+ const cachedShipsPath = node_path_1.default.join(cacheDir, "ships.json");
28
+ const cachedIdMapPath = node_path_1.default.join(cacheDir, "id-map.json");
29
+ const cachedVersionPath = node_path_1.default.join(cacheDir, "version.json");
30
+ if (isCacheFresh(cachedVersionPath, maxAge)) {
31
+ return loadFromCache(cachedShipsPath, cachedIdMapPath);
32
+ }
33
+ // Fetch remote version
34
+ let remoteVersion = null;
35
+ try {
36
+ remoteVersion = await fetchJson(`${baseUrl}/version.json`);
37
+ }
38
+ catch {
39
+ // Network error — try cache regardless of age
40
+ return loadFromCache(cachedShipsPath, cachedIdMapPath);
41
+ }
42
+ // Compare with cached version
43
+ if (remoteVersion && isCacheUpToDate(cachedVersionPath, remoteVersion)) {
44
+ // Touch the cache file to reset maxAge timer
45
+ touchFile(cachedVersionPath);
46
+ return loadFromCache(cachedShipsPath, cachedIdMapPath);
47
+ }
48
+ // Fetch fresh data
49
+ try {
50
+ const [ships, idMap] = await Promise.all([
51
+ fetchJson(`${baseUrl}/ships.json`),
52
+ fetchJson(`${baseUrl}/id-map.json`),
53
+ ]);
54
+ if (!ships || !idMap) {
55
+ return loadFromCache(cachedShipsPath, cachedIdMapPath);
56
+ }
57
+ // Write to cache atomically
58
+ writeCache(cacheDir, {
59
+ "ships.json": ships,
60
+ "id-map.json": idMap,
61
+ "version.json": remoteVersion,
62
+ });
63
+ return { ships, idMap };
64
+ }
65
+ catch {
66
+ return loadFromCache(cachedShipsPath, cachedIdMapPath);
67
+ }
68
+ }
69
+ // ── Helpers ──────────────────────────────────────────────────────────────────
70
+ async function fetchJson(url) {
71
+ const res = await fetch(url);
72
+ if (!res.ok)
73
+ return null;
74
+ return (await res.json());
75
+ }
76
+ function isCacheFresh(versionPath, maxAge) {
77
+ try {
78
+ const stat = node_fs_1.default.statSync(versionPath);
79
+ return Date.now() - stat.mtimeMs < maxAge;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ }
85
+ function isCacheUpToDate(versionPath, remote) {
86
+ try {
87
+ const cached = JSON.parse(node_fs_1.default.readFileSync(versionPath, "utf8"));
88
+ return cached.generatedAt === remote.generatedAt;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ function touchFile(filePath) {
95
+ try {
96
+ const now = new Date();
97
+ node_fs_1.default.utimesSync(filePath, now, now);
98
+ }
99
+ catch { /* ignore */ }
100
+ }
101
+ function loadFromCache(shipsPath, idMapPath) {
102
+ try {
103
+ if (!node_fs_1.default.existsSync(shipsPath) || !node_fs_1.default.existsSync(idMapPath))
104
+ return null;
105
+ const ships = JSON.parse(node_fs_1.default.readFileSync(shipsPath, "utf8"));
106
+ const idMap = JSON.parse(node_fs_1.default.readFileSync(idMapPath, "utf8"));
107
+ return { ships, idMap };
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ function writeCache(cacheDir, files) {
114
+ node_fs_1.default.mkdirSync(cacheDir, { recursive: true });
115
+ for (const [name, data] of Object.entries(files)) {
116
+ const target = node_path_1.default.join(cacheDir, name);
117
+ const tmp = target + ".tmp";
118
+ node_fs_1.default.writeFileSync(tmp, JSON.stringify(data), "utf8");
119
+ node_fs_1.default.renameSync(tmp, target); // atomic on most filesystems
120
+ }
121
+ }
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Ships = exports.AzurData = void 0;
4
+ var AzurData_js_1 = require("./AzurData.js");
5
+ Object.defineProperty(exports, "AzurData", { enumerable: true, get: function () { return AzurData_js_1.AzurData; } });
6
+ var ships_js_1 = require("./ships.js");
7
+ Object.defineProperty(exports, "Ships", { enumerable: true, get: function () { return ships_js_1.Ships; } });
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Ships = void 0;
7
+ const fuse_js_1 = __importDefault(require("fuse.js"));
8
+ class Ships {
9
+ data;
10
+ idMap;
11
+ fuse;
12
+ constructor(options) {
13
+ this.data = options.data;
14
+ this.idMap = options.idMap;
15
+ this.fuse = new fuse_js_1.default(Object.values(options.data), {
16
+ keys: [
17
+ { name: "names.en", weight: 3 },
18
+ { name: "names.code", weight: 2 },
19
+ { name: "names.jp", weight: 1 },
20
+ { name: "names.cn", weight: 1 },
21
+ ],
22
+ threshold: options.fuseThreshold ?? 0.4,
23
+ includeScore: true,
24
+ });
25
+ }
26
+ // Primary getter. Accepts:
27
+ // - canonical group_type: "20212"
28
+ // - legacy 3-digit code: "115" (via idMap)
29
+ // - any name in idMap (case-insensitive)
30
+ // - fuzzy match via fuse.js
31
+ get(nameOrId) {
32
+ if (typeof nameOrId !== "string" || nameOrId.length === 0)
33
+ return undefined;
34
+ // 1. Direct id hit
35
+ if (this.data[nameOrId])
36
+ return this.data[nameOrId];
37
+ // 2. Exact id-map hit (case sensitive first)
38
+ const directId = this.idMap[nameOrId];
39
+ if (directId && this.data[directId])
40
+ return this.data[directId];
41
+ // 3. Case-insensitive id-map hit
42
+ // Build a lowercased copy lazily
43
+ const lower = nameOrId.toLowerCase();
44
+ const ciIdMap = this.getCiIdMap();
45
+ const ciId = ciIdMap[lower];
46
+ if (ciId && this.data[ciId])
47
+ return this.data[ciId];
48
+ // 4. Fuse.js fuzzy
49
+ const results = this.fuse.search(nameOrId, { limit: 1 });
50
+ if (results.length > 0 && results[0].score !== undefined && results[0].score < 0.5) {
51
+ return results[0].item;
52
+ }
53
+ return undefined;
54
+ }
55
+ _ciIdMap;
56
+ getCiIdMap() {
57
+ if (!this._ciIdMap) {
58
+ this._ciIdMap = {};
59
+ for (const [k, v] of Object.entries(this.idMap)) {
60
+ this._ciIdMap[k.toLowerCase()] = v;
61
+ }
62
+ }
63
+ return this._ciIdMap;
64
+ }
65
+ getAll() {
66
+ return Object.values(this.data);
67
+ }
68
+ getById(id) {
69
+ return this.data[id];
70
+ }
71
+ count() {
72
+ return Object.keys(this.data).length;
73
+ }
74
+ // Search multiple matches
75
+ search(query, limit = 10) {
76
+ const results = this.fuse.search(query, { limit });
77
+ return results.map(r => r.item);
78
+ }
79
+ }
80
+ exports.Ships = Ships;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DATA_BASE_URL = exports.THUMBNAILS_URL = exports.PAINTINGS_URL = exports.IMAGE_BASE_URL = void 0;
4
+ // Central config for GitHub URLs.
5
+ const GITHUB_USER = "iujab";
6
+ const GITHUB_BRANCH = "main";
7
+ // Image URLs (azurlane-images repo)
8
+ const IMAGES_REPO = "azurlane-images";
9
+ exports.IMAGE_BASE_URL = `https://raw.githubusercontent.com/${GITHUB_USER}/${IMAGES_REPO}/${GITHUB_BRANCH}`;
10
+ exports.PAINTINGS_URL = `${exports.IMAGE_BASE_URL}/paintings`;
11
+ exports.THUMBNAILS_URL = `${exports.IMAGE_BASE_URL}/thumbnails`;
12
+ // Data URLs (azurdata repo — for runtime fetch)
13
+ const DATA_REPO = "AzurAPI";
14
+ exports.DATA_BASE_URL = `https://raw.githubusercontent.com/${GITHUB_USER}/${DATA_REPO}/${GITHUB_BRANCH}/data`;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Ships = exports.AzurData = void 0;
4
+ var index_js_1 = require("./client/index.js");
5
+ Object.defineProperty(exports, "AzurData", { enumerable: true, get: function () { return index_js_1.AzurData; } });
6
+ Object.defineProperty(exports, "Ships", { enumerable: true, get: function () { return index_js_1.Ships; } });
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadShipDataTemplate = loadShipDataTemplate;
7
+ exports.loadShipDataStatistics = loadShipDataStatistics;
8
+ exports.loadShipDataBreakout = loadShipDataBreakout;
9
+ exports.loadShipSkinWords = loadShipSkinWords;
10
+ exports.loadShopTemplate = loadShopTemplate;
11
+ exports.loadShipDataStrengthen = loadShipDataStrengthen;
12
+ exports.loadShipDataBlueprint = loadShipDataBlueprint;
13
+ exports.loadShipDataTrans = loadShipDataTrans;
14
+ exports.loadShipDataGroup = loadShipDataGroup;
15
+ exports.loadShipSkinTemplate = loadShipSkinTemplate;
16
+ exports.loadTransformDataTemplate = loadTransformDataTemplate;
17
+ exports.loadFleetTechShipTemplate = loadFleetTechShipTemplate;
18
+ exports.loadFleetTechShipClass = loadFleetTechShipClass;
19
+ exports.loadFleetTechGroup = loadFleetTechGroup;
20
+ exports.loadShipDataByType = loadShipDataByType;
21
+ exports.loadShipDataByStar = loadShipDataByStar;
22
+ exports.loadSkillDataTemplate = loadSkillDataTemplate;
23
+ exports.loadSkillDataDisplay = loadSkillDataDisplay;
24
+ exports.loadVoiceActorCn = loadVoiceActorCn;
25
+ exports.loadEquipDataByType = loadEquipDataByType;
26
+ exports.loadShipDataCreateExchange = loadShipDataCreateExchange;
27
+ exports.loadAttributeInfoByType = loadAttributeInfoByType;
28
+ const fs_1 = __importDefault(require("fs"));
29
+ const index_js_1 = require("../schema/raw/index.js");
30
+ const upstream_js_1 = require("./upstream.js");
31
+ function readJsonFile(filePath) {
32
+ try {
33
+ const raw = fs_1.default.readFileSync(filePath, "utf-8");
34
+ const parsed = JSON.parse(raw);
35
+ // Many upstream files have an "all" key containing an array of IDs
36
+ // (an index listing all record keys). Strip it before validation.
37
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
38
+ const obj = parsed;
39
+ if ("all" in obj && Array.isArray(obj["all"])) {
40
+ const { all: _all, ...rest } = obj;
41
+ return rest;
42
+ }
43
+ }
44
+ return parsed;
45
+ }
46
+ catch {
47
+ // File missing or unreadable — treat as empty
48
+ return {};
49
+ }
50
+ }
51
+ function loadFile(schema, filePath) {
52
+ const data = readJsonFile(filePath);
53
+ // Empty object stub — return as-is (valid empty record)
54
+ if (data !== null && typeof data === "object" && !Array.isArray(data) && Object.keys(data).length === 0) {
55
+ return {};
56
+ }
57
+ const result = schema.safeParse(data);
58
+ if (!result.success) {
59
+ throw new Error(`Zod parse failed for file: ${filePath}\n${result.error.message}`);
60
+ }
61
+ return result.data;
62
+ }
63
+ // ---------------------------------------------------------------------------
64
+ // sharecfgdata/ files
65
+ // ---------------------------------------------------------------------------
66
+ function loadShipDataTemplate(region = "EN") {
67
+ return loadFile(index_js_1.ShipDataTemplateSchema, (0, upstream_js_1.regionPath)(region, "sharecfgdata", "ship_data_template.json"));
68
+ }
69
+ function loadShipDataStatistics(region = "EN") {
70
+ return loadFile(index_js_1.ShipDataStatisticsSchema, (0, upstream_js_1.regionPath)(region, "sharecfgdata", "ship_data_statistics.json"));
71
+ }
72
+ function loadShipDataBreakout(region = "EN") {
73
+ return loadFile(index_js_1.ShipDataBreakoutSchema, (0, upstream_js_1.regionPath)(region, "sharecfgdata", "ship_data_breakout.json"));
74
+ }
75
+ function loadShipSkinWords(region = "EN") {
76
+ return loadFile(index_js_1.ShipSkinWordsSchema, (0, upstream_js_1.regionPath)(region, "sharecfgdata", "ship_skin_words.json"));
77
+ }
78
+ function loadShopTemplate(region = "EN") {
79
+ return loadFile(index_js_1.ShopTemplateSchema, (0, upstream_js_1.regionPath)(region, "sharecfgdata", "shop_template.json"));
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // ShareCfg/ files
83
+ // ---------------------------------------------------------------------------
84
+ function loadShipDataStrengthen(region = "EN") {
85
+ return loadFile(index_js_1.ShipDataStrengthenSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_strengthen.json"));
86
+ }
87
+ function loadShipDataBlueprint(region = "EN") {
88
+ return loadFile(index_js_1.ShipDataBlueprintSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_blueprint.json"));
89
+ }
90
+ function loadShipDataTrans(region = "EN") {
91
+ return loadFile(index_js_1.ShipDataTransSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_trans.json"));
92
+ }
93
+ function loadShipDataGroup(region = "EN") {
94
+ return loadFile(index_js_1.ShipDataGroupSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_group.json"));
95
+ }
96
+ function loadShipSkinTemplate(region = "EN") {
97
+ return loadFile(index_js_1.ShipSkinTemplateSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_skin_template.json"));
98
+ }
99
+ function loadTransformDataTemplate(region = "EN") {
100
+ return loadFile(index_js_1.TransformDataTemplateSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "transform_data_template.json"));
101
+ }
102
+ function loadFleetTechShipTemplate(region = "EN") {
103
+ return loadFile(index_js_1.FleetTechShipTemplateSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "fleet_tech_ship_template.json"));
104
+ }
105
+ function loadFleetTechShipClass(region = "EN") {
106
+ return loadFile(index_js_1.FleetTechShipClassSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "fleet_tech_ship_class.json"));
107
+ }
108
+ function loadFleetTechGroup(region = "EN") {
109
+ return loadFile(index_js_1.FleetTechGroupSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "fleet_tech_group.json"));
110
+ }
111
+ function loadShipDataByType(region = "EN") {
112
+ return loadFile(index_js_1.ShipDataByTypeSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_by_type.json"));
113
+ }
114
+ function loadShipDataByStar(region = "EN") {
115
+ return loadFile(index_js_1.ShipDataByStarSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_by_star.json"));
116
+ }
117
+ function loadSkillDataTemplate(region = "EN") {
118
+ return loadFile(index_js_1.SkillDataTemplateSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "skill_data_template.json"));
119
+ }
120
+ function loadSkillDataDisplay(region = "EN") {
121
+ return loadFile(index_js_1.SkillDataDisplaySchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "skill_data_display.json"));
122
+ }
123
+ function loadVoiceActorCn(region = "EN") {
124
+ return loadFile(index_js_1.VoiceActorCnSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "voice_actor_CN.json"));
125
+ }
126
+ function loadEquipDataByType(region = "EN") {
127
+ return loadFile(index_js_1.EquipDataByTypeSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "equip_data_by_type.json"));
128
+ }
129
+ function loadShipDataCreateExchange(region = "EN") {
130
+ return loadFile(index_js_1.ShipDataCreateExchangeSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "ship_data_create_exchange.json"));
131
+ }
132
+ function loadAttributeInfoByType(region = "EN") {
133
+ return loadFile(index_js_1.AttributeInfoByTypeSchema, (0, upstream_js_1.regionPath)(region, "ShareCfg", "attribute_info_by_type.json"));
134
+ }
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./upstream.js"), exports);
18
+ __exportStar(require("./files.js"), exports);
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UPSTREAM_ROOT = void 0;
7
+ exports.regionPath = regionPath;
8
+ const path_1 = __importDefault(require("path"));
9
+ exports.UPSTREAM_ROOT = path_1.default.resolve(process.cwd(), "upstream/azurlane-data");
10
+ function regionPath(region, ...segments) {
11
+ return path_1.default.join(exports.UPSTREAM_ROOT, region, ...segments);
12
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeConstruction = normalizeConstruction;
4
+ const enums_js_1 = require("../translate/enums.js");
5
+ // ---------------------------------------------------------------------------
6
+ // Internal helpers
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * Flatten all string tokens from a description entry tuple.
10
+ * description entries are [string, unknown, number?] tuples.
11
+ */
12
+ function descEntryText(entry) {
13
+ return entry[0];
14
+ }
15
+ // ---------------------------------------------------------------------------
16
+ // Main
17
+ // ---------------------------------------------------------------------------
18
+ function normalizeConstruction(inputs) {
19
+ const { groupType, groupRow, lb3RowId, shipType, initialStar, fleetTechShipTemplate, shipDataCreateExchange, shipDataByType, shipDataByStar, lookups, constructionTimeOverride, } = inputs;
20
+ const result = {};
21
+ // -------------------------------------------------------------------------
22
+ // 1. construction.availableIn
23
+ // -------------------------------------------------------------------------
24
+ const descriptions = groupRow.description ?? [];
25
+ let lightFlag = false;
26
+ let heavyFlag = false;
27
+ let aviationFlag = false;
28
+ let limitedFlag = false;
29
+ for (const entry of descriptions) {
30
+ const text = descEntryText(entry);
31
+ const lower = text.toLowerCase();
32
+ if (lower.includes("light construction") ||
33
+ (lower.includes("light") && lower.includes("construction"))) {
34
+ lightFlag = true;
35
+ }
36
+ if (lower.includes("heavy construction") ||
37
+ (lower.includes("heavy") && lower.includes("construction"))) {
38
+ heavyFlag = true;
39
+ }
40
+ if (lower.includes("special construction") ||
41
+ (lower.includes("aviation") && lower.includes("construction"))) {
42
+ aviationFlag = true;
43
+ }
44
+ if (lower.startsWith("event:") ||
45
+ lower === "limited build") {
46
+ limitedFlag = true;
47
+ }
48
+ }
49
+ // exchange: true if lb3RowId appears as a value in any exchange_ship_id array
50
+ let exchangeFlag = false;
51
+ const lb3RowIdNum = Number(lb3RowId);
52
+ for (const exchangeRow of Object.values(shipDataCreateExchange)) {
53
+ if (exchangeRow === undefined)
54
+ continue;
55
+ const ids = exchangeRow.exchange_ship_id ?? [];
56
+ if (ids.includes(lb3RowIdNum)) {
57
+ exchangeFlag = true;
58
+ break;
59
+ }
60
+ }
61
+ const anyAvailableIn = lightFlag || heavyFlag || aviationFlag || limitedFlag || exchangeFlag;
62
+ const hasConstructionTime = constructionTimeOverride !== undefined;
63
+ if (anyAvailableIn || hasConstructionTime) {
64
+ const availableIn = anyAvailableIn
65
+ ? {
66
+ light: lightFlag,
67
+ heavy: heavyFlag,
68
+ aviation: aviationFlag,
69
+ limited: limitedFlag,
70
+ exchange: exchangeFlag,
71
+ }
72
+ : undefined;
73
+ result.construction = {
74
+ ...(hasConstructionTime ? { constructionTime: constructionTimeOverride } : {}),
75
+ ...(availableIn !== undefined ? { availableIn } : {}),
76
+ };
77
+ }
78
+ // -------------------------------------------------------------------------
79
+ // 2. scrapValue
80
+ // -------------------------------------------------------------------------
81
+ const typeRow = shipDataByType[String(shipType)];
82
+ const goldRatio = typeRow?.distory_resource_gold_ratio ?? 0;
83
+ const fixGold = typeRow?.fix_resource_gold ?? 0;
84
+ const coin = Math.floor(goldRatio * 120 + fixGold);
85
+ // medal: from ship_data_by_star[initialStar].destory_item — find item_id === 15001
86
+ const starRow = shipDataByStar[String(initialStar)];
87
+ const destroyItems = starRow?.destory_item ?? [];
88
+ let medal = 0;
89
+ for (const [, itemId, qty] of destroyItems) {
90
+ if (itemId === 15001) {
91
+ medal = qty;
92
+ break;
93
+ }
94
+ }
95
+ if (coin > 0 || medal > 0) {
96
+ result.scrapValue = { coin, oil: 0, medal };
97
+ }
98
+ // -------------------------------------------------------------------------
99
+ // 3. fleetTech
100
+ // -------------------------------------------------------------------------
101
+ const techRow = fleetTechShipTemplate[groupType];
102
+ if (techRow !== undefined) {
103
+ const collection = techRow.pt_get;
104
+ const maxLimitBreak = techRow.pt_upgrage;
105
+ const maxLevel = techRow.pt_level;
106
+ const total = collection + maxLimitBreak + maxLevel;
107
+ const techPoints = {
108
+ collection,
109
+ maxLimitBreak,
110
+ maxLevel,
111
+ total,
112
+ };
113
+ // collection bonus
114
+ let collectionBonus;
115
+ if (techRow.add_get_attr !== 0 && techRow.add_get_value !== 0) {
116
+ collectionBonus = {
117
+ stat: enums_js_1.FLEET_TECH_STAT_MAP[techRow.add_get_attr] ?? "unknown",
118
+ bonus: techRow.add_get_value,
119
+ applicable: techRow.add_get_shiptype.map((id) => lookups.hullTypeName(id)),
120
+ };
121
+ }
122
+ // maxLevel bonus
123
+ let maxLevelBonus;
124
+ if (techRow.add_level_attr !== 0 && techRow.add_level_value !== 0) {
125
+ maxLevelBonus = {
126
+ stat: enums_js_1.FLEET_TECH_STAT_MAP[techRow.add_level_attr] ?? "unknown",
127
+ bonus: techRow.add_level_value,
128
+ applicable: techRow.add_level_shiptype.map((id) => lookups.hullTypeName(id)),
129
+ };
130
+ }
131
+ result.fleetTech = {
132
+ techPoints,
133
+ statsBonus: {
134
+ ...(collectionBonus !== undefined ? { collection: collectionBonus } : {}),
135
+ ...(maxLevelBonus !== undefined ? { maxLevel: maxLevelBonus } : {}),
136
+ },
137
+ };
138
+ }
139
+ // -------------------------------------------------------------------------
140
+ // 4. obtainedFrom
141
+ // -------------------------------------------------------------------------
142
+ const mapPrefixes = ["explore chapter", "exploring stage"];
143
+ const fromMaps = [];
144
+ let firstNonMapText;
145
+ for (const entry of descriptions) {
146
+ const text = descEntryText(entry);
147
+ const lower = text.toLowerCase();
148
+ const isMap = mapPrefixes.some((prefix) => lower.startsWith(prefix));
149
+ if (isMap) {
150
+ // Extract the stage code using a regex: match Chapter/Stage followed by digits/dashes
151
+ const match = /\b(?:Chapter|Stage)\s+([0-9\-SEX\s]+)/i.exec(text);
152
+ if (match !== null && match[1] !== undefined) {
153
+ fromMaps.push(`Chapter ${match[1].trim()}`);
154
+ }
155
+ else {
156
+ // Fallback: take last whitespace-separated token
157
+ const tokens = text.trim().split(/\s+/);
158
+ const last = tokens[tokens.length - 1];
159
+ if (last !== undefined) {
160
+ fromMaps.push(last);
161
+ }
162
+ }
163
+ }
164
+ else if (firstNonMapText === undefined) {
165
+ firstNonMapText = text;
166
+ }
167
+ }
168
+ let obtainedFromStr;
169
+ if (firstNonMapText !== undefined) {
170
+ obtainedFromStr = firstNonMapText;
171
+ }
172
+ else if (fromMaps.length > 0) {
173
+ obtainedFromStr = "Map Drop";
174
+ }
175
+ else {
176
+ obtainedFromStr = "Unknown";
177
+ }
178
+ const shouldEmitObtainedFrom = fromMaps.length > 0 || obtainedFromStr !== "Unknown";
179
+ if (shouldEmitObtainedFrom) {
180
+ result.obtainedFrom = {
181
+ ...(fromMaps.length > 0 ? { fromMaps } : {}),
182
+ obtainedFrom: obtainedFromStr,
183
+ };
184
+ }
185
+ return result;
186
+ }