poi-plugin-compass 0.1.0 → 0.1.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 (84) hide show
  1. package/README.md +33 -61
  2. package/assets/compass.css +4 -2
  3. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/1-4/I.json +1 -0
  4. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/1-5/H.json +1 -0
  5. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/1-5/I.json +1 -0
  6. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/1-6/I.json +1 -0
  7. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/1-6/L.json +1 -0
  8. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/2-1/F.json +1 -0
  9. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/2-3/M.json +1 -0
  10. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/2-4/E.json +1 -0
  11. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-1/B.json +1 -0
  12. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-1/D.json +1 -0
  13. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-2/J.json +1 -0
  14. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-2/K.json +1 -0
  15. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-3/K.json +1 -0
  16. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-4/B.json +1 -0
  17. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-4/I.json +1 -0
  18. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-4/M.json +1 -0
  19. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/3-4/N.json +1 -0
  20. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-1/E.json +1 -0
  21. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-3/A.json +1 -0
  22. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-3/G.json +1 -0
  23. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-3/I.json +1 -0
  24. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-3/L.json +1 -0
  25. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-4/B.json +1 -0
  26. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-4/F.json +1 -0
  27. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-4/G.json +1 -0
  28. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-4/H.json +1 -0
  29. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/E.json +1 -0
  30. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/F.json +1 -0
  31. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/G.json +1 -0
  32. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/J.json +1 -0
  33. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/N.json +1 -0
  34. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/O.json +1 -0
  35. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/4-5/S.json +1 -0
  36. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-2/I.json +1 -0
  37. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-2/K.json +1 -0
  38. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-2/L.json +1 -0
  39. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-3/C.json +1 -0
  40. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-3/M.json +1 -0
  41. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-3/N.json +1 -0
  42. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-4/F.json +1 -0
  43. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-4/G.json +1 -0
  44. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-4/L.json +1 -0
  45. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-5/J.json +1 -0
  46. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/A1.json +1 -0
  47. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/C1.json +1 -0
  48. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/P.json +1 -0
  49. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/Q1.json +1 -0
  50. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/Q2.json +1 -0
  51. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/T.json +1 -0
  52. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/5-6/V.json +1 -0
  53. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-1/D.json +1 -0
  54. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-1/I.json +1 -0
  55. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-1/J.json +1 -0
  56. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-2/B.json +1 -0
  57. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-2/F.json +1 -0
  58. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-2/H.json +1 -0
  59. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-2/I.json +1 -0
  60. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-4/E.json +1 -0
  61. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-4/G.json +1 -0
  62. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-4/H.json +1 -0
  63. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/6-4/L.json +1 -0
  64. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-1/B.json +1 -0
  65. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-1/C.json +1 -0
  66. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-2/H.json +1 -0
  67. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-2/J.json +1 -0
  68. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-3/B.json +1 -0
  69. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-3/J.json +1 -0
  70. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-3/M.json +1 -0
  71. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-3/N.json +1 -0
  72. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-4/G.json +1 -0
  73. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-4/H.json +1 -0
  74. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-5/C.json +1 -0
  75. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-5/E.json +1 -0
  76. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-5/L.json +1 -0
  77. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-5/N.json +1 -0
  78. package/data/sources/kcnav/enemies/2025-09-11_2026-09-11/7-5/R.json +1 -0
  79. package/data/sources/kcnav/enemies/manifest.json +771 -381
  80. package/docs/sources.md +2 -2
  81. package/enemies.js +1 -1
  82. package/package.json +4 -3
  83. package/scripts/cache-kcnav-enemies-long-low.js +115 -0
  84. package/scripts/cache-kcnav-enemies.js +9 -1
package/docs/sources.md CHANGED
@@ -31,9 +31,9 @@ data/kcnav-node-types-by-map.json
31
31
 
32
32
  ### 敌编成日期筛选(2026-09-11)
33
33
 
34
- 核对 `https://tsunkit.net/nav/scripts/routing.js`:`startDate`/`endDate` 分别使用参数 `start`/`end`,格式为 `YYYY-MM-DD`;`enemies` 面板将同一组筛选参数传给节点的 `enemycomps` 接口。本插件固定本轮起止日期,仅发送这两个参数,不发送司令部等级、血条、phase、舰队或路线限制。最近一个月采用当天 UTC 日期向前一个日历月(月末截到上月末日)。
34
+ 核对 `https://tsunkit.net/nav/scripts/routing.js`:`startDate`/`endDate` 分别使用参数 `start`/`end`,格式为 `YYYY-MM-DD`;`enemies` 面板将同一组筛选参数传给节点的 `enemycomps` 接口。本插件固定本轮起止日期,仅发送这两个参数,不发送司令部等级、血条、phase、舰队或路线限制。通常节点采用当天 UTC 日期向前一个日历月(月末截到上月末日);月度样本总数小于 100 的战斗节点另使用前一年同日到当天的窗口,并按节点保存实际使用的日期窗口。
35
35
 
36
- `https://tsunkit.net/lang/en/kcnav.json` 的 `panelEnemiesAirPowerHeader` 顺序为 `termAirAD / termAirAP / termAirAS / termAirASPlus`;与前端 `airpower[0..3]` 对应,分别为劣势、均势、优势、确保。`lbasAirpower` 使用相同顺序但单独显示。原始响应按日期窗口/海图/节点保存,manifest 记录请求 URL、SHA-256、每节点抓取时间和完成状态。遇到请求失败即停止,保留进度供重跑;运行时只读取本地缓存。
36
+ `https://tsunkit.net/lang/en/kcnav.json` 的 `panelEnemiesAirPowerHeader` 顺序为 `termAirAD / termAirAP / termAirAS / termAirASPlus`;与前端 `airpower[0..3]` 对应,分别为劣势、均势、优势、确保。`lbasAirpower` 使用相同顺序但单独显示。原始响应按日期窗口/海图/节点保存,manifest 记录请求 URL、SHA-256、每节点抓取时间和完成状态。遇到请求失败即停止,保留进度供重跑;运行时只读取本地缓存。低样本节点的一年窗口补采由 `npm run cache:kcnav-enemies:long-low` 执行,默认间隔 15 秒。
37
37
 
38
38
  每个 API entry 原样保留舰船顺序、装备、阵型及编成 masterId;舰船实体用 `mainFleet[].id` / `escortFleet[].id`。界面按 `count` 降序显示,以本节点返回记录的 count 总和计算样本占比。零样本不生成百分比;四档制空缺失时显示未知;任何编成缺制空时,节点整体范围也显示未知。混合阶段和司令部等级的占比仅描述当前抓取样本。
39
39
 
package/enemies.js CHANGED
@@ -44,7 +44,7 @@ function loadEnemies(mapId, node) {
44
44
  const source = manifest.nodes[`${mapId}/${node}`]
45
45
  if (!source) return { status: 'missing', window: manifest.window }
46
46
  const payload = readLocalJson(path.join(cacheRoot, source.file))
47
- return { status: 'ready', window: manifest.window, source, ...summarizeEntries(payload.result.entries) }
47
+ return { status: 'ready', window: source.window || manifest.window, source, ...summarizeEntries(payload.result.entries) }
48
48
  } catch (error) {
49
49
  return { status: error.code === 'ENOENT' ? 'missing' : 'invalid' }
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-compass",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "通常海域舰队罗盘",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -28,9 +28,10 @@
28
28
  "cache:kcwiki": "node scripts/cache-kcwiki-routes.js",
29
29
  "cache:kcnav-maps": "node scripts/cache-kcnav-maps.js",
30
30
  "cache:kcnav-enemies": "node scripts/cache-kcnav-enemies.js",
31
+ "cache:kcnav-enemies:long-low": "node scripts/cache-kcnav-enemies-long-low.js",
31
32
  "compress:kcnav-maps": "node scripts/compress-kcnav-maps.js --remove-png",
32
33
  "compress:kcnav-enemies": "node scripts/compact-kcnav-enemies.js",
33
- "check:syntax": "node --check index.js && node --check logic.js && node --check enemies.js && node --check exporters.js && node --check scripts/cache-kcnav-enemies.js && node --check scripts/compress-kcnav-maps.js && node --check scripts/compact-kcnav-enemies.js && node --check test.js && node --check test-enemies.js && node --check test-exporters.js",
34
+ "check:syntax": "node --check index.js && node --check logic.js && node --check enemies.js && node --check exporters.js && node --check scripts/cache-kcnav-enemies.js && node --check scripts/cache-kcnav-enemies-long-low.js && node --check scripts/compress-kcnav-maps.js && node --check scripts/compact-kcnav-enemies.js && node --check test.js && node --check test-enemies.js && node --check test-exporters.js",
34
35
  "test": "node test.js && node test-enemies.js && node test-exporters.js && node test-ui.js && node test-overview.js"
35
36
  },
36
37
  "keywords": [
@@ -51,7 +52,7 @@
51
52
  "icon": "fa/compass",
52
53
  "priority": 20,
53
54
  "apiVer": {
54
- "11.99.99": "0.1.0"
55
+ "11.99.99": "0.1.1"
55
56
  }
56
57
  },
57
58
  "peerDependencies": {
@@ -0,0 +1,115 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('crypto')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const nodeTypes = require('../data/kcnav-node-types-by-map.json')
7
+ const styles = require('../data/kcnav-node-types.json')
8
+ const { enemyUrl, validateResponse } = require('./cache-kcnav-enemies')
9
+
10
+ const root = path.join(__dirname, '../data/sources/kcnav/enemies')
11
+ const manifestPath = path.join(root, 'manifest.json')
12
+ const LOW_SAMPLE_THRESHOLD = 100
13
+ const INTERVAL_MS = 15000
14
+
15
+ function oneYearWindow(now = new Date()) {
16
+ const end = now.toISOString().slice(0, 10)
17
+ const startDate = new Date(`${end}T00:00:00Z`)
18
+ startDate.setUTCFullYear(startDate.getUTCFullYear() - 1)
19
+ return { start: startDate.toISOString().slice(0, 10), end }
20
+ }
21
+
22
+ function readJson(file) {
23
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
24
+ }
25
+
26
+ function sampleCount(payload) {
27
+ return (payload?.result?.entries || []).reduce((sum, entry) => sum + entry.count, 0)
28
+ }
29
+
30
+ function writeJson(file, value, compact = false) {
31
+ const content = `${JSON.stringify(value, null, compact ? 0 : 2)}\n`
32
+ fs.mkdirSync(path.dirname(file), { recursive: true })
33
+ fs.writeFileSync(`${file}.tmp`, content)
34
+ fs.renameSync(`${file}.tmp`, file)
35
+ return crypto.createHash('sha256').update(content).digest('hex')
36
+ }
37
+
38
+ function combatTargets() {
39
+ return Object.entries(nodeTypes).sort(([a], [b]) => a.localeCompare(b, undefined, { numeric: true }))
40
+ .flatMap(([mapId, nodes]) => Object.entries(nodes)
41
+ .filter(([, type]) => styles[type]?.hasEnemies)
42
+ .map(([node]) => ({ mapId, node })))
43
+ }
44
+
45
+ function currentSamples(manifest, target) {
46
+ const source = manifest.nodes[`${target.mapId}/${target.node}`]
47
+ if (!source) return 0
48
+ try {
49
+ return sampleCount(readJson(path.join(root, source.file)))
50
+ } catch (error) {
51
+ return 0
52
+ }
53
+ }
54
+
55
+ async function main() {
56
+ const manifest = readJson(manifestPath)
57
+ const window = oneYearWindow()
58
+ const targets = combatTargets().filter((target) => currentSamples(manifest, target) < LOW_SAMPLE_THRESHOLD)
59
+ const previousRun = manifest.longWindow
60
+ manifest.longWindow = {
61
+ start: window.start,
62
+ end: window.end,
63
+ threshold: LOW_SAMPLE_THRESHOLD,
64
+ intervalMs: INTERVAL_MS,
65
+ targetCount: targets.length,
66
+ complete: false,
67
+ lastRequestAt: previousRun?.start === window.start && previousRun?.end === window.end
68
+ ? previousRun.lastRequestAt : null,
69
+ }
70
+ writeJson(manifestPath, manifest)
71
+
72
+ for (const [index, target] of targets.entries()) {
73
+ const key = `${target.mapId}/${target.node}`
74
+ const existing = manifest.nodes[key]
75
+ const expectedFile = `${window.start}_${window.end}/${target.mapId}/${target.node}.json`
76
+ const existingFile = existing?.file === expectedFile ? path.join(root, existing.file) : null
77
+ if (existingFile && fs.existsSync(existingFile)) {
78
+ const hash = crypto.createHash('sha256').update(fs.readFileSync(existingFile)).digest('hex')
79
+ if (hash === existing.sha256) continue
80
+ }
81
+
82
+ const elapsed = Date.now() - Date.parse(manifest.longWindow.lastRequestAt || '1970-01-01')
83
+ const wait = Math.max(0, INTERVAL_MS - elapsed)
84
+ if (wait) await new Promise((resolve) => setTimeout(resolve, wait))
85
+ manifest.longWindow.lastRequestAt = new Date().toISOString()
86
+ writeJson(manifestPath, manifest)
87
+
88
+ const url = enemyUrl(target.mapId, target.node, window)
89
+ console.log(`${index + 1}/${targets.length} ${key} ${window.start}..${window.end}`)
90
+ const response = await fetch(url, { signal: AbortSignal.timeout(60000) })
91
+ if (!response.ok) throw new Error(`${key}: HTTP ${response.status}; cache retained, rerun to resume`)
92
+ const payload = await response.json()
93
+ const result = validateResponse(payload)
94
+ const file = expectedFile
95
+ const sha256 = writeJson(path.join(root, file), payload, true)
96
+ manifest.nodes[key] = {
97
+ file,
98
+ url,
99
+ sha256,
100
+ fetchedAt: new Date().toISOString(),
101
+ window,
102
+ entries: result.entries.length,
103
+ samples: sampleCount(payload),
104
+ }
105
+ writeJson(manifestPath, manifest)
106
+ }
107
+
108
+ manifest.longWindow.complete = true
109
+ manifest.longWindow.finishedAt = new Date().toISOString()
110
+ writeJson(manifestPath, manifest)
111
+ console.log(`Cached ${targets.length} low-sample enemy nodes for one year`)
112
+ }
113
+
114
+ if (require.main === module) main().catch((error) => { console.error(error.message); process.exitCode = 1 })
115
+ module.exports = { oneYearWindow, sampleCount, combatTargets }
@@ -85,7 +85,15 @@ async function main() {
85
85
  const result = validateResponse(payload)
86
86
  const file = `${window.start}_${window.end}/${mapId}/${node}.json`
87
87
  const sha256 = writeJson(path.join(root, file), payload, true)
88
- manifest.nodes[key] = { file, url, sha256, fetchedAt: new Date().toISOString(), entries: result.entries.length }
88
+ manifest.nodes[key] = {
89
+ file,
90
+ url,
91
+ sha256,
92
+ fetchedAt: new Date().toISOString(),
93
+ window,
94
+ entries: result.entries.length,
95
+ samples: result.entries.reduce((sum, entry) => sum + entry.count, 0),
96
+ }
89
97
  writeJson(manifestPath, manifest)
90
98
  }
91
99
  manifest.complete = true