Compare commits

..

210 Commits

Author SHA1 Message Date
tei
a3c3b4e540 docs: record V28 GSRL release verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-21 16:26:32 +09:00
tei
e48258434e release: finalize unified particle.28
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-21 16:24:22 +09:00
tei
4d9cb0ef69 release: prepare unified particle.28 2026-10-21 15:39:08 +09:00
tei
1aba9875f0 feat(ios): expose lossless-or-reject style filter API 2026-10-21 15:38:35 +09:00
tei
9d6c9e9684 docs: audit v27 unified GSRL release
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:56:07 +09:00
tei
3c3dcb9d18 release: finalize unified particle.27
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:53:27 +09:00
tei
7f92f71416 release: record v27 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:53:07 +09:00
tei
970e3ad606 release: prepare unified particle.27
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:16:33 +09:00
tei
a9cea8b581 fix: retain correct Metal composite vertex buffer slot
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-14 10:16:14 +09:00
tei
b0f34cc9a2 docs: record V26 GSRL final public receipt
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:39:31 +09:00
tei
e3cd9eba3c release: finalize unified particle.26
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:35:27 +09:00
tei
21c4450e01 release: record v26 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-13 07:35:04 +09:00
tei
fce326a900 release: prepare unified particle.26
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 23:29:55 +09:00
tei
343ca2fa68 fix: secure Symbol Retained drawable lifetime and record investigations
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 23:27:06 +09:00
tei
badb515d59 docs: audit v25 GSRL release and remote verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 07:26:56 +09:00
tei
3774d0a841 release: finalize unified particle.25
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-12 07:25:01 +09:00
tei
4b2f3c8d38 release: record v25 GSRL source lock and receipt instructions 2026-10-12 07:24:23 +09:00
tei
f32f6f45fd build: fix v25 iOS device-only Bazel release inputs
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 22:28:58 +09:00
tei
d5841e6471 release: prepare unified particle.25
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 19:57:17 +09:00
tei
e3f5760527 test: record STEP-119 Android RCRR CPU validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 19:50:28 +09:00
tei
32ac795fc4 test: benchmark particle-only C FastPath CPU across R layouts
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 18:02:33 +09:00
tei
1072816740 test: add STEP-119 RCRR boundary variants and audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:42:26 +09:00
tei
46c88b0209 docs: record STEP-119 first observed Android PBF Fast
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:18:06 +09:00
tei
499f6fb2c2 docs: record STEP-119 real PBF Fast and missing-source control
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 17:07:35 +09:00
tei
3d989a00a8 feat: admit settled Symbol render results to retained cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 16:47:21 +09:00
tei
869930046b docs: record STEP-118 no-tide PBF Android Fast result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 14:08:49 +09:00
tei
3051196054 test: add isolated no-tide PBF retained cache scenario
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:57:53 +09:00
tei
fb8e78ac1a test: satisfy STEP-117 strict NDK C++ warning checks
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:06:11 +09:00
tei
696899e113 test: record STEP-117 PBF R cache admission blockers
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 13:03:47 +09:00
tei
43e5f5b915 docs: record STEP-116 Android PBF Fast Path device result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 12:19:33 +09:00
tei
c48a72a9d4 fix: guard STEP-113 empty annotation and expose PBF blockers
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 11:50:03 +09:00
tei
e2d202b560 test: prepare STEP-113 PBF Fast Path APK retest
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 10:34:08 +09:00
tei
21a1081643 feat: prepare STEP-114 guarded fast smoke validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 10:01:10 +09:00
tei
b02e54de3d docs: record STEP-113 full-frame source mutation validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 09:24:08 +09:00
tei
92efa36f4c feat: add STEP-113 Android PBF fast gate diagnostics
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 09:11:27 +09:00
tei
c6d13e1059 docs: record STEP-112 RDC Git verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 01:14:10 +09:00
tei
abc137ca96 docs: investigate STEP-112 particle full render and symbol cache
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 01:12:24 +09:00
tei
41b416c261 docs: add STEP-112 particle full-render cache investigation task
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-11 00:48:49 +09:00
tei
50ae48ad70 docs: record STEP-111 per-R placement feasibility partial
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 17:07:15 +09:00
tei
7ec9ac8c16 docs: record STEP-110 Git delivery evidence
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 16:18:29 +09:00
tei
cdb45c023b docs: investigate STEP-110 R0 placement attribution
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 16:17:16 +09:00
tei
2df504cdc0 docs: record STEP-109 V23 CPU investigation handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 12:15:10 +09:00
tei
2aae38f402 perf(particle): skip repeated field snapshot deep comparisons
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-10 12:14:06 +09:00
tei
5b8bdfbfbb docs: close STEP-108 GSRL v23 final release audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:55:51 +09:00
tei
a0a4787b43 release: finalize unified particle.23
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:52:26 +09:00
tei
061b24a4bb release: record v23 GSRL source lock and receipt instructions
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 23:52:01 +09:00
tei
87f3d1c59a release: prepare unified particle.23
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:53:06 +09:00
tei
93fb20479e fix(release): support incremental particle revisions after native 13.6.1 upgrade
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:52:08 +09:00
tei
51c38b1296 docs: record STEP-107 iOS build and remote verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:47:40 +09:00
tei
016bf5ad93 fix(ios): guard Darwin style CoreOnly layer peer access
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 21:46:59 +09:00
tei
24105b1be7 docs(ios): record MLNStyle nil peer tap crash investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 20:44:36 +09:00
tei
bf7f6193e0 release: finalize unified particle.22
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 16:17:29 +09:00
tei
7cfad1c72f build(release): pin built source and correct SwiftPM RN dependency note
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 16:13:30 +09:00
tei
ea5bd393c6 release: prepare unified particle.22
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 14:08:10 +09:00
tei
40b79764ae build(release): support 13.6.1 v22 and RN 11.5.0 version lock 2026-10-09 14:07:18 +09:00
tei
69922b795a docs(android): close STEP-106 FZG T2 regression PASS
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 13:17:29 +09:00
tei
23f7b8a4d7 docs(android): record T2 old-version reproduction and pending rebuild gate
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 13:03:02 +09:00
tei
86906c8a6a test(android): isolate internal annotation symbol from STEP-102 retained ranges 2026-10-09 13:01:43 +09:00
tei
a49dd936c4 docs(android): close FZG native runtime audit with placement finding
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 12:35:40 +09:00
tei
2c6f673e54 docs(android): record FZG 13.6.1 ADB runtime matrix and T2 blocker 2026-10-09 12:35:24 +09:00
tei
c30add4d33 docs(android): record FZG MapLibre 13.6.1 APK installation verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 12:05:55 +09:00
tei
73db0356ac build(android): permit signed Sentry auxiliary ABIs in FZG validation APK 2026-10-09 12:04:38 +09:00
tei
30d0e6665e docs(upgrade): record android 13.6.1 native OpenGL compile PASS
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 10:31:21 +09:00
tei
a4b5f4eaa0 docs(upgrade): record official native 13.6.1 merge checkpoint 2026-10-09 09:37:47 +09:00
tei
f8378fd711 merge(upgrade): port particle extensions onto official native 13.6.1 (unverified) 2026-10-09 09:37:07 +09:00
tei
e4c7af7c40 docs(upgrade): establish native 13.6.1 migration gate and recorder 2026-10-09 09:23:25 +09:00
tei
b433775348 docs: record upstream audit git closure
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 09:06:09 +09:00
tei
2b4075b60b docs: audit upstream MapLibre Native upgrade compatibility
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-09 09:05:22 +09:00
tei
630ec65768 release: finalize unified particle.21
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-08 13:33:00 +09:00
tei
16a5d15f16 release: prepare unified particle.21 2026-10-08 12:03:17 +09:00
tei
d3bf98bc55 docs: record STEP-102 V21 validation waiver 2026-10-08 12:03:03 +09:00
tei
0ce551ad37 feat(step102): isolate placement-free retained segments 2026-10-08 09:59:58 +09:00
tei
6df21dac38 docs: 记录 RenderSegment 多 Retained Placement 审查说明
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-07 23:06:51 +09:00
tei
53a844e818 release: finalize unified particle.20
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-07 21:35:44 +09:00
tei
a4c6047d70 release: prepare unified particle.20 2026-10-07 18:06:02 +09:00
tei
078ba40b76 release: build iOS distribution with bazel opt 2026-10-07 18:05:30 +09:00
tei
6b48f905d2 release: finalize unified particle.19
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-07 16:31:36 +09:00
tei
7c5ed27b0b release: prepare unified particle.19 2026-10-07 08:42:32 +09:00
tei
01863569fb docs: close STEP-101 teardown validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-07 08:42:09 +09:00
tei
64cf8f4435 fix: make render orchestrator teardown safe
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-07 08:41:08 +09:00
tei
d29e84c1a9 release: finalize unified particle.18
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-06 20:52:00 +09:00
tei
2780550b62 release: prepare unified particle.18 2026-10-06 17:20:34 +09:00
tei
524aaf4af5 fix: localize retained segment source invalidation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-06 17:18:09 +09:00
tei
c4b481fd2c docs: close STEP-099 render source diagnostics
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-10-06 15:33:12 +09:00
tei
8a5214d92d diagnostics: add STEP-099 render source readiness audit 2026-10-06 15:15:12 +09:00
tei
e8b451fe70 release: finalize unified particle.17
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-29 09:44:09 +09:00
tei
95f0a45d6d release: prepare unified particle.17 2026-09-28 21:40:56 +09:00
tei
baf9374318 fix: reset V17 weather runtime across Android and iOS
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-28 21:37:16 +09:00
tei
1e7215e2a0 release: finalize unified particle.16
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-27 22:34:41 +09:00
tei
e9f84a8ec6 build(ios): include WeatherColor lifecycle header in Bazel core 2026-09-27 21:35:45 +09:00
tei
2e119961d9 release: prepare unified particle.16 2026-09-27 19:21:52 +09:00
tei
750c14f527 docs: 校正 V15 WeatherColor 阶段状态
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-27 18:44:50 +09:00
tei
4712e1752a docs: 记录 V15 WeatherColor RDC 验证与推送
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-27 18:43:14 +09:00
tei
5af91fe7a1 fix: 修复 V15 WeatherColor Metal 纹理生命周期
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-27 18:41:56 +09:00
tei
cd71640a84 release: finalize unified particle.15
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-27 11:10:40 +09:00
tei
1fdc5df625 docs: plan NavSea PBF tile codec across platforms 2026-09-27 10:56:25 +09:00
tei
2fca4a71a7 build(ios): declare particle GPU math header in Bazel core 2026-09-27 10:40:03 +09:00
tei
f081180bed release: prepare unified particle.15 2026-09-27 08:43:08 +09:00
tei
159b0ad5f1 feat(step098): single-frame display and keyed rolling cache 2026-09-27 08:42:00 +09:00
tei
f3a44c7d5f feat(step097): preserve B fixes for unified V15 source 2026-09-27 08:41:27 +09:00
tei
b1239ac3b1 docs(step097): close A2 Android Core validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 15:18:45 +09:00
tei
f49ffa68a1 fix(step097): complete A2 clock and GPU field correctness
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 15:17:43 +09:00
tei
5bb1a0e635 docs(step097): record A2 expanded regression and legacy test mismatch
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 11:54:20 +09:00
tei
bd1cfe95b7 docs(step097): record A2 partial RDC verification and resume gate
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 11:23:17 +09:00
tei
72d9369384 docs(step097): close A1 Android gate with iOS runtime waiver
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 10:58:57 +09:00
tei
8c006ac83d fix(step097): compile Metal retained attachment tests
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 10:47:09 +09:00
tei
0aab2bdc83 docs(step097): record A1 test entries and RDC checks
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 09:15:31 +09:00
tei
d0ced72351 test(step097): add A1 renderer regression entry points 2026-09-26 09:15:10 +09:00
tei
be5b9080ed docs: validate STEP-097 cache diagnostics on FZG1
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 08:21:42 +09:00
tei
237a3e6608 docs: verify STEP-097 A1 diagnostic GTest on Mac
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-26 08:07:20 +09:00
tei
0d13552bed docs: record STEP-097 A1 FZG1 APK smoke validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 23:55:57 +09:00
tei
91c2491b0e docs: record STEP-097 partial A1 handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 22:28:23 +09:00
tei
e67e4961b5 fix: checkpoint STEP-097 A1 renderer correctness 2026-09-25 22:26:33 +09:00
tei
720d309364 docs: plan STEP-097 three-review V13 fixes
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 22:08:37 +09:00
tei
b5c1c4879a docs: deliver isolated blind V13 audit STEP-096
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 21:31:27 +09:00
tei
82e8a18de7 docs: define isolated V13 blind review STEP-096
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 21:06:43 +09:00
tei
4bc9d0ca56 docs: record STEP-095 delivery verification
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 20:48:48 +09:00
tei
ad5f92c48e docs: audit V13 source and performance for STEP-095
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 20:46:44 +09:00
tei
05878d6623 docs: define STEP-095 independent V13 code review
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 18:53:45 +09:00
tei
45c4dca849 docs: archive independent Particle V13 code review
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 18:52:06 +09:00
tei
ee0b89e711 release: finalize unified particle.13
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 14:56:05 +09:00
tei
ddf20dec9f release: prepare unified particle.13 2026-09-25 14:08:28 +09:00
tei
9d1f66c169 docs: close STEP-094 render segment validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 13:56:50 +09:00
tei
016fd1fff6 feat: add generic render segment model
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 13:55:19 +09:00
tei
8fbb892a12 docs: add STEP-094 generic render segment task
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-25 11:47:18 +09:00
tei
7eaff6e123 release: finalize unified particle.12
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 18:42:47 +09:00
tei
b001fd63c2 fix: include continuous range header in bazel core 2026-09-24 17:14:47 +09:00
tei
64715e4ce8 release: prepare unified particle.12 2026-09-24 15:58:16 +09:00
tei
0dd475c22f feat: add STEP-093 iPad validation menu
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 15:28:29 +09:00
tei
66690b460e docs: hand off STEP-093 runtime validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 14:42:25 +09:00
tei
b3ec8edc87 feat: add STEP-093 runtime validation tooling 2026-09-24 14:39:39 +09:00
tei
8a5d88008c docs: close STEP-092 native validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 12:18:03 +09:00
tei
4908244d2d fix: close STEP-092 native validation issues
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 12:14:17 +09:00
tei
b89c8c8cb4 docs: record STEP-092 continuous ranges
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 11:47:01 +09:00
tei
a9eaec6b65 feat: add continuous retained render ranges
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-24 11:46:19 +09:00
tei
b29e564944 release: finalize unified particle.11
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 16:05:38 +09:00
tei
57b42676a1 build: sync particle renderer sources for iOS Bazel 2026-09-23 14:50:37 +09:00
tei
b17b9b0b1c release: prepare unified particle.11 2026-09-23 11:58:42 +09:00
tei
e70b795098 docs: preserve particle task history and RDC workflow rules
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 11:57:44 +09:00
tei
f8f84da6a1 build: stabilize Android particle validation APK pipeline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 11:36:06 +09:00
tei
a2642bbedf docs: record STEP-091 zoom out reseed
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 10:08:47 +09:00
tei
4286c16a53 fix: reseed particles after zoom out
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 10:05:01 +09:00
tei
297b912620 docs: record STEP-090 stability retest
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 08:46:30 +09:00
tei
746b316de0 docs: finalize STEP-090 validation record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 08:23:12 +09:00
tei
4e3d27e1f3 feat: add particle static CPU fast path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-23 08:22:24 +09:00
tei
98e1d2d977 docs: finalize STEP-089 validation record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-22 21:01:15 +09:00
tei
bcec6ce5e0 feat: add retained underlay revision invalidation 2026-09-22 21:01:01 +09:00
tei
07a17b4c70 docs: finalize STEP-088 git record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-22 16:19:54 +09:00
tei
a32e4e1d5b docs: record STEP-088 runtime cache baselines
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-22 16:19:21 +09:00
tei
8384c8bd24 feat: add retained underlay runtime controls 2026-09-22 16:18:58 +09:00
tei
9ad2fe5d58 docs: record STEP-088 Android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-22 10:24:14 +09:00
tei
1d1a7915e0 fix: scope retained underlay placement fallback
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-22 10:19:59 +09:00
tei
e7d109246a test: log retained underlay debug counters
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 23:18:06 +09:00
tei
c58d370e71 docs: record STEP-088 retained underlay poc
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 22:57:53 +09:00
tei
8b9ab30322 feat: add retained underlay render cache poc
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 22:53:00 +09:00
Adrian Cojocaru
c7506d6aff Release MapLibre Android 13.6.1 (#4601) 2026-09-08 18:56:06 +00:00
Taiyu Yoshizawa
57d1981497 keep digits and short codes upright in CJK line labels (#4566) 2026-09-07 23:17:43 +02:00
Akash Mohan Chaudhary
042f21da5c Rename OpenGL event to GraphicsBackend (#4515) 2026-09-06 21:02:21 +00:00
VandenOstende
a33d3f00fb fix(ios): use window scene interface orientation for heading compensation (#4548) 2026-09-06 01:04:02 +00:00
Tore Halset
eca9e1db33 mbgl-render add support for style from stdin and png to stdout (#4561) 2026-09-05 23:05:39 +00:00
Taku Fukada
843cdec996 fix(core): bundle fastpfor-lib into the Linux/macOS amalgams (#4538) 2026-09-06 00:06:43 +02:00
John Carmack
474d127bfb fix(core): break a label before a left parenthesis, not after it (#4553) 2026-09-06 00:03:38 +02:00
John Carmack
fed6dc019b ci: cancel superseded rust-ci runs on pull requests (#4555) 2026-09-06 00:02:17 +02:00
Adrian Cojocaru
0b342a1209 fix(core): Fix missing symbols/glyphs (#4554) 2026-09-04 21:42:09 +02:00
Adrian Cojocaru
96e806931d fix(core): Sync image transition based on layout (#4540) 2026-09-02 14:29:54 +03:00
Bart Louwers
e5d0fce32a core: refactor renderable size ownership (#4512) 2026-09-01 18:35:29 +03:00
Taku Fukada
be3f03b86e fix(macos): preserve exception RTTI in amalgam archive (#4350)
Co-authored-by: Bart Louwers <bart@emeel.net>
2026-08-30 10:28:43 +02:00
Bart Louwers
2919f71e39 Release MapLibre Android 13.6.0 (#4529) 2026-08-28 11:38:05 +02:00
Taku Fukada
6010a2e687 fix(core): update amalgam symbol filters for mln namespace rename (#4531) 2026-08-28 11:37:51 +02:00
Taku Fukada
c3b588321b Fix ImageSource not rendering across world copies (#4508) 2026-08-28 02:48:01 +00:00
Alex Cristici
cbe4681ef1 Reduce symbol buffers memory by using instancing (Core optimization) (#4383)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-27 00:12:07 +03:00
Haruto Sasaki
1d0908bdaa test: restore heatmap render tests disabled during renderer migration (#4466) 2026-08-25 07:15:18 +00:00
Yul Kang
96afb98b7f core: remove unused Metal RendererBackend state-cache methods (#4519) 2026-08-25 09:10:14 +02:00
Copilot
ef5ff9ac01 Update expected-lite to restore C++23 consumer compatibility for database_file_source.hpp (#4474)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: louwers <649392+louwers@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
2026-08-25 04:49:07 +00:00
dependabot[bot]
08f775e2ed build(deps): bump the github-actions group across 1 directory with 13 updates (#4493)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-25 00:21:54 +02:00
Bart Louwers
a4670161a0 Fix iOS Swift package release ordering (#4520) 2026-08-24 23:47:43 +02:00
Bart Louwers
0c7fbe2b29 Release MapLibre iOS 6.29.0 (#4509) 2026-08-23 11:00:37 +00:00
Bart Louwers
2a8ebc4906 core: rename mbgl directories to mln (#4511)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-22 20:50:14 +02:00
Bart Louwers
06fcf13d42 fix(glfw): synchronize Metal renderable size (#4510) 2026-08-21 16:32:44 +02:00
Adrian Cojocaru
f0bd259b53 Release MapLibre Android 13.5.1 (#4507) 2026-08-21 14:29:21 +03:00
Trietes
2e13de25f4 Fix surface transform (#4495) 2026-08-18 16:22:23 +03:00
Adrian Cojocaru
65a3405370 fix(core): Fix nullptr access during custom layer pre-render (#4496) 2026-08-18 13:16:09 +02:00
Bart Louwers
550f64be22 core: rename mbgl namespace to mln (#4487)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-14 23:25:20 +02:00
Bart Louwers
836b862984 android: allow configurable Maven publication repository (#4485) 2026-08-14 17:52:50 +02:00
Bart Louwers
95a977e12e Reduce Android AAR size with header-only Prefab (#4483) 2026-08-14 14:37:28 +00:00
Bouke van der Bijl
6485d28b3e macOS: report the drawable size to the renderer (#4479) 2026-08-14 13:31:09 +00:00
Bouke van der Bijl
d508e65692 macOS: render the Metal map on demand (#4480) 2026-08-14 15:24:14 +02:00
Adrian Cojocaru
743ed53794 fix (glfw): Remove duplicate glfwSwapInterval (#4484) 2026-08-14 11:57:36 +00:00
Bart Louwers
a666f02633 Release MapLibre Android 13.5.0 (#4477) 2026-08-13 00:37:23 +02:00
munawiki
9a9f831782 feat: implement split and join expressions (#4463)
Co-authored-by: Bart Louwers <bart@emeel.net>
2026-08-12 18:25:47 +02:00
Adrian Cojocaru
bd92c87d39 fix(android): Workaround for Adreno VK_ERROR_DEVICE_LOST (#4442) 2026-08-12 09:21:24 +00:00
Nathan Gass
8be081ae83 accept libpng with different release/patch number (#4467) 2026-08-11 23:37:00 +00:00
Steve Gifford
49f80e95f3 Changes to the custom Metal layer to allow more flexible rendering (#4446)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: louwers <649392+louwers@users.noreply.github.com>
Co-authored-by: Bart Louwers <bart@emeel.net>
2026-08-12 00:23:35 +02:00
Adrian Cojocaru
d9664ee187 core: Add error reporting for corrupted symbols (#4458) 2026-08-11 18:20:59 +02:00
Adrian Cojocaru
7f430aa624 core: Use compatible descriptor set layout for tile clipping (#4470) 2026-08-11 17:18:44 +02:00
Veniamin Vynohradov
b0388d186d Add multiBackend Android flavor for runtime OpenGL/Vulkan switching (#4288) 2026-08-06 15:54:58 +02:00
David Wheatley
ccb9af119e feat: specify layer/property that has a parse error when logging (#4292) 2026-08-05 08:14:36 -07:00
Sargun Vohra
e2b01754b6 build: Make the emdawnwebgpu suspension mode configurable (#4451) 2026-08-05 08:50:19 +02:00
Richard Fairhurst
40be634126 Android: setOverrideSynchronousUpdate for existing sources (#4366)
Co-authored-by: Bart Louwers <bart@emeel.net>
2026-08-04 20:00:50 +00:00
Adrian Cojocaru
f1905c5215 fix(core): Add action journal exception handling (#4438) 2026-08-04 19:34:45 +03:00
Stephan Schmid (MBAG)
e626fc0fbd Make string expressions operate on unicode (#4344) 2026-08-04 18:26:26 +02:00
Vladyslav Odobesku
9ef0f1c790 core: add CustomVectorSource for binary tile data delivery (#4377)
Co-authored-by: Vladyslav Odobesku <VladyslavOdobesku@intellias.com>
Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-04 18:23:41 +02:00
Adrian Cojocaru
25e4f6acbf fix(android): Add option to move symbol location indicator bearing down the image stack (#4408) 2026-08-04 18:21:25 +02:00
Brandon Roeder
a34723ff4a fix(core): repaint feature-state-driven symbol paint properties (#4445) 2026-08-04 18:17:53 +02:00
Bart Louwers
5e6fd6bb78 do a recursive checkout in gh-pages-docc.yml workflow (#4450) 2026-08-03 08:59:17 +00:00
Adrian Cojocaru
c8dad00be5 fix(android): Fix surface changed ANR (#4439) 2026-07-29 19:23:35 +02:00
Yul Kang
a6189feecb docs: remove duplicate packages from macOS Homebrew
install command (#4441)
2026-07-29 15:12:01 +00:00
Chris Gervang
83ce54ed3e fix(core): accept alpha in hsl colors (#4435) 2026-07-28 17:29:01 +02:00
Sargun Vohra
5bcdc704b8 core: Drain WebGPU render-thread cleanup jobs (#4367) 2026-07-24 08:11:26 +00:00
Sargun Vohra
15fcee54dc core: emdawnwebgpu build support (#4370) 2026-07-24 07:57:28 +00:00
2528 changed files with 67361 additions and 23241 deletions

View File

@@ -26,7 +26,7 @@ jobs:
outputs:
should_skip: ${{ github.event_name != 'workflow_dispatch' && steps.changed-files.outputs.android_any_modified != 'true' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -46,7 +46,7 @@ jobs:
strategy:
fail-fast: false
matrix:
renderer: [opengl, vulkan]
renderer: [opengl, vulkan, multiBackend]
defaults:
run:
working-directory: platform/android
@@ -57,14 +57,14 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN_ANDROID }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
- uses: ./.github/actions/setup-android-ci
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"
@@ -169,6 +169,7 @@ jobs:
case "${{ matrix.renderer }}" in
opengl) echo "renderer=OpenGL" >> "$GITHUB_ENV" ;;
vulkan) echo "renderer=Vulkan" >> "$GITHUB_ENV" ;;
multiBackend) echo "renderer=MultiBackend" >> "$GITHUB_ENV" ;;
*) echo "::error ::Unknown renderer '${{ matrix.renderer }}'"; exit 1 ;;
esac
@@ -210,7 +211,7 @@ jobs:
working-directory: test/android
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -255,7 +256,7 @@ jobs:
if: needs.pre_job.outputs.should_skip != 'true'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -283,7 +284,7 @@ jobs:
env:
IS_LOCAL_DEVELOPMENT: false
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -316,7 +317,7 @@ jobs:
C_COMPILER: aarch64-linux-android26-clang
BINDGEN_TARGET: aarch64-linux-android26
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -390,7 +391,7 @@ jobs:
# automatically trigger android-release when code is pushed to main
# and the platform/android/VERSION file has changed
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
- name: VERSION file changed

View File

@@ -78,11 +78,11 @@ jobs:
runs-on: ubuntu-latest
if: github.repository_owner == 'maplibre' && github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
@@ -145,7 +145,7 @@ jobs:
- name: Configure AWS Credentials
if: env.run_device_test == 'true'
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}

View File

@@ -15,7 +15,7 @@ jobs:
run:
working-directory: platform/android
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Validate and set version
working-directory: .
@@ -52,7 +52,7 @@ jobs:
- name: Create release (draft)
id: create_release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ steps.prepare_release.outputs.version_tag }}
name: ${{ steps.prepare_release.outputs.version_tag }}
@@ -74,19 +74,20 @@ jobs:
RENDERER:
- vulkan
- opengl
- multiBackend
BUILDTYPE:
- Release
- Debug
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "17"
@@ -118,7 +119,7 @@ jobs:
"build/debug-symbols-maplibre-android-${{ matrix.RENDERER }}-${{ env.buildtype }}-$version_tag.tar.gz"
- name: Upload release assets (${{ matrix.RENDERER }}, ${{ matrix.BUILDTYPE }})
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ needs.android-create-release.outputs.version_tag }}
fail_on_unmatched_files: true
@@ -133,8 +134,12 @@ jobs:
if [ "$renderer" = "opengl" ]; then
./gradlew :MapLibreAndroid:publishOpengl${{ env.buildtype }}PublicationToMavenCentralRepository
./gradlew :MapLibreAndroid:publishDefault${{ env.buildtype }}PublicationToMavenCentralRepository
else
elif [ "$renderer" = "vulkan" ]; then
./gradlew :MapLibreAndroid:publishVulkan${{ env.buildtype }}PublicationToMavenCentralRepository
elif [ "$renderer" = "multiBackend" ]; then
./gradlew :MapLibreAndroid:publishMultibackend${{ env.buildtype }}PublicationToMavenCentralRepository
else
echo "::error ::Unknown renderer '$renderer'"; exit 1
fi
env:
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
@@ -149,20 +154,20 @@ jobs:
- android-build-and-upload-release
steps:
- name: Publish release (remove draft)
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ needs.android-create-release.outputs.version_tag }}
name: ${{ needs.android-create-release.outputs.version_tag }}
draft: false
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "17"
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"

View File

@@ -10,7 +10,7 @@ jobs:
create-android-testapp-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
renderer: [opengl, vulkan]
renderer: [opengl, vulkan, multibackend]
defaults:
run:
working-directory: platform/android
@@ -43,7 +43,7 @@ jobs:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN_ANDROID }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -68,11 +68,11 @@ jobs:
needs: [android-build-testapp]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Finalize release (set draft=false)
run: |

View File

@@ -15,7 +15,7 @@ jobs:
contents: read
steps:
- name: Check out code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

View File

@@ -14,7 +14,7 @@ jobs:
create-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
@@ -46,13 +46,13 @@ jobs:
needs: [create-release]
runs-on: macos-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- run: brew install webp libuv
- run: brew install webp libuv llvm
- uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145 # v2.83.1
- uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with:
tool: armerge@2
@@ -103,14 +103,14 @@ jobs:
- renderer: vulkan
runner: { os: ubuntu-24.04-arm, arch: arm64 }
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Install dependencies
run: .github/scripts/install-linux-deps
- uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145 # v2.83.1
- uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with:
tool: armerge@2
@@ -156,7 +156,7 @@ jobs:
- run: |
git config --system core.longpaths true
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive

View File

@@ -15,11 +15,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
- uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: "temurin"
java-version: "17"
@@ -28,7 +28,7 @@ jobs:
run: ./gradlew dokkaGenerate
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
branch: gh-pages
folder: platform/android/MapLibreAndroid/build/dokka/html

View File

@@ -17,13 +17,13 @@ jobs:
shell: bash
steps:
- name: Checkout 🛎️
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Generate documentation
run: make mkdocs-build
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
branch: gh-pages
folder: platform/android/site

View File

@@ -8,7 +8,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
@@ -20,7 +20,7 @@ jobs:
run: doxygen
- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
branch: gh-pages
folder: docs/doxygen/html

View File

@@ -15,15 +15,17 @@ on:
jobs:
gh-pages-docc-build:
if: ${{ github.ref_name == 'main' }}
name: Build DocC Docs
runs-on: macos-latest
if: ${{ github.ref_name == 'main' }}
steps:
- name: Checkout 🛎️
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
@@ -52,7 +54,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download DocC docs artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -67,7 +69,7 @@ jobs:
rm docs.zip
- name: Deploy DocC documentation (main) 🚀
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
branch: gh-pages
folder: build/docs

View File

@@ -16,7 +16,7 @@ jobs:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: extractions/setup-just@53165ef7e734c5c07cb06b3c8e7b647c5aa16db3 # v4
- name: Setup mdBook
uses: peaceiris/actions-mdbook@ee69d230fe19748b7abf22df32acaa93833fad08 # v2.0.0
@@ -40,7 +40,7 @@ jobs:
name: Deploy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download book
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
@@ -48,7 +48,7 @@ jobs:
path: artifacts/book
- name: Deploy
uses: JamesIves/github-pages-deploy-action@d92aa235d04922e8f08b40ce78cc5442fcfbfa2f # v4.8.0
uses: JamesIves/github-pages-deploy-action@fa24774553152dd7873cd16ebd8d959b010c5445 # v4.9.0
with:
branch: gh-pages
folder: artifacts/book

View File

@@ -22,7 +22,7 @@ jobs:
outputs:
should_skip: ${{ github.event_name != 'workflow_dispatch' && steps.changed-files-yaml.outputs.ios_any_modified != 'true' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -58,7 +58,7 @@ jobs:
working-directory: platform/ios
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: false
persist-credentials: false
@@ -211,7 +211,7 @@ jobs:
- name: Configure AWS Credentials
if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
@@ -236,7 +236,7 @@ jobs:
runs-on: macos-14
if: needs.pre_job.outputs.should_skip != 'true'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
@@ -269,7 +269,7 @@ jobs:
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: macos-26
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -304,7 +304,7 @@ jobs:
if: needs.pre_job.outputs.should_skip != 'true'
runs-on: macos-26
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -366,7 +366,7 @@ jobs:
# Trigger release on main if VERSION file changed
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
if: github.ref == 'refs/heads/main'
with:
persist-credentials: false

View File

@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-24.04
if: github.repository_owner == 'maplibre' && github.event.workflow_run.conclusion == 'success'
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -52,7 +52,7 @@ jobs:
files: "${{ matrix.test.xcTestFile }}, ${{ matrix.test.ipaFile }}"
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}

View File

@@ -14,7 +14,7 @@ jobs:
working-directory: platform/ios
runs-on: macos-14
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

View File

@@ -23,7 +23,7 @@ jobs:
working-directory: platform/ios
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
@@ -65,7 +65,7 @@ jobs:
working-directory: platform/ios
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
@@ -104,13 +104,13 @@ jobs:
working-directory: platform/ios
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"
@@ -144,7 +144,7 @@ jobs:
cat changelog_for_version.md
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
@@ -155,7 +155,7 @@ jobs:
- name: Release (GitHub) - Create Draft
id: github_release
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
name: ios-v${{ env.version }}
files: |
@@ -177,6 +177,22 @@ jobs:
private-key: ${{ secrets.MAPLIBRE_NATIVE_BOT_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- run: npm install
working-directory: .
- name: Release (CocoaPods)
shell: bash -leo pipefail {0} # so pod is found
run: gh workflow run ios-release-cocoapods.yml --field version=${{ env.version }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish release (remove draft)
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ios-v${{ env.version }}
name: ios-v${{ env.version }}
draft: false
- name: Release (Swift Package Index)
run: |
echo "::add-mask::${{ steps.generate_token.outputs.token }}"
@@ -192,22 +208,6 @@ jobs:
"version":"${{ env.version }}",
"download_url": "https://github.com/maplibre/maplibre-native/releases/download/ios-v${{ env.version }}/MapLibre.dynamic.xcframework.zip"}}'
- run: npm install
working-directory: .
- name: Release (CocoaPods)
shell: bash -leo pipefail {0} # so pod is found
run: gh workflow run ios-release-cocoapods.yml --field version=${{ env.version }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Publish release (remove draft)
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
tag_name: ios-v${{ env.version }}
name: ios-v${{ env.version }}
draft: false
- name: Write release notifications
if: github.repository_owner == 'maplibre'
env:

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: macos-26
timeout-minutes: 90
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -49,7 +49,7 @@ jobs:
test -f bazel-bin/platform/ios/app-swift/MapLibreApp.ipa
- name: Upload app to TestFlight
uses: apple-actions/upload-testflight-build@1ad58030672057aa084b4e96beb6f7a8c627f9e6 # v5.2.1
uses: apple-actions/upload-testflight-build@5e75ff58276689011512ba87a381d93dc67dbcf8 # v5.3.0
with:
app-path: bazel-bin/platform/ios/app-swift/MapLibreApp.ipa
issuer-id: ${{ vars.APPSTORE_ISSUER_ID }}

View File

@@ -9,4 +9,4 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
- uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0

View File

@@ -33,7 +33,7 @@ jobs:
outputs:
should_skip: ${{ github.event_name != 'workflow_dispatch' && steps.changed-files.outputs.linux_any_modified != 'true' }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -66,20 +66,20 @@ jobs:
rust: true
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: cpp
- name: Install dependencies
run: .github/scripts/install-linux-deps
- uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145 # v2.83.1
- uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with:
tool: armerge@2
@@ -156,7 +156,7 @@ jobs:
# CodeQL
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
category: "/language:cpp"
@@ -199,7 +199,7 @@ jobs:
linux-coverage:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -215,7 +215,7 @@ jobs:
${{ runner.os }}-bazel-
path: ~/.cache/bazel
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
@@ -249,7 +249,7 @@ jobs:
- linux-build-and-test
- linux-coverage
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- if: github.event_name == 'pull_request'
uses: ./.github/actions/save-pr-number

View File

@@ -69,7 +69,7 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
fetch-depth: 0
@@ -106,7 +106,7 @@ jobs:
name: wgpu
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false

View File

@@ -23,7 +23,7 @@ jobs:
working-directory: platform/macos
shell: bash
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
with:
submodules: recursive
@@ -56,7 +56,7 @@ jobs:
working-directory: .
- name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: xcframework
path: |
@@ -70,7 +70,7 @@ jobs:
working-directory: platform/macos
shell: bash
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
with:
submodules: recursive
@@ -92,7 +92,7 @@ jobs:
working-directory: .
- name: Upload artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: xcframework-static
path: |
@@ -111,13 +111,13 @@ jobs:
working-directory: platform/macos
shell: bash
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
with:
submodules: recursive
- uses: actions/download-artifact@v8
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v4
with:
node-version-file: ".nvmrc"
@@ -153,7 +153,7 @@ jobs:
cat changelog_for_version.md
- name: Release (GitHub) - Create Draft
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
name: macos-v${{ env.version }}
files: |
@@ -167,7 +167,7 @@ jobs:
draft: true
- name: Publish release (remove draft)
uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: macos-v${{ env.version }}
name: macos-v${{ env.version }}

View File

@@ -100,7 +100,7 @@ jobs:
run: git config --global core.longpaths true
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -141,7 +141,7 @@ jobs:
/usr/sbin/update-ccache-symlinks
- name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'platform/node/.nvmrc'

View File

@@ -17,12 +17,12 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'platform/node/.nvmrc'
@@ -50,7 +50,7 @@ jobs:
run:
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -118,7 +118,7 @@ jobs:
run: git config --global core.longpaths true
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -159,7 +159,7 @@ jobs:
/usr/sbin/update-ccache-symlinks
- name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'platform/node/.nvmrc'
@@ -297,12 +297,12 @@ jobs:
shell: bash
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Use Node.js from nvmrc
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: 'platform/node/.nvmrc'
registry-url: 'https://registry.npmjs.org'

View File

@@ -19,14 +19,14 @@ jobs:
outputs:
should_skip: ${{ steps.parent_workflow.outputs.was_skipped_or_cancelled }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github
.nvmrc
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
@@ -45,7 +45,7 @@ jobs:
if: needs.pre_job.outputs.should_skip == 'false'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -85,7 +85,7 @@ jobs:
- name: Configure AWS Credentials
if: vars.OIDC_AWS_ROLE_TO_ASSUME
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}

View File

@@ -22,14 +22,14 @@ jobs:
outputs:
should_skip: ${{ steps.parent_workflow.outputs.was_skipped_or_cancelled }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github
.nvmrc
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
@@ -48,7 +48,7 @@ jobs:
if: needs.pre_job.outputs.should_skip == 'false'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
@@ -95,7 +95,7 @@ jobs:
- name: Configure AWS Credentials
if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}
@@ -139,7 +139,7 @@ jobs:
if: needs.pre_job.outputs.should_skip == 'false'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
persist-credentials: false
@@ -170,7 +170,7 @@ jobs:
- name: Configure AWS Credentials
if: github.ref == 'refs/heads/main' && vars.OIDC_AWS_ROLE_TO_ASSUME
uses: aws-actions/configure-aws-credentials@517a711dbcd0e402f90c77e7e2f81e849156e31d # v6.2.2
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6.2.3
with:
aws-region: us-west-2
role-to-assume: ${{ vars.OIDC_AWS_ROLE_TO_ASSUME }}

View File

@@ -104,7 +104,7 @@ jobs:
run: git config --global core.longpaths true
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: source
submodules: recursive

View File

@@ -7,6 +7,10 @@ on:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
defaults:
run:
working-directory: rustutils/
@@ -23,9 +27,9 @@ jobs:
- os: windows-latest
- os: ubuntu-latest
steps:
- uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145 # v2.83.1
- uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with: { tool: just }
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: just -v ci-test
@@ -34,9 +38,9 @@ jobs:
name: Rust tests MSRV (Minimal Supported Rust Version)
runs-on: ubuntu-latest
steps:
- uses: taiki-e/install-action@2ca9b94c269419b7b0c711c09d0b21c4e1d51145 # v2.83.1
- uses: taiki-e/install-action@ba47c86ac325773530516bb756137ac718732518 # v2.86.5
with: { tool: just }
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Read crate metadata

View File

@@ -12,14 +12,14 @@ jobs:
outputs:
should_run: ${{ !steps.parent_workflow.outputs.was_skipped_or_cancelled }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
.github
.nvmrc
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
@@ -38,7 +38,7 @@ jobs:
if: needs.pre_job.outputs.should_run == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

View File

@@ -9,9 +9,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"

View File

@@ -9,11 +9,11 @@ jobs:
validate-scripts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: ".nvmrc"

View File

@@ -37,7 +37,7 @@ jobs:
- run: |
git config --system core.longpaths true
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -62,7 +62,7 @@ jobs:
- run: |
git config --system core.longpaths true
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
platform/windows/vendor
@@ -118,19 +118,19 @@ jobs:
- run: |
git config --system core.longpaths true
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: cpp
- uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
- uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: Initialize sccache
run: |
@@ -167,7 +167,7 @@ jobs:
# CodeQL
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
category: "/language:cpp"
@@ -255,7 +255,7 @@ jobs:
git config --system core.longpaths true
git config --system core.autocrlf input
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: recursive
persist-credentials: false
@@ -279,11 +279,11 @@ jobs:
libuv:p
- name: Initialize CodeQL
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/init@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
languages: cpp
- uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
- uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
- name: Initialize sccache
run: |
@@ -308,7 +308,7 @@ jobs:
# CodeQL
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
uses: github/codeql-action/analyze@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
category: "/language:cpp"

View File

@@ -89,3 +89,66 @@ Codex 的核心角色是“实现者”,不作为其自身修改结果的唯
每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`(仅在任务要求 Codex 完成报告时)。
修改结束必须按当前构建规范验证、更新 Step、追加 `粒子开发记录.md`,并明确所有未完成/阻塞。Git 提交必须 scoped,禁止把既有无关 dirty/untracked 一并提交。
## 8. Codex 写代码,RDC 执行编译 / 测试
本项目固定采用以下闭环,除非用户在当前任务中明确另行授权:
1. **Codex 负责实现与修复。**
- 读取当前任务、设计、源码与错误信息;
- 修改代码、测试代码、构建脚本和任务要求的文档;
- 分析 RDC 返回的编译、链接、测试、codegen、打包失败;
- 根据真实错误继续修复代码。
2. **RDC / 普通 Terminal 负责实际编译与验证。**
- CMake / Ninja / Bazel 编译;
- C++ / OpenGL / Metal 定向测试;
- codegen、链接、打包、artifact 检查;
- 其他耗时构建或机械性验证。
Codex 不应自行启动、等待或轮询这些长时间命令。
3. **固定闭环:**
`Codex 写代码 → Codex 停止并给出精确验证命令 → RDC 执行 → RDC 返回结果 → 若失败,Codex 分析并修复 → RDC 再验证`。
4. **失败日志只回传必要信息。**
- RDC 保留完整 stdout/stderr;
- 成功时只返回 exit code、耗时和必要摘要;
- 失败时优先返回首个真实 error、相关 target、前后必要上下文和日志路径;
- 不把完整大型 build log 反复送入 Codex。
5. **Codex 禁止行为。**
- 未经用户明确授权,不得自行执行长时间编译、链接、codegen、打包或大型测试;
- 不得创建 background terminal 等待 build;
- 不得高频轮询构建状态;
- 不得为了等待编译而自行切换模型或启动第二个 Codex 实例;
- 不得把“编译未完成”当作理由持续占用高能力模型。
6. **RDC 发现问题后的处理。**
- RDC 可以直接处理范围明确、风险低、原因清楚的小问题,包括:
- 构建命令、CMake/Bazel 配置、链接配置、平台条件、测试条件等小型工程问题;
- 单文件或少量文件内、无需重新设计架构的小型源码修正;
- 明确的编译错误、声明/定义不一致、include/宏条件、简单 ABI/平台兼容问题;
- 不改变既定业务语义和总体架构的局部修复。
- RDC 修完后应立即重新编译/测试验证,不必为了这类小问题启动 Codex。
- 以下情况才交给 Codex:
- 大规模逻辑修改;
- 跨模块或跨层重构;
- renderer / runtime / API 设计需要重新判断;
- 需要新增较完整功能或大量代码;
- 用户明确指定“这是 Codex 代码任务”。
- 一旦 RDC 判断修复范围开始扩大、需要重新设计或无法通过少量明确修改解决,应停止继续扩改,把错误证据和现状交给 Codex。
7. **用户可见性。**
- 本项目未经用户明确授权,不得使用后台/headless Codex 执行任务;
- Codex 默认运行在用户可见的 Terminal/TUI 中;
- 当前 Mac 上如需启动或恢复 Codex,必须优先使用现有 GNU Screen 会话 `7445.codex`;
- 不得另起隐藏的 Codex 进程或新的不可见会话,除非用户明确授权;
- 用户应能够通过该 screen 会话直接看到 Codex 的输入、输出和当前状态。
8. **任务书冲突处理。**
- 若旧任务书写有“Codex 必须执行编译/测试”,但用户未在当前任务明确重新授权,则以本节为准:
Codex 只准备代码与验证命令,RDC 负责实际执行。
- 只有用户在当前任务中明确要求“由 Codex 执行某项编译/测试”时,Codex 才可执行该项。
核心原则:
> Codex 负责写和改;RDC 负责跑和验;RDC 报错,再交回 Codex 修。

View File

@@ -10,7 +10,7 @@ MapLibre Native uses a monolithic repository that houses both core C++ code and
## Core cross-platform C++ code
In the repository, core C++ code is contained in the `include` and `src` directories. The former includes headers that are considered to make up the "public" core C++ API, while the latter includes `.cpp` implementation files and headers that are private to the implementation. Within both directories, files are nested under an `mbgl` directory, which has various subdirectories based on areas of functionality. Both public and private headers therefore can (and should) always be included with the form `#include <mbgl/___/___.hpp>`.
In the repository, core C++ code is contained in the `include` and `src` directories. The former includes headers that are considered to make up the "public" core C++ API, while the latter includes `.cpp` implementation files and headers that are private to the implementation. Within both directories, files are nested under an `mbgl` directory, which has various subdirectories based on areas of functionality. Both public and private headers therefore can (and should) always be included with the form `#include <mln/___/___.hpp>`.
## Platform specific code
@@ -24,7 +24,7 @@ Code and build scripts belonging to platform SDKs are contained in the `platform
## Other directories
- `benchmark` contains the performance tests built using https://github.com/google/benchmark/. The code under this directory builds the `mbgl-benchmark-test` executable to execute the benchmark tests.
- The entry point of this executable is `platform/default/src/mbgl/benchmark/main.cpp -> benchmark/src/mbgl/benchmark/benchmark.cpp`.
- The entry point of this executable is `platform/default/src/mln/benchmark/main.cpp -> benchmark/src/mln/benchmark/benchmark.cpp`.
- iOS SDK runs benchmark test through a separate app named `BenchmarkApp`.
- Android SDK does not have benchmark test.
- `bin` contains the code for tools like `mbgl-cache`, `mbgl-offline`, and `mbgl-render`.
@@ -142,4 +142,4 @@ To minimize data races and invalid memory access, we aim for zero shared memory
Invoking a task on another thread itself creates an ownership obligation: the responsibility for the work happening on the other thread. This ownership is represented by the invocation method returning `std::unique_ptr<AsyncTask>`. Destroying this object indicates that the result of the task is no longer required: the callback is guaranteed not to be called, and the work on the other thread may be aborted (if doing so is convenient).
All this is implemented by [Thread](https://github.com/mapbox/mapbox-gl-native/blob/master/src/mbgl/util/thread.hpp) and [RunLoop](https://github.com/mapbox/mapbox-gl-native/blob/master/include/mbgl/util/run_loop.hpp).
All this is implemented by [Thread](https://github.com/mapbox/mapbox-gl-native/blob/master/src/mln/util/thread.hpp) and [RunLoop](https://github.com/mapbox/mapbox-gl-native/blob/master/include/mln/util/run_loop.hpp).

View File

@@ -29,6 +29,10 @@
### 🐞 Bug fixes
- *...Add new stuff here...*
- [core] Draw numbers and short uppercase codes upright in vertical CJK line labels ([#4565](https://github.com/maplibre/maplibre-native/issues/4565)), compat to [maplibre-gl-js#8205](https://github.com/maplibre/maplibre-gl-js/pull/8205).
- [core] Fix `ImageSource` not rendering across world copies ([#4508](https://github.com/maplibre/maplibre-native/issues/4508)).
- [core] Repaint data-driven symbol paint properties after feature-state updates.
- [core] Accept alpha values in legacy comma-separated `hsl()` colors ([#4434](https://github.com/maplibre/maplibre-native/issues/4434)).
- [core] Fix thread-unsafe headless OpenGL display singleton initialization [#4332](https://github.com/maplibre/maplibre-native/pull/4332)
- [macos] Fix `mlt-cpp` and `mbgl-vendor-icu` not being included in the amalgamation
- [core] Fix memory access violation exception in vector_tile_data.cpp [#632](https://github.com/maplibre/maplibre-native/pull/632)
@@ -85,7 +89,7 @@
- [ios, macos] Allow specifying multiple fonts or font families for local font rendering ([#16253](https://github.com/mapbox/mapbox-gl-native/pull/16253))
By default, CJK characters are now set in the font specified by the `text-font` layout property. If the named font is not installed on the device or bundled with the application, the characters are set in one of the fallback fonts passed into the `localFontFamily` parameter of `mbgl::Renderer::Renderer()` and `mbgl::MapSnapshotter::MapSnapshotter()`. This parameter can now contain a list of font family names, font display names, and font PostScript names, each name separated by a newline.
By default, CJK characters are now set in the font specified by the `text-font` layout property. If the named font is not installed on the device or bundled with the application, the characters are set in one of the fallback fonts passed into the `localFontFamily` parameter of `mln::Renderer::Renderer()` and `mln::MapSnapshotter::MapSnapshotter()`. This parameter can now contain a list of font family names, font display names, and font PostScript names, each name separated by a newline.
### 🐞 Bug fixes
@@ -139,7 +143,7 @@
- [core][tile mode] Fix variable placement for labels with the `icon-text-fit` property set ([#16382](https://github.com/mapbox/mapbox-gl-native/pull/16382))
The `symbolIntersectsTileEdges()` util in `mbgl::TilePlacement` now considers icon shift for the variable symbols with enabled icon-text-fit setting, thus providing more accurate results.
The `symbolIntersectsTileEdges()` util in `mln::TilePlacement` now considers icon shift for the variable symbols with enabled icon-text-fit setting, thus providing more accurate results.
- [core] Correctly log a warning instead of crashing when a non-existent image is attempted to be removed. ([#16391](https://github.com/mapbox/mapbox-gl-native/pull/16391))
@@ -170,7 +174,7 @@
- [android] Add jni binding for styleable snapshotter ([#16286](https://github.com/mapbox/mapbox-gl-native/pull/16286))
- [core] Ability to set generic layer properties using setProperty method ([#16324](https://github.com/mapbox/mapbox-gl-native/pull/16324))
This change enables the following new keys for the `mbgl::Layer::setProperty()` API:
This change enables the following new keys for the `mln::Layer::setProperty()` API:
- "filter" invokes `setFilter()`
- "minzoom" invokes `setMinZoom()`
- "maxzoom" invokes `setMaxZoom()`
@@ -252,7 +256,7 @@
- Fixed using of the `in` expression as a layer filter ([#16272](https://github.com/mapbox/mapbox-gl-native/pull/16272))
The bug was caused by `mbgl::style::conversion::isExpression()` always returning `false` for the `in` expression.
The bug was caused by `mln::style::conversion::isExpression()` always returning `false` for the `in` expression.
### 🧩 Architectural changes
@@ -296,9 +300,9 @@
##### ⚠️ Breaking changes
- Changes to `mbgl::FileSourceManager::getFileSource()` ([#16238](https://github.com/mapbox/mapbox-gl-native/pull/16238))
- Changes to `mln::FileSourceManager::getFileSource()` ([#16238](https://github.com/mapbox/mapbox-gl-native/pull/16238))
It returns now `mbgl::PassRefPtr<FileSource>` (previously was `std::shared_ptr<FileSource>`) in order to enforce keeping the strong reference to the returned object.
It returns now `mln::PassRefPtr<FileSource>` (previously was `std::shared_ptr<FileSource>`) in order to enforce keeping the strong reference to the returned object.
Breaking code example:
`auto fs = FileSourceManager::getFileSource(); fs->..`
@@ -306,9 +310,9 @@
Possible fix:
`std::shared_ptr<FileSource> fs = `;
- The `mbgl::OnlineFileSource` class cannot be used directly ([#16238](https://github.com/mapbox/mapbox-gl-native/pull/16238))
- The `mln::OnlineFileSource` class cannot be used directly ([#16238](https://github.com/mapbox/mapbox-gl-native/pull/16238))
Clients must use the parent `mbgl::FileSource` interface instead.
Clients must use the parent `mln::FileSource` interface instead.
Breaking code example:
`std::shared_ptr<OnlineFileSource> onlineSource = std::static_pointer_cast<OnlineFileSource>(FileSourceManager::get()->getFileSource(..));`

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,7 @@ add_library(
${PROJECT_SOURCE_DIR}/benchmark/parse/tile_mask.benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/parse/vector_tile.benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/renderer/group_layers.benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/src/mbgl/benchmark/benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/src/mln/benchmark/benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/storage/offline_database.benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/util/tilecover.benchmark.cpp
${PROJECT_SOURCE_DIR}/benchmark/util/color.benchmark.cpp

View File

@@ -1,17 +1,17 @@
#include <benchmark/benchmark.h>
#include <mbgl/map/map.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/storage/network_status.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mln/map/map.hpp>
#include <mln/map/map_options.hpp>
#include <mln/gfx/headless_frontend.hpp>
#include <mln/renderer/renderer.hpp>
#include <mln/style/style.hpp>
#include <mln/style/image.hpp>
#include <mln/storage/network_status.hpp>
#include <mln/util/image.hpp>
#include <mln/util/io.hpp>
#include <mln/util/run_loop.hpp>
using namespace mbgl;
using namespace mln;
namespace {

View File

@@ -1,24 +1,24 @@
#include <benchmark/benchmark.h>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/map/map.hpp>
#include <mbgl/map/map_observer.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/renderer/renderer.hpp>
#include <mbgl/storage/network_status.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/style/image.hpp>
#include <mbgl/style/layers/symbol_layer.hpp>
#include <mbgl/style/sources/geojson_source.hpp>
#include <mbgl/style/style.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mln/gfx/headless_frontend.hpp>
#include <mln/map/map.hpp>
#include <mln/map/map_observer.hpp>
#include <mln/map/map_options.hpp>
#include <mln/renderer/renderer.hpp>
#include <mln/storage/network_status.hpp>
#include <mln/storage/resource_options.hpp>
#include <mln/style/image.hpp>
#include <mln/style/layers/symbol_layer.hpp>
#include <mln/style/sources/geojson_source.hpp>
#include <mln/style/style.hpp>
#include <mln/util/image.hpp>
#include <mln/util/io.hpp>
#include <mln/util/run_loop.hpp>
#include <sstream>
#include <optional>
using namespace mbgl;
using namespace mln;
namespace {
@@ -124,7 +124,7 @@ static void API_renderStill_recreate_map_2(::benchmark::State& state) {
}
static void API_renderStill_multiple_sources(::benchmark::State& state) {
using namespace mbgl::style;
using namespace mln::style;
RenderBenchmark bench;
HeadlessFrontend frontend{size, pixelRatio};
Map map{frontend,

View File

@@ -1,12 +1,12 @@
#include <benchmark/benchmark.h>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/function.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mln/style/conversion/json.hpp>
#include <mln/style/conversion/function.hpp>
#include <mln/style/conversion/property_value.hpp>
#include <mln/style/conversion_impl.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace mln;
using namespace mln::style;
static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";

View File

@@ -1,14 +1,14 @@
#include <benchmark/benchmark.h>
#include <mbgl/benchmark/stub_geometry_tile_feature.hpp>
#include <mln/benchmark/stub_geometry_tile_feature.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/function.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mln/style/conversion/json.hpp>
#include <mln/style/conversion/function.hpp>
#include <mln/style/conversion/property_value.hpp>
#include <mln/style/conversion_impl.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace mln;
using namespace mln::style;
static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";

View File

@@ -1,13 +1,13 @@
#include <benchmark/benchmark.h>
#include <mbgl/benchmark/stub_geometry_tile_feature.hpp>
#include <mln/benchmark/stub_geometry_tile_feature.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/property_value.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mln/style/conversion/json.hpp>
#include <mln/style/conversion/property_value.hpp>
#include <mln/style/conversion_impl.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace mln;
using namespace mln::style;
static std::string createFunctionJSON(size_t stopCount) {
std::string stops = "[";

View File

@@ -1,7 +1,7 @@
#pragma once
namespace mbgl {
namespace mln {
int runBenchmark(int argc, char* argv[]);
} // namespace mbgl
} // namespace mln

View File

@@ -1,8 +1,8 @@
#include <ios_test_runner.hpp>
#include <mbgl/benchmark.hpp>
#include <mln/benchmark.hpp>
#include <mbgl/util/logging.hpp>
#include <mln/util/logging.hpp>
#include <unistd.h>
#include <vector>
@@ -22,13 +22,13 @@ bool TestRunner::startTest(const std::string& basePath) {
argv.push_back(nullptr);
if (chdir(basePath.c_str())) {
mbgl::Log::Error(mbgl::Event::General, "Failed to change the directory to " + basePath);
mln::Log::Error(mln::Event::General, "Failed to change the directory to " + basePath);
return false;
}
mbgl::Log::Info(mbgl::Event::General, "Start BenchmarkRunner");
int status = mbgl::runBenchmark(static_cast<uint32_t>(argv.size()), argv.data());
mbgl::Log::Info(mbgl::Event::General, "BenchmarkRunner finished with status: '%d'", status);
mln::Log::Info(mln::Event::General, "Start BenchmarkRunner");
int status = mln::runBenchmark(static_cast<uint32_t>(argv.size()), argv.data());
mln::Log::Info(mln::Event::General, "BenchmarkRunner finished with status: '%d'", status);
return status == 0;
}

View File

@@ -1,13 +1,13 @@
#include <benchmark/benchmark.h>
#include <mbgl/style/filter.hpp>
#include <mbgl/style/conversion/json.hpp>
#include <mbgl/style/conversion/filter.hpp>
#include <mbgl/style/conversion_impl.hpp>
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/benchmark/stub_geometry_tile_feature.hpp>
#include <mln/style/filter.hpp>
#include <mln/style/conversion/json.hpp>
#include <mln/style/conversion/filter.hpp>
#include <mln/style/conversion_impl.hpp>
#include <mln/tile/geometry_tile_data.hpp>
#include <mln/benchmark/stub_geometry_tile_feature.hpp>
using namespace mbgl;
using namespace mln;
style::Filter parse(const char* expression) {
style::conversion::Error error;

View File

@@ -1,8 +1,8 @@
#include <benchmark/benchmark.h>
#include <mbgl/algorithm/update_tile_masks.hpp>
#include <mln/algorithm/update_tile_masks.hpp>
using namespace mbgl;
using namespace mln;
class FakeTile {
public:

View File

@@ -1,9 +1,9 @@
#include <benchmark/benchmark.h>
#include <mbgl/tile/vector_mvt_tile_data.hpp>
#include <mbgl/util/io.hpp>
#include <mln/tile/vector_mvt_tile_data.hpp>
#include <mln/util/io.hpp>
using namespace mbgl;
using namespace mln;
static void Parse_VectorTile(benchmark::State& state) {
auto data = std::make_shared<std::string>(

View File

@@ -1,31 +1,31 @@
#include <benchmark/benchmark.h>
// Core function to benchmark
#include <mbgl/renderer/group_by_layout.hpp>
#include <mln/renderer/group_by_layout.hpp>
// For mock layer properties
#include <mbgl/style/layer_properties.hpp>
#include <mbgl/style/layers/fill_layer_impl.hpp>
#include <mbgl/style/layers/fill_layer_properties.hpp>
#include <mbgl/style/layers/line_layer_impl.hpp>
#include <mbgl/style/layers/line_layer_properties.hpp>
#include <mbgl/style/layers/symbol_layer_impl.hpp>
#include <mbgl/style/layers/symbol_layer_properties.hpp>
#include <mbgl/style/layers/circle_layer_impl.hpp>
#include <mbgl/style/layers/circle_layer_properties.hpp>
#include <mbgl/util/immutable.hpp>
#include <mln/style/layer_properties.hpp>
#include <mln/style/layers/fill_layer_impl.hpp>
#include <mln/style/layers/fill_layer_properties.hpp>
#include <mln/style/layers/line_layer_impl.hpp>
#include <mln/style/layers/line_layer_properties.hpp>
#include <mln/style/layers/symbol_layer_impl.hpp>
#include <mln/style/layers/symbol_layer_properties.hpp>
#include <mln/style/layers/circle_layer_impl.hpp>
#include <mln/style/layers/circle_layer_properties.hpp>
#include <mln/util/immutable.hpp>
// For real style loading
#include <mbgl/style/style_impl.hpp>
#include <mbgl/storage/file_source.hpp>
#include <mbgl/util/io.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mbgl/layermanager/layer_manager.hpp>
#include <mbgl/renderer/render_layer.hpp>
#include <mbgl/util/thread_pool.hpp>
#include <mln/style/style_impl.hpp>
#include <mln/storage/file_source.hpp>
#include <mln/util/io.hpp>
#include <mln/util/run_loop.hpp>
#include <mln/layermanager/layer_manager.hpp>
#include <mln/renderer/render_layer.hpp>
#include <mln/util/thread_pool.hpp>
using namespace mbgl;
using namespace mbgl::style;
using namespace mln;
using namespace mln::style;
// Minimal FileSource stub for benchmark (doesn't actually fetch resources)
class StubFileSource : public FileSource {

View File

@@ -1,8 +1,8 @@
#include <mbgl/benchmark.hpp>
#include <mln/benchmark.hpp>
#include <benchmark/benchmark.h>
namespace mbgl {
namespace mln {
int runBenchmark(int argc, char* argv[]) {
::benchmark::Initialize(&argc, argv);
@@ -10,4 +10,4 @@ int runBenchmark(int argc, char* argv[]) {
return 0;
}
} // namespace mbgl
} // namespace mln

View File

@@ -1,9 +1,9 @@
#pragma once
#include <mbgl/tile/geometry_tile_data.hpp>
#include <mbgl/util/feature.hpp>
#include <mln/tile/geometry_tile_data.hpp>
#include <mln/util/feature.hpp>
using namespace mbgl;
using namespace mln;
class StubGeometryTileFeature : public GeometryTileFeature {
public:

View File

@@ -1,11 +1,11 @@
#include <benchmark/benchmark.h>
#include <mbgl/storage/offline_database.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/response.hpp>
#include <mbgl/storage/sqlite3.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/logging.hpp>
#include <mln/storage/offline_database.hpp>
#include <mln/storage/resource.hpp>
#include <mln/storage/response.hpp>
#include <mln/storage/sqlite3.hpp>
#include <mln/util/string.hpp>
#include <mln/util/logging.hpp>
#include <random>
@@ -18,14 +18,14 @@ public:
response.data = std::make_shared<std::string>(50 * 1024, 0);
response.mustRevalidate = false;
response.expires = mbgl::util::now() + 1h;
response.expires = mln::util::now() + 1h;
resetAmbientTiles();
resetRegion();
}
void resetAmbientTiles() {
using namespace mbgl;
using namespace mln;
db.clearAmbientCache();
@@ -37,7 +37,7 @@ public:
}
void resetRegion() {
using namespace mbgl;
using namespace mln;
auto regions = db.listRegions().value();
if (!regions.empty()) {
@@ -58,8 +58,8 @@ public:
}
}
mbgl::Response response;
mbgl::OfflineDatabase db{":memory:", mbgl::TileServerOptions::DefaultConfiguration()};
mln::Response response;
mln::OfflineDatabase db{":memory:", mln::TileServerOptions::DefaultConfiguration()};
const unsigned tileCount = 100;
int64_t regionID = 0;
@@ -78,7 +78,7 @@ BENCHMARK_F(OfflineDatabase, DeleteRegion)(benchmark::State& state) {
}
BENCHMARK_F(OfflineDatabase, InsertTileRegion)(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
while (state.KeepRunning()) {
const Resource offline = Resource::tile(
@@ -99,7 +99,7 @@ BENCHMARK_F(OfflineDatabase, ClearAmbientCache)(benchmark::State& state) {
}
BENCHMARK_F(OfflineDatabase, InsertTileCache)(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
while (state.KeepRunning()) {
const Resource ambient = Resource::tile(
@@ -109,7 +109,7 @@ BENCHMARK_F(OfflineDatabase, InsertTileCache)(benchmark::State& state) {
}
BENCHMARK_F(OfflineDatabase, InsertBigTileCache)(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
Response big;
big.data = std::make_shared<std::string>(util::DEFAULT_MAX_CACHE_SIZE / 100, 0);
@@ -122,7 +122,7 @@ BENCHMARK_F(OfflineDatabase, InsertBigTileCache)(benchmark::State& state) {
}
BENCHMARK_F(OfflineDatabase, GetTile)(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
std::random_device rd;
std::mt19937 gen(rd());
@@ -136,7 +136,7 @@ BENCHMARK_F(OfflineDatabase, GetTile)(benchmark::State& state) {
}
BENCHMARK_F(OfflineDatabase, AddTilesToFullDatabase)(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
Log::setObserver(std::make_unique<Log::NullObserver>());
db.setMaximumAmbientCacheSize(50 * 1024 * 5);
@@ -152,7 +152,7 @@ BENCHMARK_F(OfflineDatabase, AddTilesToFullDatabase)(benchmark::State& state) {
BENCHMARK_F(OfflineDatabase, AddTilesToDisabledDatabase)
(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
auto regions = db.listRegions().value();
if (!regions.empty()) {
@@ -171,7 +171,7 @@ BENCHMARK_F(OfflineDatabase, AddTilesToDisabledDatabase)
BENCHMARK_F(OfflineDatabase, GetTileFromDisabledDatabase)
(benchmark::State& state) {
using namespace mbgl;
using namespace mln;
auto regions = db.listRegions().value();
if (!regions.empty()) {

View File

@@ -2,7 +2,7 @@
#include <string>
#include <vector>
#include <mbgl/util/color.hpp>
#include <mln/util/color.hpp>
static const std::vector<std::string> testStrings = {"#000000",
"#FFFFFF",
@@ -20,7 +20,7 @@ namespace {
void ColorParse(benchmark::State& state) {
for (auto _ : state) {
for (const auto& str : testStrings) {
auto result = mbgl::Color::parse(str);
auto result = mln::Color::parse(str);
benchmark::DoNotOptimize(result);
}
}

View File

@@ -1,11 +1,11 @@
#include <benchmark/benchmark.h>
#include <mbgl/util/geo.hpp>
#include <mbgl/util/geometry.hpp>
#include <mbgl/util/tile_coordinate.hpp>
#include <mbgl/util/tile_cover.hpp>
#include <mbgl/map/transform.hpp>
#include <mln/util/geo.hpp>
#include <mln/util/geometry.hpp>
#include <mln/util/tile_coordinate.hpp>
#include <mln/util/tile_cover.hpp>
#include <mln/map/transform.hpp>
using namespace mbgl;
using namespace mln;
static const LatLngBounds sanFrancisco = LatLngBounds::hull({37.6609, -122.5744}, {37.8271, -122.3204});

View File

@@ -1,8 +1,8 @@
#include <mbgl/storage/file_source_manager.hpp>
#include <mbgl/storage/resource.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/util/chrono.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mln/storage/file_source_manager.hpp>
#include <mln/storage/resource.hpp>
#include <mln/storage/resource_options.hpp>
#include <mln/util/chrono.hpp>
#include <mln/util/run_loop.hpp>
#include <args.hxx>
#include <mapbox/io/io.hpp>
@@ -25,20 +25,20 @@ int main(int argc, char* argv[]) {
args::ValueFlag<unsigned long> expiresValue(p, "expires", "Expires date, will use 'now' otherwise", {'e'});
args::ValueFlag<unsigned long> modifiedValue(p, "modified", "Modified date, will use 'now' otherwise", {'m'});
std::unordered_map<std::string, mbgl::Resource::Kind> typeMap{{"glyphs", mbgl::Resource::Glyphs},
{"image", mbgl::Resource::Image},
{"source", mbgl::Resource::Source},
{"sprite-image", mbgl::Resource::SpriteImage},
{"sprite-json", mbgl::Resource::SpriteJSON},
{"style", mbgl::Resource::Style},
{"tile", mbgl::Resource::Tile}};
std::unordered_map<std::string, mln::Resource::Kind> typeMap{{"glyphs", mln::Resource::Glyphs},
{"image", mln::Resource::Image},
{"source", mln::Resource::Source},
{"sprite-image", mln::Resource::SpriteImage},
{"sprite-json", mln::Resource::SpriteJSON},
{"style", mln::Resource::Style},
{"tile", mln::Resource::Tile}};
std::string typeHelp("One of the following (required):");
for (const auto& key : typeMap) {
typeHelp += " " + key.first;
}
args::MapFlag<std::string, mbgl::Resource::Kind> typeFlag(
args::MapFlag<std::string, mln::Resource::Kind> typeFlag(
p, "type", typeHelp, {"type"}, typeMap, args::Options::Required);
args::Group tileIdGroup(p, "Coordinates (required for 'tile')", args::Group::Validators::AllOrNone);
@@ -67,15 +67,15 @@ int main(int argc, char* argv[]) {
exit(3);
}
mbgl::Response response;
mln::Response response;
response.data = std::make_shared<std::string>(*file);
response.etag = etagValue ? args::get(etagValue) : std::string();
response.expires = expiresValue ? mbgl::Timestamp(mbgl::Seconds(args::get(expiresValue))) : mbgl::util::now();
response.modified = modifiedValue ? mbgl::Timestamp(mbgl::Seconds(args::get(modifiedValue))) : mbgl::util::now();
response.expires = expiresValue ? mln::Timestamp(mln::Seconds(args::get(expiresValue))) : mln::util::now();
response.modified = modifiedValue ? mln::Timestamp(mln::Seconds(args::get(modifiedValue))) : mln::util::now();
mbgl::Resource resource(args::get(typeFlag), args::get(urlValue));
mln::Resource resource(args::get(typeFlag), args::get(urlValue));
if (args::get(typeFlag) == mbgl::Resource::Tile) {
if (args::get(typeFlag) == mln::Resource::Tile) {
if (!xValueFlag || !yValueFlag || !zValueFlag) {
std::cerr << "Error: -x, -y and -z must be provided for tiles" << '\n';
exit(1);
@@ -88,9 +88,9 @@ int main(int argc, char* argv[]) {
.z = static_cast<int8_t>(args::get(zValueFlag))}};
}
mbgl::util::RunLoop loop;
std::shared_ptr<mbgl::FileSource> dbfs = mbgl::FileSourceManager::get()->getFileSource(
mbgl::FileSourceType::Database, mbgl::ResourceOptions().withCachePath(args::get(cacheValue)));
mln::util::RunLoop loop;
std::shared_ptr<mln::FileSource> dbfs = mln::FileSourceManager::get()->getFileSource(
mln::FileSourceType::Database, mln::ResourceOptions().withCachePath(args::get(cacheValue)));
dbfs->forward(resource, response, [&loop] { loop.stop(); });
loop.run();
return 0;

View File

@@ -1,12 +1,12 @@
#include <mbgl/util/run_loop.hpp>
#include <mbgl/util/string.hpp>
#include <mbgl/util/geojson.hpp>
#include <mln/util/run_loop.hpp>
#include <mln/util/string.hpp>
#include <mln/util/geojson.hpp>
#include <mbgl/storage/database_file_source.hpp>
#include <mbgl/storage/file_source_manager.hpp>
#include <mbgl/storage/resource_options.hpp>
#include <mbgl/util/client_options.hpp>
#include <mbgl/util/tile_server_options.hpp>
#include <mln/storage/database_file_source.hpp>
#include <mln/storage/file_source_manager.hpp>
#include <mln/storage/resource_options.hpp>
#include <mln/util/client_options.hpp>
#include <mln/util/tile_server_options.hpp>
#include <args.hxx>
@@ -51,19 +51,19 @@ mapbox::geometry::geometry<double> parseGeometry(const std::string& json) {
});
}
std::ostream& operator<<(std::ostream& os, mbgl::Response::Error::Reason r) {
std::ostream& operator<<(std::ostream& os, mln::Response::Error::Reason r) {
switch (r) {
case mbgl::Response::Error::Reason::Success:
case mln::Response::Error::Reason::Success:
return os << "Response::Error::Reason::Success";
case mbgl::Response::Error::Reason::NotFound:
case mln::Response::Error::Reason::NotFound:
return os << "Response::Error::Reason::NotFound";
case mbgl::Response::Error::Reason::Server:
case mln::Response::Error::Reason::Server:
return os << "Response::Error::Reason::Server";
case mbgl::Response::Error::Reason::Connection:
case mln::Response::Error::Reason::Connection:
return os << "Response::Error::Reason::Connection";
case mbgl::Response::Error::Reason::RateLimit:
case mln::Response::Error::Reason::RateLimit:
return os << "Response::Error::Reason::RateLimit";
case mbgl::Response::Error::Reason::Other:
case mln::Response::Error::Reason::Other:
return os << "Response::Error::Reason::Other";
}
@@ -122,7 +122,7 @@ int main(int argc, char* argv[]) {
exit(2);
}
auto mapTilerConfiguration = mbgl::TileServerOptions::MapTilerConfiguration();
auto mapTilerConfiguration = mln::TileServerOptions::MapTilerConfiguration();
std::string style = styleValue ? args::get(styleValue) : mapTilerConfiguration.defaultStyles().at(0).getUrl();
std::cout << " Style: " << style << std::endl;
@@ -138,7 +138,7 @@ int main(int argc, char* argv[]) {
const bool includeIdeographs = includeIdeographsValue ? args::get(includeIdeographsValue) : false;
const std::string output = outputValue ? args::get(outputValue) : "offline.db";
using namespace mbgl;
using namespace mln;
OfflineRegionDefinition definition = [&]() {
if (geometryValue) {
@@ -185,7 +185,7 @@ int main(int argc, char* argv[]) {
int retCode = 0;
std::cout << "Start Merge" << std::endl;
inputSource.mergeOfflineRegions(
*mergePath, [&](mbgl::expected<std::vector<OfflineRegion>, std::exception_ptr> result) {
*mergePath, [&](mln::expected<std::vector<OfflineRegion>, std::exception_ptr> result) {
if (!result) {
std::cerr << "Error merging database: " << util::toString(result.error()) << std::endl;
retCode = 1;
@@ -264,7 +264,7 @@ int main(int argc, char* argv[]) {
std::signal(SIGINT, [](int) { stop(); });
fileSource->createOfflineRegion(
definition, metadata, [&](mbgl::expected<OfflineRegion, std::exception_ptr> region_) {
definition, metadata, [&](mln::expected<OfflineRegion, std::exception_ptr> region_) {
if (!region_) {
std::cerr << "Error creating region: " << util::toString(region_.error()) << std::endl;
loop.stop();

View File

@@ -1,11 +1,11 @@
#include <mbgl/map/map.hpp>
#include <mbgl/map/map_options.hpp>
#include <mbgl/util/image.hpp>
#include <mbgl/util/run_loop.hpp>
#include <mln/map/map.hpp>
#include <mln/map/map_options.hpp>
#include <mln/util/image.hpp>
#include <mln/util/run_loop.hpp>
#include <mbgl/gfx/backend.hpp>
#include <mbgl/gfx/headless_frontend.hpp>
#include <mbgl/style/style.hpp>
#include <mln/gfx/backend.hpp>
#include <mln/gfx/headless_frontend.hpp>
#include <mln/style/style.hpp>
#include <args.hxx>
@@ -19,8 +19,10 @@ int main(int argc, char* argv[]) {
args::ValueFlag<std::string> backendValue(argumentParser, "Backend", "Rendering backend", {"backend"});
args::ValueFlag<std::string> apikeyValue(argumentParser, "key", "API key", {'t', "apikey"});
args::ValueFlag<std::string> styleValue(argumentParser, "URL", "Map stylesheet", {'s', "style"});
args::ValueFlag<std::string> outputValue(argumentParser, "file", "Output file name", {'o', "output"});
args::ValueFlag<std::string> styleValue(
argumentParser, "URL|file|-", "Map stylesheet (use '-' for stdin)", {'s', "style"});
args::ValueFlag<std::string> outputValue(
argumentParser, "file|-", "Output file name (use '-' for stdout)", {'o', "output"});
args::ValueFlag<std::string> cacheValue(argumentParser, "file", "Cache database file name", {'c', "cache"});
args::ValueFlag<std::string> assetsValue(
argumentParser, "file", "Directory to which asset:// URLs will resolve", {'a', "assets"});
@@ -69,7 +71,7 @@ int main(int argc, char* argv[]) {
const double lon = lonValue ? args::get(lonValue) : 0;
const double alt = altValue ? args::get(altValue) : 0;
const double zoom = zoomValue ? args::get(zoomValue) : 0;
const double fov = fovValue ? args::get(fovValue) : mbgl::util::rad2deg(mbgl::util::DEFAULT_FOV);
const double fov = fovValue ? args::get(fovValue) : mln::util::rad2deg(mln::util::DEFAULT_FOV);
const double bearing = bearingValue ? args::get(bearingValue) : 0;
const double pitch = pitchValue ? args::get(pitchValue) : 0;
const double roll = rollValue ? args::get(rollValue) : 0;
@@ -87,9 +89,11 @@ int main(int argc, char* argv[]) {
const bool debug = debugFlag ? args::get(debugFlag) : false;
using namespace mbgl;
using namespace mln;
auto mapTilerConfiguration = mbgl::TileServerOptions::MapTilerConfiguration();
auto mapTilerConfiguration = mln::TileServerOptions::MapTilerConfiguration();
std::string json;
std::string style = styleValue ? args::get(styleValue) : mapTilerConfiguration.defaultStyles().at(0).getUrl();
util::RunLoop loop;
@@ -115,11 +119,16 @@ int main(int argc, char* argv[]) {
.withApiKey(apikey)
.withTileServerOptions(mapTilerConfiguration));
if (style.find("://") == std::string::npos) {
style = std::string("file://") + style;
}
if (style == "-") {
json = std::string(std::istreambuf_iterator<char>(std::cin), std::istreambuf_iterator<char>());
map.getStyle().loadJSON(json);
} else {
if (style.find("://") == std::string::npos) {
style = std::string("file://") + style;
}
map.getStyle().loadURL(style);
map.getStyle().loadURL(style);
}
std::vector<double> bounds = args::get(boundsValue);
if (bounds.size() == 4) {
LatLngBounds boundingBox = LatLngBounds::hull(LatLng(bounds[0], bounds[1]), LatLng(bounds[2], bounds[3]));
@@ -136,14 +145,19 @@ int main(int argc, char* argv[]) {
}
if (debug) {
map.setDebug(debug ? mbgl::MapDebugOptions::TileBorders | mbgl::MapDebugOptions::ParseStatus
: mbgl::MapDebugOptions::NoDebug);
map.setDebug(debug ? mln::MapDebugOptions::TileBorders | mln::MapDebugOptions::ParseStatus
: mln::MapDebugOptions::NoDebug);
}
try {
std::ofstream out(output, std::ios::binary);
out << encodePNG(frontend.render(map).image);
out.close();
std::string png = encodePNG(frontend.render(map).image);
if (output == "-") {
std::cout.write(png.data(), png.size());
} else {
std::ofstream out(output, std::ios::binary);
out << png;
out.close();
}
} catch (std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
exit(1);

View File

@@ -11,94 +11,93 @@ target_compile_definitions(
)
list(APPEND
INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/context.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/mtl_fwd.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/tile_layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/vertex_attribute.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/mtl/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/background.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/common.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/collision.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/debug.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/particle_gpu_validation.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/mtl/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/context.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/mtl_fwd.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/tile_layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/vertex_attribute.hpp
${PROJECT_SOURCE_DIR}/include/mln/mtl/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/background.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/circle.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/common.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/collision.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/debug.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/fill.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/line.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/particle_gpu_validation.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/retained_underlay_composite.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/raster.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/mtl/symbol.hpp
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/drawable_impl.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/mtl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/mtl/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/background.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/circle.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/collision.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/debug.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/fill.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/line.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/particle_gpu_validation.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/raster.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/mtl/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/mtl/custom_layer_render_parameters.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/context.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/drawable_impl.hpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/mtl.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mln/mtl/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/background.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/circle.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/collision.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/debug.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/fill.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/line.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/particle_gpu_validation.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/retained_underlay_composite.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/raster.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/mtl/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mln/style/layers/mtl/custom_layer_render_parameters.cpp
)
find_library(METAL_FRAMEWORK Metal)
if (METAL_FRAMEWORK)
target_link_libraries(mbgl-core PRIVATE ${METAL_FRAMEWORK})
endif()
target_link_libraries(mbgl-core PRIVATE "-framework Metal")

View File

@@ -11,142 +11,144 @@ target_compile_definitions(
)
list(APPEND
INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/platform/gl_functions.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_manifest.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_group_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/prelude.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/background.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/background_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/collision_box.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/collision_circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/debug.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_outline.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_outline_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_outline_triangulated.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_extrusion_instanced.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_extrusion_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/fill_extrusion_pattern_instanced.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line_gradient.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/line_sdf.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/location_indicator_textured.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/symbol_sdf.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/symbol_text_and_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/wide_vector.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mln/platform/gl_functions.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/shader_manifest.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/shader_group_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/prelude.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/background.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/background_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/circle.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/collision_box.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/collision_circle.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/debug.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_outline.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_outline_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_outline_triangulated.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion_instanced.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/fill_extrusion_pattern_instanced.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/particle_gpu_validation.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/retained_underlay_composite.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/line.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/line_gradient.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/line_pattern.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/line_sdf.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/location_indicator_textured.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/raster.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/symbol_sdf.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/symbol_text_and_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/wide_vector.hpp
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/gl/attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/attribute.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/command_encoder.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/context.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/fence.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/debugging_extension.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/debugging_extension.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/defines.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/draw_scope_resource.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/enum.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/enum.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/extension.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/framebuffer.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/object.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/object.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/render_pass.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/renderbuffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/renderbuffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_pool.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/resource_pool.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/state.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/timestamp_query_extension.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/timestamp_query_extension.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/types.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/upload_pass.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/value.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/value.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_array.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_array.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/platform/gl_functions.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/legacy/clipping_mask_program.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/legacy/program_base.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/legacy/program.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/legacy/programs.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/legacy/programs.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/attribute.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/attribute.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/command_encoder.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/context.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/context.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/fence.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/fence.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/debugging_extension.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/debugging_extension.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/defines.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/draw_scope_resource.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/enum.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/enum.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/extension.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/framebuffer.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/object.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/object.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/render_pass.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/renderbuffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/renderbuffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/resource_pool.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/resource_pool.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/state.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/timestamp_query_extension.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/timestamp_query_extension.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/types.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/uniform.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/uniform.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/upload_pass.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/value.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/value.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/vertex_array.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/vertex_array.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/src/mln/platform/gl_functions.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/legacy/clipping_mask_program.hpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/legacy/program_base.hpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/legacy/program.hpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/legacy/programs.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/legacy/programs.hpp
)
list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/background_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/circle_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/collision_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/custom_drawable_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/debug_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/fill_extrusion_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/heatmap_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/heatmap_texture_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/color_relief_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/line_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/location_indicator_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/raster_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_defines.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/symbol_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/widevector_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/custom_drawable_layer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/layermanager/custom_drawable_layer_factory.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/gl/shader_program_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/buffer_allocator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/drawable_gl_builder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/layer_group_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/uniform_buffer_gl.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/gl/vertex_attribute_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/background_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/circle_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/collision_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/custom_drawable_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/debug_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/fill_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/fill_extrusion_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/heatmap_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/heatmap_texture_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/hillshade_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/color_relief_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/hillshade_prepare_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/line_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/location_indicator_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/raster_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/shader_defines.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/symbol_layer_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/widevector_ubo.hpp
${PROJECT_SOURCE_DIR}/include/mln/style/layers/custom_drawable_layer.hpp
${PROJECT_SOURCE_DIR}/include/mln/layermanager/custom_drawable_layer_factory.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/gl/shader_program_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/buffer_allocator.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/drawable_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/drawable_gl_builder.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/layer_group_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/uniform_buffer_gl.hpp
${PROJECT_SOURCE_DIR}/include/mln/gl/vertex_attribute_gl.hpp
)
list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_info.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/gl/shader_program_gl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/buffer_allocator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/drawable_gl_impl.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/layer_group_gl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/uniform_buffer_gl.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gl/vertex_attribute_gl.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/shader_info.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/gl/shader_program_gl.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/buffer_allocator.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/drawable_gl.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/drawable_gl_builder.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/drawable_gl_impl.hpp
${PROJECT_SOURCE_DIR}/src/mln/gl/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/layer_group_gl.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/uniform_buffer_gl.cpp
${PROJECT_SOURCE_DIR}/src/mln/gl/vertex_attribute_gl.cpp
)

View File

@@ -12,93 +12,93 @@ target_compile_definitions(
list(APPEND
INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/vulkan/custom_layer_init_parameters.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/style/layers/vulkan/custom_layer_render_parameters.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/context.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/descriptor_set.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/pipeline.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/tile_layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/vertex_attribute.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/vulkan/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/background.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/collision.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/common.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/fill.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/vulkan/widevector.hpp
${PROJECT_SOURCE_DIR}/include/mln/style/layers/vulkan/custom_layer_init_parameters.hpp
${PROJECT_SOURCE_DIR}/include/mln/style/layers/vulkan/custom_layer_render_parameters.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/context.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/descriptor_set.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/offscreen_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/pipeline.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/renderable_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/tile_layer_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/vertex_attribute.hpp
${PROJECT_SOURCE_DIR}/include/mln/vulkan/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/shader_group.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/background.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/circle.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/collision.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/common.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/fill.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/line.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/raster.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/vulkan/widevector.hpp
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/style/layers/vulkan/custom_layer_render_parameters.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/descriptor_set.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/drawable_impl.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/pipeline.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/renderable_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/vulkan/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/background.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/circle.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/collision.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/debug.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/fill.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/line.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/raster.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/vulkan/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mln/style/layers/vulkan/custom_layer_render_parameters.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/context.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/descriptor_set.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/drawable_impl.hpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/pipeline.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/renderable_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mln/vulkan/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/background.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/circle.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/collision.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/debug.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/fill.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/line.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/raster.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/vulkan/widevector.cpp
)

View File

@@ -17,99 +17,98 @@ target_include_directories(
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/headless_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/draw_scope_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/wgsl_preprocessor.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/background.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/circle.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/collision.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/debug.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/fill.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/line.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/raster.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/draw_scope_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/wgsl_preprocessor.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/background.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/circle.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/clipping_mask.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/collision.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/custom_geometry.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/custom_symbol_icon.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/debug.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/fill.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/fill_extrusion.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/heatmap.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/heatmap_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/hillshade.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/hillshade_prepare.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/color_relief.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/line.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/location_indicator.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/raster.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/symbol.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/widevector.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/offscreen_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/tile_layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/layer_group.cpp
)
list(APPEND
INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/context.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/background.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/circle.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/collision.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/common.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/debug.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/fill.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/line.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/raster.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/webgpu/widevector.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/webgpu/vertex_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/command_encoder.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/context.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/drawable.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/drawable_builder.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/dynamic_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/index_buffer_resource.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/render_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/renderer_backend.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/shader_program.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/background.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/circle.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/clipping_mask.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/collision.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/common.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/custom_geometry.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/custom_symbol_icon.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/debug.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/fill.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/fill_extrusion.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/heatmap.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/heatmap_texture.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/hillshade.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/hillshade_prepare.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/color_relief.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/line.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/location_indicator.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/raster.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/symbol.hpp
${PROJECT_SOURCE_DIR}/include/mln/shaders/webgpu/widevector.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/texture2d.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/uniform_buffer.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/upload_pass.hpp
${PROJECT_SOURCE_DIR}/include/mln/webgpu/vertex_buffer_resource.hpp
)
list(APPEND
SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/vertex_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/command_encoder.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/context.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/drawable.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/drawable_builder.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/dynamic_texture.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/index_buffer_resource.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/render_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/renderer_backend.cpp
${PROJECT_SOURCE_DIR}/src/mln/shaders/webgpu/shader_program.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/texture2d.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/upload_pass.cpp
${PROJECT_SOURCE_DIR}/src/mln/webgpu/vertex_buffer_resource.cpp
)
# Include WebGPU vendor configuration (Dawn or wgpu)
@@ -119,7 +118,13 @@ include(${PROJECT_SOURCE_DIR}/vendor/webgpu.cmake)
if(MLN_WEBGPU_IMPL_DAWN)
include(${PROJECT_SOURCE_DIR}/vendor/dawn.cmake)
if(TARGET mbgl-vendor-dawn)
target_link_libraries(mbgl-core PRIVATE mbgl-vendor-dawn)
if(MLN_WEBGPU_EMDAWN)
# Emdawn is a compiler/linker port rather than a conventional
# library. Consumers must inherit its final-link options.
target_link_libraries(mbgl-core PUBLIC mbgl-vendor-dawn)
else()
target_link_libraries(mbgl-core PRIVATE mbgl-vendor-dawn)
endif()
endif()
elseif(MLN_WEBGPU_IMPL_WGPU OR MLN_WEBGPU_IMPL_FFI)
# Include wgpu-native integration
@@ -127,6 +132,11 @@ elseif(MLN_WEBGPU_IMPL_WGPU OR MLN_WEBGPU_IMPL_FFI)
if(TARGET mbgl-vendor-wgpu)
target_link_libraries(mbgl-core PRIVATE mbgl-vendor-wgpu)
# Add WebGPU-Cpp implementation file (required for wgpu-native backend)
list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/webgpu_cpp_impl.cpp)
list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mln/webgpu/webgpu_cpp_impl.cpp)
endif()
endif()
# Headless backend uses Dawn native / wgpu-native bootstrap; skip for emdawnwebgpu.
if(NOT MLN_WEBGPU_EMDAWN)
list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mln/webgpu/headless_backend.cpp)
endif()

View File

@@ -75,7 +75,7 @@ The shaders need a representation visible outside the toolkit and we need to cha
Individual shaders are represented by an object class with massive amounts of template logic and a minimum of in-line comments. They’re opaque from the outside and only controllable through vector tile data and styles.
The [render_raster_layer](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/layers/render_raster_layer.cpp), as an example, asks for the instantiation of (eventually) RasterProgram. Rather than having the source for the program, that then pokes into a compressed chunk of memory that contains the source, which is then uncompressed and fed into OpenGL for compilation.
The [render_raster_layer](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/layers/render_raster_layer.cpp), as an example, asks for the instantiation of (eventually) RasterProgram. Rather than having the source for the program, that then pokes into a compressed chunk of memory that contains the source, which is then uncompressed and fed into OpenGL for compilation.
All of this makes sense in context, but is completely inaccessible outside the toolkit and rather incomprehensible when making changes.
@@ -134,9 +134,9 @@ Modern graphics pipelines use more than one pass to create a visual representati
Without getting too deep into specifics, early rendering passes allow the developer to use the power of the rasterizer for their own data. Later rendering passes are typically used to decorate the map with effects.
#### The way it is now:
The [low level rendering logic](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/renderer_impl.cpp) in the toolkit seems to only support one explicit rendering pass.
The [low level rendering logic](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/renderer_impl.cpp) in the toolkit seems to only support one explicit rendering pass.
However, there is the [RenderPass](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/gl/render_pass.hpp) so some notion of this exists in the toolkit, but it's not clear how complete that is. It may depend on ordering to work things out, rather than the explicit command buffer filling and fences we would use in a more modern approach.
However, there is the [RenderPass](https://github.com/maplibre/maplibre-native/blob/main/src/mln/gl/render_pass.hpp) so some notion of this exists in the toolkit, but it's not clear how complete that is. It may depend on ordering to work things out, rather than the explicit command buffer filling and fences we would use in a more modern approach.
In any case, we need this filled out more explicitly for the render targets.
@@ -205,7 +205,7 @@ In WhirlyGlobe we’ve always called out separate objects for drawing certain ki
Which is to say that Drawable is a good concept which can encapsulate a lot of complexity. So much so that it eventually encapsulates ALL the complexity, at least at that level of rendering. It’s a good interface between the things that construct geometry and the things that render geometry.
To keep the Drawable somewhat manageable, we added the concept of a Drawable Builder. This is an object that you throw geometry at in a somewhat disordered way and it emits Drawables when you’re done. MapLibre's [Buckets](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/bucket.hpp) are similar, but not quite the same. Perhaps they'll be adaptable. We'll see.
To keep the Drawable somewhat manageable, we added the concept of a Drawable Builder. This is an object that you throw geometry at in a somewhat disordered way and it emits Drawables when you’re done. MapLibre's [Buckets](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/bucket.hpp) are similar, but not quite the same. Perhaps they'll be adaptable. We'll see.
Drawables and Builders have a subclass for each supported rendering SDK. One mistake we won’t bring over is using multiple inheritance for that. You’re welcome.
@@ -221,7 +221,7 @@ Builders will be used primarily by Layers to emit Drawables for rendering.
This is a big topic because it gets to the very heart of the critical changes we want to make to the toolkit. There are a couple of different categories that are revealing and we'll use some examples.
Most commonly there are *render layers*. These are individual classes that know how to render, for instance, the [fill layer](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/layers/render_fill_layer.cpp). You'll see a bunch of these that start with "render" and end with "layer".
Most commonly there are *render layers*. These are individual classes that know how to render, for instance, the [fill layer](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/layers/render_fill_layer.cpp). You'll see a bunch of these that start with "render" and end with "layer".
What's going on here is that the toolkit has broken out a bunch of features from a tile, consolidated them together in a Bucket and then handed them off to a Layer to render. Sure enough, if you look in the render_*_layer you can see it doing just that.
@@ -230,7 +230,7 @@ Now it's not redoing the work of consolidation each frame, but it is doing a lot
- This is more specific logic than we'd want to see in a rendering loop. Remember, this runs every frame.
- If your data is not in tile form, it's going to be hard to render.
That last one is really interesting and we can find an example in MapLibre Native itself. Look to the [Render Location Indicator Layer](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/layers/render_location_indicator_layer.cpp). Here's where this approach trips up... rolls down the hill... into a pile of broken glass. You can feel the developers' pain in this module.
That last one is really interesting and we can find an example in MapLibre Native itself. Look to the [Render Location Indicator Layer](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/layers/render_location_indicator_layer.cpp). Here's where this approach trips up... rolls down the hill... into a pile of broken glass. You can feel the developers' pain in this module.
If you're not familiar with real time rendering toolkits, I'll put it this way. Managing the logic for a location puck that updates every time the user moves should not involve direct OpenGL calls. It should be abstracted, at least a little.
@@ -319,7 +319,7 @@ Every real time rendering toolkit has a main rendering loop or something like it
On iOS this runs on the main thread and on Android this runs on its own separate thread. You might think the latter is better, but it has its own problems (gesture lag). In any case, MapLibre Native handles both just fine.
The toolkit's main rendering "loop" lives in [renderer_impl](https://github.com/maplibre/maplibre-native/blob/main/src/mbgl/renderer/renderer_impl.cpp) and it's worth a look. It serves as a central point to begin examining the real time rendering.
The toolkit's main rendering "loop" lives in [renderer_impl](https://github.com/maplibre/maplibre-native/blob/main/src/mln/renderer/renderer_impl.cpp) and it's worth a look. It serves as a central point to begin examining the real time rendering.
As you might suspect, the rendering loop logic and implementation are heavily influenced by OpenGL.

View File

@@ -0,0 +1,184 @@
# Custom Vector Source
## Motivation
MapLibre Native currently supports two ways to provide vector tile data:
1. **URL-based sources** (`vector` source type) — tiles are fetched according to the protocol of the URL (`http://`,
`file://`, etc.).
2. **CustomGeometrySource** — accepts GeoJSON features programmatically, but internally re-encodes them into vector
tiles, adding overhead and limiting control over the tile encoding.
This proposal comes from the need for an efficient and kotlin native way to asynchronously connect a tile data source
similar to vector tile.
CustomGeometrySource was considered, but it has some obvious drawbacks:
1. Tile requests are in the format of a bounding box — since it's not a tile X/Y/Z it is pretty unclear what logic it
conforms to.
1. Is it just a tile X/Y/Z but converted to geo bounds?
2. Can it ever request data in a bounding box that is bigger than just one X/Y/Z tile
3. Additional boilerplate of mapping geo bounds into tile X/Y/Z
2. Internally CustomGeometrySource spawns a 4 thread threadpool and runs tasks one by one inside. It's a good old
technique
but Kotlin has things like coroutines which are a more ergonomic way to handle suspendable operations (like network
requests).
3. CustomGeometrySource operates over GeoJSON. So in case of a vector tiles like format it needs to be converted to
GeoJSON and then internally it will be cut down into vector tile format back.
## Proposed Change
Introduce a new source type, `CustomVectorSource`, that lets the application deliver raw binary tile data (starting with
MVT) directly to the rendering pipeline. The source participates in the standard tile loading lifecycle — the core
requests tiles by `CanonicalTileID`, and the application responds asynchronously with encoded data or an error.
### Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Platform layer (Kotlin) │
│ │
│ CustomVectorTileProvider.fetchTile(z, x, y) → TileData │
│ ↕ (JNI / platform bridge) │
├─────────────────────────────────────────────────────────────┤
│ Core C++ layer │
│ │
│ CustomVectorSource │
│ ├── Options { fetchTileFunction, cancelTileFunction, │
│ │ zoomRange } │
│ ├── setTileData(CanonicalTileID, shared_ptr<string>, │
│ │ TileDataFormat) │
│ ├── setTileError(CanonicalTileID, exception_ptr) │
│ └── invalidateTile(CanonicalTileID) │
│ │
│ CustomVectorTileLoader (Actor on Scheduler::GetBackground) │
│ └── Receives fetch/cancel requests from tiles │
│ │
│ CustomVectorTile (extends Tile) │
│ └── Parses MVT data via VectorTileData, delegates to │
│ standard GeometryTileWorker for symbol/line/fill │
│ layout │
│ │
│ RenderCustomVectorSource (extends RenderTileSetSource) │
│ └── Standard tile pyramid rendering │
├─────────────────────────────────────────────────────────────┤
│ Existing rendering pipeline (unchanged) │
│ GeometryTileWorker → Buckets → Drawables → GPU │
└─────────────────────────────────────────────────────────────┘
```
### Key Design Decisions
1. **Binary data, not GeoJSON.** For the Kotlin layer to send data to C++, it needs to be marshaled into some
data format that can be passed across languages. One could come up with a new binary format, implement an encoder in
Kotlin and a decoder in C++. But it turns out MVT is already such a format — just a binary blob that can be passed
simply as a sized buffer. Existing infrastructure of `MVT` tile data is just reused as is.
2. **Actor-based concurrency.** `CustomVectorTileLoader` runs on a background thread via MapLibre's Actor system. The
platform callback (fetch/cancel) is invoked on this thread; the platform layer is responsible for dispatching to its
own preferred context (e.g., coroutines on Android).
3. **Cooperative cancellation.** When a tile is no longer needed (e.g., user panned away), the core calls
`cancelTileFunction`. The platform layer can abort in-flight work.
4. **Standard tile pipeline reuse.** Once binary data arrives, it goes through the same `VectorTileData` →
`GeometryTileWorker` path as HTTP-fetched vector tiles. No custom rendering code is needed.
5. **Extensible format enum.** `TileDataFormat` is an enum starting with `MVT`. Future formats (e.g., a hypothetical
compact binary format) can be added without API changes. And obvious next candidate is `MLT` format support in
CustomVectorSource.
### Threading Model
```
Main thread: Source::update() → tile needs data
↓
Background Actor: CustomVectorTileLoader::fetchTile()
↓
Platform bridge: fetchTileFunction callback → platform code
↓ (async, any thread)
setTileData() → mailbox → CustomVectorTile::setData()
↓
GeometryTileWorker: parse MVT, create buckets (background pool)
↓
Main thread: Tile ready → render
```
## API Modifications
### Core C++ (public header)
```cpp
namespace mln::style {
enum class TileDataFormat : uint8_t { MVT = 0 };
using TileFunction = std::function<void(const CanonicalTileID&)>;
class CustomVectorSource final : public Source {
public:
struct Options {
TileFunction fetchTileFunction;
TileFunction cancelTileFunction;
Range<uint8_t> zoomRange = {0, 18};
};
CustomVectorSource(std::string id, const Options& options);
void setTileData(const CanonicalTileID&,
const std::shared_ptr<const std::string>& data,
TileDataFormat format = TileDataFormat::MVT);
void setTileError(const CanonicalTileID&, std::exception_ptr error);
void invalidateTile(const CanonicalTileID&);
};
} // namespace mln::style
```
### Android (Kotlin)
```kotlin
interface CustomVectorTileProvider {
suspend fun fetchTile(z: Int, x: Int, y: Int): TileData
}
sealed class TileData {
class Mvt(val data: ByteArray) : TileData()
}
class CustomVectorSource(
id: String,
provider: CustomVectorTileProvider,
scope: CoroutineScope,
minZoom: Int = 0,
maxZoom: Int = 18
) : Source()
```
The Android API uses Kotlin coroutines for async tile fetching. The `CoroutineScope` is caller-provided. Meaning
`CustomVectorSource` can run on a threadpool but not limited to a threadpool. `CoroutineScope` abstracts it away.
### iOS / macOS
Not included in this initial implementation. The core C++ API is platform-agnostic and can be wrapped in Swift/Obj-C
following the same pattern.
## Migration Plan and Compatibility
All changes are **additive**. No existing APIs are modified or removed. The new source type is opt-in — applications
that don't use it are unaffected.
- A new `SourceType::CustomMVTVector` enum value is added to the core.
- Existing `CustomGeometrySource` remains unchanged and is not deprecated.
## Rejected Alternatives
### Extend CustomGeometrySource to accept binary data
`CustomGeometrySource` is designed around GeoJSON features and internally manages geometry encoding. Bolting binary tile
support onto it would conflate two different data models and complicate its already non-trivial implementation.
### Use a local file source with a custom URL scheme
This approach (e.g., `mbtiles://` or `custom://`) requires the application to write tile data to disk or implement a
custom `FileSource`. It adds I/O overhead, doesn't support cooperative cancellation, and is harder to integrate with
structured concurrency frameworks like Kotlin coroutines.

View File

@@ -0,0 +1,68 @@
# Android 粒子 Validation APK 构建规范 v1
## 目的
固定 `MapLibreAndroidTestApp` 粒子验证 APK 的制作流程,避免每次重新调查 Gradle 参数、Java/Android SDK 环境、native strip 与 APK 体积异常。
权威入口:
```bash
./tools/particle_android_validation/build_validation_apk.sh
```
除排错外,不再手工拼接 Validation APK 的 Gradle 命令。
## 固定构建契约
```text
App: MapLibreAndroidTestApp
Renderer: OpenGL
BuildType: Debug
ABI: x86_64
Native debug symbols: strip
Gradle console: plain
```
核心参数固定为:
```text
:MapLibreAndroidTestApp:assembleOpenglDebug
-Pmaplibre.abis=x86_64
-Pmaplibre.validation.stripNativeDebugSymbols=true
--console=plain
```
## 必须执行的防错逻辑
1. 构建前删除目标 APK 文件,但不做全量 Gradle clean。
2. Java 必须为 17;Android SDK 必须可解析。
3. 构建失败时不得复用旧 APK。
4. 构建成功后必须校验 APK ZIP 完整性。
5. APK 内 native ABI 必须只有 `x86_64`。
6. 必须包含 `lib/x86_64/libmaplibre.so`。
7. 默认 `libmaplibre.so` 不得超过 80 MiB。
8. 默认 APK 不得超过 100 MiB。
9. 必须记录 SHA-256、commit、branch、dirty 状态和完整 Gradle 日志。
STEP-090/091 的正常 APK 参考体积约 57 MB。数百 MB 通常表示未 strip native debug symbols;约 95 MB 且 ZIP 逻辑内容仍约 60 MB,通常表示同一路径增量打包残留旧 ZIP 尾部数据。
## 标准产物目录
```text
artifacts/maplibre-particle/android-validation/
├── MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk
├── build-info.txt
└── build.log
```
该目录为本地验证产物目录,不进入 Git。
## 边界
本 Pipeline 只负责构建和 APK 自验,不负责 ADB 安装、Activity 启动、设备交互或 STEP 业务场景验收。
Android 上机验证仍由后续独立步骤执行。构建成功不等于 STEP 场景已通过。
## 变更规则
若未来测试 App、ABI、Renderer、strip 策略或产物门槛发生正式变化,应先修改 Pipeline 脚本与本规范,再使用新流程;不得只在聊天或临时命令中形成新的事实基线。

View File

@@ -0,0 +1,104 @@
# Particle V13 严格代码审查报告
日期:2026-09-25(Asia/Tokyo)
审查性质:Mac 仓库只读代码审查、历史任务/报告交叉核对、定向原生测试复跑。未修改业务源码、未执行设备 GPU runtime 或 profiler。
## 1. 审查基线和证据分级
- Mac:`/Users/tei/dev/MapLibreParticleLab`;分支:`particle/main`。
- HEAD 与 `origin/particle/main` 一致:`ee0b89e711bf01335b3aa71d36def785cd97fec3`;工作树 clean。
- V13 功能提交:`016fd1fff6a33cf795c10d4f5c5413d308383677`。发布版本:`13.4.1-particle.13`。
- Integration 主线:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`。
- 参照:`docs/40_任务/STEP-092...`、`STEP-093...`、`STEP-094...` 任务;对应 `docs/报告/` 与 `docs/50_Step记录/`;STEP-088/089/090 性能历史报告。
- 本次独立复跑:Render Segment/Retained/Fast Path 43/43 PASS;Field/QBIN/Viewport Reseed 69/69 PASS;V13 RN package TypeScript PASS。
- 当前 RN Test Host `npm run typecheck` **失败(exit 2)**:TS2307 无法解析 `@navsea/maplibre-particle-rn`,伴随 TS7006。Host package.json 仍指向 `.10`。此前 Codex 使用临时工作树依赖通过,不等于当前默认安装可重复验证。
- STEP-094 报告记录 OpenGL、Metal 构建、Codegen、Android Validation APK 通过;**Android/iOS V13 runtime、GPU frame/visual、功耗与长时间运行仍 NOT RUN**。此报告不将历史 Android 性能测量冒充 V13 性能。
## 2. 审查结论
没有在已复跑的纯数据结构/协议测试中发现失败;但 V13 在高频渲染、Metal 多段重建、GPU 资源分配和真实运行态验收上存在明确风险。不能由 43 项纯 Native 测试和编译 PASS 推断任意 R/C 交错的 GPU 输出、跨平台性能已经验收。
### F01 — P1:完整 Diagnostics 深复制进入每帧热路径(源码确定)
证据:`src/mbgl/renderer/renderer_impl.cpp:206-208,500-505`;`src/mbgl/renderer/retained_underlay_runtime_state.hpp:243-254`。
`tryRenderParticleFastFrame()` 和 `render()` 调用 `renderSegmentSnapshot()`;该函数在互斥锁内复制完整 `RenderSegmentDiagnostics`(含 `styleLayers` 与每个 Segment 的重复 `layers`/字符串),再补几个状态字段。一次 fast frame 可能复制两次。与 pull-based 诊断的设计意图不一致。
修复:增加只读的轻量 hot-path 状态(enabled/configRevision/topologyRevision 等);完整快照只在外部 pull 时构造;快照构建/复制次数做计数,稳定 30/60 FPS 应接近零。
### F02 — P1:Metal Retained 构建逐段 GPU 同步等待(源码确定,运行影响待 profile)
证据:`renderer_impl.cpp:790-824`;`retained_underlay_cache.cpp:316-322`;`src/mbgl/mtl/command_encoder.cpp:29-31`;`src/mbgl/mtl/offscreen_texture.cpp:86-91`。
`finishBuild()->present(*offscreenTexture)->swap()` 内部 `commit()` 后 `waitUntilCompleted()`。一帧多个 Retained 段失效时,可能产生多次串行 CPU/GPU 同步。使用 Metal System Trace / GPU Capture 测量 `waitUntilCompleted` 时间和 command-buffer 数量,再设计保持依赖顺序的异步提交/跨 pass 协调;不可仅删除 wait 而不确保纹理可读。
### F03 — P1:缓存内容失效与 GPU 资源销毁耦合(源码确定)
证据:`retained_underlay_cache.cpp:203-209,241-255,364-374`。
`revisionChanged` 使 `invalidate()` 与 `releaseResources()` 一起发生,即使 viewport 未变、GPU context 有效,重建时仍需重新创建纹理和 composite group。普通 camera、source/tile、content revision 在多 Retained 段下会放大分配/销毁和相关同步。
修复:分离 `contentDirty` 与 `resourceInvalid`,同尺寸同 context 保留 allocation、重新 clear/build;context loss、尺寸不兼容、显式关闭/释放时才销毁。用 peak GPU bytes、textureCreate、resourceRelease、每帧 build/reuse 证明收益与正确性。
### F04 — P1:多缓存没有 GPU 内存预算(源码确定,OOM 影响待量化)
证据:`renderer_impl.cpp:506-524`;`retained_underlay_cache.cpp:241-251`;`mtl/offscreen_texture.cpp:20-55`;`gl/offscreen_texture.cpp:18-35`。
每个 Retained Segment 均拥有完整 viewport 的 RGBA + depth/stencil 离屏资源;总成本约随 retainedCount×width×height 增长。相邻 R-R 也各自独立,符合契约,但高像素 iPad 与多段场景缺乏实际 bytes/peak/budget 观测。先增加预算与失败时的安全 full-render fallback,切勿通过强制合并相邻 R-R 破坏 V13 契约。
### F05 — P1:V13 Test Host 默认安装不可复现(已复现失败)
证据:`integration/react-native/MapLibreParticleRNTestHost/package.json` 指向 `.10`;当前 `npm run typecheck` exit 2;报告也明确临时本地替换后已恢复旧依赖。
修复:将 Test Host 依赖和 lockfile 与已发布 `.13` 精确对应,重新安装并验证 V13 typecheck/codegen/iOS & Android bridge/真实运行;测试日志注明源包及 Native 精确版本。不得将临时链接环境结果写成默认环境 PASS。
### F06 — P1:多 Continuous 的 Particle Fast Path 固定 fallback(明确设计限制)
证据:`renderer_impl.cpp:45-57,198-287` 和 `particle_fast_path.hpp:110-140`,STEP-094 设计与报告。
N 个 Continuous 中只支持恰好一个的 Fast Path;R-C-R-C-R 场景总走 Full CPU。STEP-090 历史 A/B 场景 RenderThread 约为 fast ON 21.7% / OFF 97.0%,只是历史参考,不能直接视作 V13 数据。先采集 V13 多 C 的 CPU/GPU 帧时间与频率,再设计可组合的 multi-C fast path;绝不可为性能偷偷移动 Style Layer 或合并相邻段。
### F07 — P2:范围访问反复计算线性 ordinal(源码确定)
证据:`src/mbgl/renderer/render_orchestrator.hpp:156-180`,`std::distance(multimap.begin(), lower_bound(...))`。每个 Segment 的 opaque/translucent、build、upload/tweaker 路径可重复触发定位。
修复:在 LayerGroup topology 变化时预计算有序索引/ordinal;高频 render 只用稳定区间和 ordinal,避免多段场景重复 O(N) 前缀遍历。
### F08 — P2:多段失效粒度仍过宽(明确设计限制)
证据:`renderer_impl.cpp:565-646`;`render_orchestrator.cpp:156-158,1005-1018`。Layer Impl fingerprint 可局部失效;source/tile/image/glyph/placement 与聚合 `retainedSupported` 仍可能使全部 R fallback/build。应先用 `sourceId` 与 Segment 关联建立有证据的保守依赖关系,无法归属继续全局失效,不得牺牲安全。
### F09 — P2:Diagnostics 资源指标可能发生归属偏差(源码路径风险)
证据:`retained_underlay_runtime_state.hpp:173-240` 更新新 topology/entry;`renderer_impl.cpp:506-524` 随后 `clear()` 旧 caches;旧对象析构在 `retained_underlay_cache.cpp:364-374` 用旧 segmentIndex 报 release。不同 topology 的相同 index 可能指向新段,旧资源的释放计数可能归于新段或被 Continuous cache=null 丢弃。建议使用 cache generation/unique ID 归属,或在更换 diagnostics topology 前记录旧 generation 的 release;聚合 totals 与 per-generation 分离。
### F10 — P2:发布与协调状态资料漂移(核对事实)
Integration 的 `status/`、`compatibility/MATRIX.md` 仍描述 `.5` / iOS XCFramework 等旧状态,Mac 粒子仓库已发布 `.13`。应由粒子负责人维护正式兼容矩阵,区分“Particle 已发布”和“NavSea 已接入/验证”,不得仅根据旧矩阵断言 V13 不存在或已在 NavSea 生效。
## 3. 正确性与验证缺口
以下不是已证实的渲染 bug,而是必须在设备上验证的风险:
- V13 segmented pass 改变了 opaque/translucent、offscreen composite 的执行拆分;纯 resolver GTest 不覆盖 GPU blending/depth/stencil 与 Metal/OpenGL 的最终像素。
- 必须覆盖 R-C-R、C-R-R、R-R-C、R-C-R-C-R、相邻 R-R 独立 cache、全 R、全 C,特别是透明航标/半透明颜色场与动态粒子叠加。
- hidden boundary、invalid/recovery、Style layer insert/remove/reorder、Resize/Rotate/Pan/Zoom/Pitch、Context loss、Cache ON/OFF、Source/Tile mutation、切换 Plan 后的下一帧,均需要截图/帧采样和资源 counter 交叉验收。
- STEP-093/094 报告已明确运行态 NOT RUN,因此不以原生编译 PASS 代替此项。
## 4. 推荐执行顺序(分批,降低反复安装成本)
A. **无需设备的修复**:先处理 F01 快照深复制、F05 Test Host 精确版本与复现、F07 ordinal、F09 诊断归属;增加纯 C++ 定向回归。每阶段只做对应范围,不把源文件无关改动混入。
B. **Metal 资源修复**:合并 F02/F03/F04,统一处理 GPU 对象分配与同步、资源预算;OpenGL/Metal 静态编译和 Native regression 先行;再一次集中到 iPhone/iPad 采集 GPU Frame Capture。
C. **正式运行态验收**:集中执行 topology、alpha/depth、invalid/recovery、30/60 FPS、稳定/变更、内存峰值、30 分钟以上长稳态;记录相同设备、相同 Style、相同视口的 V12/V13 或 V13 ON/OFF 成对指标,不外推历史数字。
D. **后续性能架构**:在基础正确性通过后,再研究多 Continuous Fast Path 和 source-to-segment 精确依赖。保持 Style Z-order 唯一顺序源、不合并同策略相邻段、不引入业务频率分类。
## 5. 最终判定边界
- 纯 Native tests:43/43 + 69/69 PASS;RN V13 package TS PASS。
- Test Host 默认环境:FAIL(缺失模块 / `.10` 引用)。
- 源码性能问题:F01/F02/F03/F07 的触发调用链得到静态确认;真实节省量仍需设备测量。
- V13 Android/iOS GPU runtime、完整画面、内存和长时间性能:本次 NOT RUN;不声称生产级全量 PASS。
- Mac Git:HEAD 与 origin 一致,工作树 clean;未修改粒子源码。

View File

@@ -0,0 +1,116 @@
# R0 Placement 跨段归因与缓存安全边界调查 v1
**STEP-110,2026-10-10;状态:PARTIAL(只读调查,非修复验收)。**
**执行记录**:Codex CLI 0.161.0 实际会话模型 gpt-6-astra,推理 high,完成源码和历史证据审查;其报告写入阶段重复无响应,故本报告由 GPT 指挥 RDC 根据 Codex 调查结论独立回读关键源码后落盘。未运行测试、编译、ADB,也未修改业务实现。所有源码行号以调查起点 particle/main 2df504c 为准。
## 1. 发行源码核对
| 来源 | Git 提交 | 实际意义 |
|---|---|---|
| V23 GSRL Source Lock | 87f3d1c59a52581fb8339f0ea7d106b9f4b94fef | RN / Android Native 13.6.1-particle.23 构建源码 |
| V23 Tag 剥离后提交 | a0a4787b4379ab2819943d20ce05a7ae760a88c3 | 构建提交之后的发行资料提交;注释 Tag 对象本身是 3ce42184… |
| 当前 particle/main 和 origin/particle/main 调查起点 | 2df504cdc0403bdad85d3f31f16fd54a8744abac | 包含后续 STEP-109 尚未发行的 FieldSnapshot 比较优化 |
Codex 通过 Git Blob 对照确认:src/mln/renderer/render_orchestrator.cpp、renderer_impl.cpp、src/mln/text/placement.cpp、include/mln/renderer/render_segment.hpp、src/mln/renderer/retained_underlay_cache.cpp 和 particle_fast_path.hpp 在上述三个提交完全相同。此范围内 main 的分析适用于 V23;不能反推 STEP-109 优化已在 V23 APK 生效。
NavSea 输入冻结事实:Android 13 FZG1-2 V23,R0 [0,31) RETAINED 含地名 Symbol,C1 后续 82 层 CONTINUOUS。15.24 秒期间 placementChanged=true、R0 placementGatePassed=false、build/reuse=0、fallback/invalidation=453、fastPathHit Δ0、fastPathMiss Δ452,CPU 约 99%。**快照只能证实 Gate 未通过,不能证实 C1 引发 R0 Symbol 实际位置变化。**
## 2. Q1:Placement 产生、传播、状态语义
1. src/mln/renderer/renderer.cpp:53-65:先尝试 tryRenderParticleFastFrame;未命中即创建 RenderTree、prepare、完整 render,再缓存 prepared tree。
2. src/mln/renderer/render_orchestrator.cpp:737-749:逐个 RenderLayer.prepare,依据动态 needsPlacement() 构建 layersNeedPlacement。该集合与 R0 的结构性 placementCapable 不是一回事。
3. 同文件 :758-773:反向遍历整个 RenderTree 的需要 Placement 的图层,CrossTileSymbolIndex.addLayer 汇聚 Tile/Symbol 变化。该过程是整图范围,不按 Segment 独立产生。
4. 同文件 :775-810:在 Continuous 模式,placementChanged = !placementController.placementIsRecent(now, zoom, override)。为 true 才调用 Placement::create、placeLayers、setPlacement;否则 setPlacementStale。随后取全局 symbolFadeChange,调用 hasTransitions 控制 needsRepaint。它表示**本轮要重新 Placement**,不表示 R0 最终视觉结果必然不同;为 false 也不保证新旧 R0 内容相等。
5. 同文件 :811-823:非 Continuous 模式还有每次非空 Symbol Layers 就重算的特殊路径,不应混同天气播放运行态。
6. 同文件 :840-848:全局 Placement、layersNeedPlacement、symbolBucketsChanged 进入 RenderTree。Renderer 再独立判断各 Retained Segment。
src/mln/text/placement.cpp:45-59 CollisionGroups::get 在 crossSourceCollisions=false 时为不同 Source 创建隔离 collision group;开启时跨 Source Symbol 可以互相影响。**Source 不是 Segment**,同 Source 跨段仍存在相互影响,反之分段并不天然保证视觉隔离。
src/mln/text/placement.cpp:688-748 根据新旧 placed icon/text 更新 opacity 与 fadeStartTime;:1282、:1319 提供 symbolFadeChange / hasTransitions。shaders/symbol_sdf.vertex.glsl:153-158 和 shaders/symbol_text_and_icon.vertex.glsl:153-158 表明 alpha 依赖 symbol fade uniform。即使符号几何未变,透明度也可能变化。symbolFadeChange、placementTransitionActive 是全局时间/过渡量,不是 R0 独立验收结果。
Source layout/tile/visibility、Camera、Animation、Transition 均可能令 RenderTree prepare 或 Placement 状态变化;普通非 Symbol 粒子/颜色动画可以触发 repaint,但**不能单靠 repaint 来源判定 R0 新旧 placed 结果**。
## 3. Q2:R0 Cache Gate 的真实数据流
src/mln/renderer/renderer_impl.cpp:634-686 对每个 RETAINED Segment 分别取得:
- retainedRangeContentFingerprint / layerContentChanged;
- retainedSegmentSourceState 的 ready、stable、revision;
- 组合的 segmentContentRevision;
- retainedSegmentIsStable;
- retainedRangeHasPlacementCapableLayer 结构性 placementCapable;
- renderSegmentPlacementGatePassed 的结果。
**确切 Gate**:include/mln/renderer/render_segment.hpp:85-90 规定:无 Placement 能力的段直接放行 Placement Gate;否则必须同时满足 placementChanged=false、symbolFadeChange=0、placementTransitionActive=false。它读取的后三者是全局状态,不读取“事件由 C1 发起”或 R0 实际视觉等价证明。
src/mln/renderer/renderer_impl.cpp:661-686:Cache 支持还需要 Style/Sprite/global resources ready、Continuous 模式、没有 3D/debug、segmentStable、无 legacy/unsupported layer。Gate 失败可选择 static-placement 并传递给 cache.evaluate;:681-692 只有 Build/Reuse 才申请资源。:998-1028 先判断 global-live fallback,否则以 visitRenderSegmentExecution 顺序执行每段 composite/live,保持层序;actualExecution=live 不是证明 R0 Symbol 真变化,只表示本帧未从缓存 Composite。
src/mln/renderer/renderer_impl.cpp:590-630 还保留聚合的 retainedPlacementStable 和 Fast Path 支持判定,说明每段 Gate 局部化**不等于**整个 Fast Path 可局部化。
src/mln/renderer/retained_underlay_cache.cpp:85-105 将 camera/boundary、topology、content、tile、static-transition、static-placement 分别归类;:359-379 的 invalidate 更新 lastInvalidationReason,releaseResources 释放缓存,没有“每帧清零历史原因”的语义。因此早期快照当前 placementChanged=false 同时保留 lastInvalidationReason=static-placement **不是直接矛盾证据**;必须在同一帧比较 Gate、cacheSupported、decision、lastReason 的采样时序。
STEP-102/STEP-106 的作用边界是 **placement-free Retained 的局部复用**。本机原始 Android 保存日志 artifacts/maplibre-particle/step106-t2-fix/runtime-T2/stream.log 末尾 tick130:placementChanged=true、crossSourceCollisions=true,但两个 placementCapable=false 的 Retained 段显示 actual=composite、decision=reuse、build=1、reuse=3878;内部 Annotation Symbol 被放入后续 Continuous。T1 的 Symbol-bearing Retained 未解除保守回退。**历史验收未证明含地名 Symbol 的 R0 可复用。**
## 4. Q3:七种反事实场景
| 场景 | Placement 事件来源能否定位 | R0 视觉是否可独立证明不变 | Reuse 必要安全条件 | V23 结论 |
|---|---|---|---|---|
| 1 R0 地名静态,C1 只更新非 Symbol 粒子/颜色 | 可按 Layer/Repaint 来源条件性追踪,Placement Controller 仍是全局 | 不能凭 C1 非 Symbol 就证明 | R0 Tile/layout/content/Camera、全部 Symbol placed/opacity/fade 与缓存时等价,无其他全局影响 | 无充分逐段证明;fail-closed |
| 2 C1 Symbol 改动,不同 Source、crossSourceCollisions 关闭 | Source mutation 可区分 | 隔离碰撞降低风险,但仍不能独立证明 R0 Fade/Camera/其他 Source 状态不变 | 确认 Source 独立、Collision group 隔离、R0 自身配置、Tile、Fade 和层序均稳定 | 仅全局 Gate;fail-closed |
| 3 C1 Symbol 改动且跨源碰撞开启 | 可以部分追踪事件 Source | **不能**;跨 Source Symbol 可能排斥 R0 地名 | 比较新旧全局 Collision 后 R0 每个文本/图标位置、显隐、Opacity、层序 | 无独立证明;fail-closed |
| 4 R0 Symbol layout/tile/visibility/zoom 改动 | 局部 revision/fingerprint 可见 | 默认不能 | 重新 Placement / Build 缓存后验证 R0 版本和绘制结果 | 失效正确;fail-closed |
| 5 Camera pan/zoom/rotation/pitch 改动 | Camera/signature/matrix 可识别 | 旧屏幕纹理通常不等价 | 新 Camera、projection、viewport 对应重新构建缓存并核对碰撞 | Camera/Boundary Gate;fail-closed |
| 6 Fade/Placement Transition 活跃或刚结束 | Controller 可观察过渡 | 不能由几何稳定推出 opacity 稳定 | 所有 R0 Symbol opacity/fade、Transition 与缓存时等价,且状态稳定 | 只有全局 Fade/Transition Gate;fail-closed |
| 7 C1 自动插入 Annotation Symbol | 验证场景的 Annotation layer 可追踪;NavSea 实际归段需运行证据 | 不能:跨段 Annotation 可能参与全局 Collision | 明确真实层序、Source/collision 关系,证明 R0 新旧 placed/fade 等价 | STEP-106 只覆盖 placement-free 段;fail-closed |
重点:不同 Segment **不是** Collision 独立证据。仅识别“谁引起 Placement”并不足以让 R0 安全 Reuse;必须满足实际视觉、相机、图层顺序以及已缓存纹理的等价性。
## 5. Q4:Fast Path 的其他独立必要 Gate
Renderer 的 Fast Path 在完整 RenderTree 之前尝试。src/mln/renderer/renderer_impl.cpp:590-630 的 Retained eligibility/单 C 支持是前置条件;src/mln/renderer/particle_fast_path.hpp 的 Fast Path 判定还检查 boundary、topology、staticStable、hasPlacementTransition、pending changes、nonparticle transition/repaint、fading tiles、是否 Particle repaint、still image、continuous mode、debug、unsupported continuous layer、supported scene、annotation mutation 等。
因此**R0 cache 有效是相关 Fast Path 的必要条件之一,绝不是充分条件**。当前 cache-invalid 说明首先在此受阻,不允许宣称修好 R0 就一定 hit,也不能在本 STEP 扩大调查到 C1 82 层所有性能开销或实施多 Continuous 优化。
## 6. 数据流 / 状态流
Host 更新 / Style / Source / Camera / 动画
→ Renderer::update 尝试 Fast Path(未命中时)
→ RenderOrchestrator.createRenderTree
→ 全局 layer.prepare / needsPlacement / CrossTileIndex / PlacementController
→ 全局 placementChanged / symbolFadeChange / hasTransitions
→ 对各 Retained 段独立:Source ready/stable/revision + Fingerprint + structural placementCapable
→ placement-free 段绕开全局 Placement Gate;Symbol-bearing 段使用全局三项严格 Gate
→ Style/Resource/Camera/Revision 安全校验
→ Cache Build / Reuse / Fallback
→ 保序 RenderSegment Composite / Live
→ Fast Path 另有单 C、pending、unsupported continuous 等 Gate。
## 7. 确定事实、假设、证据不足
| 确定事实 | 不能直接断言的假设 | 需要补的证据 |
|---|---|---|
| V23 核心 Placement/Cache 逻辑与当前 main 等价 | C1 变化直接导致 R0 视觉改变 | NavSea 同一帧事件来源、各 Source mutation、R0 全 Symbol 新旧结果 |
| placementChanged 是全局计算触发量 | placementChanged=true 意味 R0 所有地名改变 | per-R0 placed/opacity/fade 结果签名 |
| 含 Symbol 的 R0 使用 global conservative Gate | R0 453 次 fallback 是已证的性能 Bug | 既有语义被违反的状态不变量;当前并无证据 |
| T2 实验 placement-free 段重复复用 | 现有 T2 足以支持含 Symbol R0 | NavSea 实际 Source/Annotation/跨源碰撞和真实设备对照 |
| STEP-109 main 更改没有改 Gate | V23 已有 STEP-109 修复 | 需下一正式发行 GSRL 和设备验收(本 Step 禁止) |
## 8. 必选结论矩阵
| 维度 | 明确判定 |
|---|---|
| Placement 事件来源能否定位 | **条件性可区分**(Source/Layer 可追踪,global Placement 本身不标 per-Segment 责任) |
| 能否证明 R0 视觉最终未变化 | **需扩展内部安全证据** |
| 现有 Gate 性质 | **正确的设计保守限制**,不是已证明违反 STEP-102 语义的 Bug |
| 缓存失效主要责任 | **多因素**:Native Placement 缺少 per-R0 结果证据 + Native 全局 Gate,受 Collision 语义和 Host Style/Annotation 组合影响 |
| 下一步 | **Native 内部诊断补强 → 安全判据设计 → 另行授权真机对照**;若改公共接口/Host,则交 Integration 协商 |
## 9. 依赖顺序与最小后续验证
1. 在不影响生产安全和公开 API 的前提下,记录 placement 重算触发原因与时间,拆分 Source/Layer/Camera/Tile/transition 事件;
2. 设计 per-R0 Placement **结果等价安全证据**,至少包含 R0 Symbol placed IDs、位置、可见性、text/icon opacity/fade、Collision group、Tile/glyph/source 版本、Camera 和合成顺序;先审查其不变量;
3. 只有确证 R0 不受 C1 影响时,才另立有边界的 Native Gate 优化任务;不能先去掉 placementChanged;
4. 再调查 Fast Path 单 C / unsupported / pending 等 Gate;进行等条件 V23 与后续版本 CPU/Frame/Visual A-B;
5. 如需要 NavSea 配合改 Style/Annotation 或改公共 Contract/API,形成 Integration 阻塞请求,不在 STEP-110 擅自实施。
**调查状态:PARTIAL。** 已完成静态因果审计、源码版本对照和 STEP-106 历史证据核对;缺少生产 R0 per-Symbol 等价、事件逐帧对应和 CPU 因果实测。这是安全证据边界,不是“R0 已修好”“缓存已命中”“CPU 已下降”。

View File

@@ -0,0 +1,436 @@
# RenderSegment 多 Retained 段全局 Placement 门槛——粒子管理员审查说明 v1
日期:2026-10-07
性质:调查结论 / 修改目标 / 管理员审查输入,不是直接实施任务书。
目标读者:Particle Shared Core / Renderer 维护者。
## 1. 本文要解决的问题
NavSea 当前已经能通过公开 `RendererRuntime.setRenderSegmentPlan()` 配置任意连续的 `RETAINED / CONTINUOUS` Segment,且 `.19 / .20` 的 RenderSegment 实现已经具备 source-local revision / readiness / cache diagnostics 等基础。
但 NavSea 真实运行验证发现:当存在多个 Retained Segment 时,一个与某 Retained Segment 无直接关系的全局 placement/fade 状态,仍可能把所有 Retained Segment 同时打回 fallback,导致多 Segment 不能发挥设计目标。
本文希望 Particle 管理员审查并决定:怎样把剩余的 placement/global fallback 门槛收敛到正确粒度,同时保持 fail-closed、Style Z-order、跨平台语义和 C-001 边界不变。
## 2. 正式边界
跨项目 Contract 仍为 `C-001`。
本问题属于 Particle Renderer 内部实现优化,目标上不要求新增 NavSea 私有 Native 通道,也不要求 Particle 理解 AIS、Route、Weather、Alarm 等 NavSea 业务语义。
NavSea 仍只负责:
- 按 Style Z-order 选择 Segment 边界;
- 通过稳定公开 API 提交 RenderSegmentPlan;
- 读取 diagnostics;
- 决定天气等业务能力何时显示。
Particle 仍负责:
- Segment resolve / cache / invalidation / renderer execution;
- placement / transition / fading / source dependency 的正确性;
- GPU resource 和 stale-cache 安全;
- Android / iOS 共用的 Shared Core 语义。
本修改若不改变公共 API、生命周期、错误语义或双方职责,不需要升级 Contract。
## 3. 当前 Mac 基线需要管理员先确认
本次只读检查发现:
- `/Users/tei/dev/MapLibreParticleLab`
- branch: `particle/main`
- HEAD: `750c14f52708d7dc93d8eb6ec2b21112185afd5c`
- `/Users/tei/dev/MapLibreParticleLab-release-v19`
- branch: `release/particle.19`
- HEAD: `6b48f905d2d17a8271b6ff883e682db7bc45dcd3`
- `/Users/tei/dev/MapLibreParticleLab-release-v20`
- branch: `release/particle.20`
- HEAD: `53a844e81849f254ba2d169c2d875dbd862cc7d5`
- `/Users/tei/dev/MapLibreParticleLab-step101`
- branch: `step101-render-orchestrator-teardown`
NavSea 当前实际消费的是 `.19` 行为;本次检查确认 `release/particle.19` 与 `release/particle.20` 在本文相关 placement gate 代码上相同。
注意:当前 `particle/main` 与 `release/particle.19/.20` 的 RenderSegment 执行细节存在差异,因此管理员开始修改前必须先明确正式修复基线,不要把 main 与 release 分支默认视为完全一致。
## 4. NavSea 当前为什么需要多 Segment
NavSea 当前为了避免旧的 retained fallback,采用保守的两段方案:
```text
R0 [0,31) RETAINED
C0 [31,114) CONTINUOUS
```
C0 从 `rn-weather-color` 开始一直覆盖到 Style 尾部。
真实 diagnostics 已确认该 Continuous 尾段包含天气之后的大量运行时对象,包括 AIS / ownship / navigation / alarm 等 source/layer。
这能保护 R0 底图复用,但代价是:天气粒子需要持续 repaint 时,天气以上整个 Style 尾部都留在 Continuous 区域。
RenderSegment 的原始设计目标本来就是允许:
```text
R0 → C0 → R1 → C1 → R2 ...
```
并严格保持 Style Z-order,不把业务语义写进 Renderer。
## 5. NavSea 真实多段实验 A
第一次临时配置:
```text
R0 [0,31)
C0 [31,79)
R1 [79,98)
C1 [98,114)
```
Native 接受该配置:
```text
configured=true
valid=true
segmentCount=4
retainedSegmentCount=2
continuousSegmentCount=2
coverageComplete=true
fallback=false
```
解析结果:
- R0: `background` → `place-labels-land-ja`
- C0: `rn-weather-color` → `ownshipRangeLabelLayer`
- R1: `routeLineActiveShadowLayer` → `planSimBoatLabelLayer`
- C1: `crossCursorGotoLineLayer` → `chartDangerScanCenterLineLayer`
R1 当时依赖:
- `navigationSource`
- `ownshipTrackSource`
- `planSimRouteSource`
结果 R1 为 `source-fading` fallback,`reuse=0`。
这个结果本身还不能证明 placement 问题,因为 R1 确实包含活动 source。
## 6. NavSea 真实多段实验 B:把 R1 收窄到 idle PlanSim
第二次把 R1 收窄为只有 PlanSim Route 的 10 个 layer:
```text
R0 [0,31)
C0 [31,88)
R1 [88,98)
C1 [98,114)
```
R1:
- start: `planSimRouteShadowLayer`
- end: `planSimBoatLabelLayer`
- dependent source: 仅 `planSimRouteSource`
- 当时 PlanSim 为 idle。
5 秒早期诊断仍有 `source-fading`,所以继续等待到约 20 秒稳定态。
20 秒稳定态关键结果:
```text
fadingSourceIds = []
```
R0:
```text
sourceReady=true
sourceStable=true
cacheState=fallback
build=0
reuse=0
invalidation=306
lastInvalidationReason=static-placement
finalDecision=fallback
```
R1:
```text
sourceReady=true
sourceStable=true
cacheState=fallback
build=1
reuse=0
invalidation=306
lastInvalidationReason=static-placement
finalDecision=fallback
```
这说明稳定 source 已经不是阻塞因素;两个 Retained Segment 被同一个 `static-placement` 门槛同步打回 fallback。
## 7. release/particle.19 / .20 当前代码事实
### 7.1 per-segment source locality 已经存在
`.19 / .20` 在 `renderer_impl.cpp` 中会对每个 Retained Segment 单独计算:
- `retainedSegmentSourceState(begin,end)`
- source-local revision
- source `ready / stable`
- `retainedSegmentIsStable(begin,end)`
- `segmentSupportsCache[]`
- 每个 cache 独立 `evaluate()`
- 每段独立 diagnostics / finalDecision
`render_orchestrator.cpp` 中 `retainedSegmentSourceState()` 已通过 RenderSegment dependency 只检查该 Segment 相关 source 的 loaded / fading 状态。
这部分方向是正确的,不应回退到全局 source invalidation。
### 7.2 剩余 placement gate 仍是全局聚合
`.19 / .20` 的 `renderer_impl.cpp` 仍先对全部 Retained Segment 聚合:
```cpp
bool retainedStaticUsesPlacement = false;
...
retainedStaticUsesPlacement =
retainedStaticUsesPlacement ||
orchestrator.retainedRangeUsesPlacement(...);
const bool retainedPlacementStable =
!retainedStaticUsesPlacement ||
(!renderTreeParameters.placementChanged &&
renderTreeParameters.symbolFadeChange == 0.0f);
```
然后每个 Segment 的 `segmentSupported` 又共同依赖同一个 `retainedPlacementStable`。
因此只要任一 Retained Segment 使用 placement,并且当前全局 frame 有 `placementChanged / symbolFadeChange`,所有 Retained Segment 都可能失去 cache eligibility。
### 7.3 full CPU mixed execution 已有基础,但仍有 globalLiveFallback
`.19 / .20` 已有 `visitRenderSegmentExecution(...)`,理论上可以:
```text
R0 reuse
C0 live
R1 fallback/live
C1 live
R2 reuse
```
但是进入该混合执行前仍有:
```cpp
const bool globalLiveFallback =
...
!retainedPlacementStable ||
orchestrator.retainedGlobalLightTransition();
```
`!retainedPlacementStable` 会直接导致:
```text
drawableOpaquePass(0, layerCount)
drawableTranslucentPass(0, layerCount)
```
即整帧退回全 live,绕过 per-segment mixed execution。
这与 NavSea 实验中 R0/R1 同时出现 `static-placement` fallback 的现象一致。
## 8. 希望管理员审查的核心修改目标
目标不是简单“关闭 placement 检查”。
目标是:
> 当 placement / symbol fade 只影响某些 Segment 时,不应让与其无关、且自身内容/依赖/相机均稳定的 Retained Segment 一起失效。
期望行为示例:
```text
R0:不含 placement layer,source stable
C0:Weather continuous
R1:含 placement 或当前 placement unstable
C1:AIS / Alarm continuous
R2:不含 placement layer,source stable
期望:
R0 = reuse
C0 = live
R1 = fallback/live
C1 = live
R2 = reuse
```
而不是:
```text
R1 placement unstable
→ global retainedPlacementStable=false
→ R0/R1/R2 全部失效
→ 整帧 full live
```
## 9. 建议管理员重点评估的实现方向
以下是审查建议,不是强制实现方案。
### 9.1 先把 placement eligibility 变成 per-retained-segment
对于每个 Retained Segment 分别判断:
- 该 Segment 是否实际 `usesPlacement`;
- 该 Segment 的 layer transition / needsRepaint;
- 该 Segment 的 source readiness / fading;
- placement transition 是否确实会改变该 Segment 的最终像素;
- 是否存在必须全局 fail-closed 的 placement 共享状态。
至少对于 **完全不含 placement layer 的 Retained Segment**,应评估是否有理由继续受全局 `placementChanged / symbolFadeChange` 阻塞。
### 9.2 审查 `globalLiveFallback` 中的 `!retainedPlacementStable`
建议确认是否可以:
- 从 globalLiveFallback 移除纯 placement 条件;
- 让 placement 结果进入每段 `segmentSupported / decision`;
- 真正使用现有 `visitRenderSegmentExecution()` 做 mixed retained/live。
真正全局的条件仍可保持全局,例如:
- 3D;
- Debug 模式;
- MapMode 不支持;
- sprite/image/global resource 未就绪;
- global light transition(若确实会改变所有段);
- context/resource 失效;
- 无法证明安全的全局状态。
### 9.3 不要破坏已有 source-local 机制
`.19/.20` 已有 `retainedSegmentSourceState()` 与 dependency-scoped revision/readiness。
目标应是在此基础上继续收窄 placement/global gate,而不是重新把 source/tile mutation 退回“所有 Retained 一起 invalidate”。
### 9.4 placement 本身若无法简单 local 化,应明确分层
管理员需要判断 MapLibre placement 是否存在以下区别:
1. Segment 完全不使用 placement:理论上是否可忽略全局 placementChanged?
2. Segment 使用 symbol placement,但其相关 source/layer 没变化:是否仍必须 invalidate?
3. cross-source collision / shared collision index 是否会让某 Segment 的符号像素受其他 Segment placement 影响?
4. `symbolFadeChange` 是全局时间量还是可以按 dependency/range 推导?
5. 是否需要一个 per-segment placement revision/fingerprint,而不是 bool?
如果 2/3 无法安全局部化,也至少应优先解决第 1 类,让“无 placement Retained Segment”不被无关 placement 拖死。
## 10. 不能牺牲的正确性约束
任何优化都必须保持:
- Style Z-order 是唯一顺序事实;
- Segment 不重新编号 Layer 的全局 ordinal;
- opaque/translucent/depth 语义不变;
- retained cache 不得显示 stale 像素;
- Camera / viewport / pixelRatio 变化仍正确 invalidation;
- Style reload / topology change 正确释放旧 cache;
- Android / iOS 使用同一 Shared Core 语义;
- 不把 AIS / Route / Weather 等业务名称写进 Renderer;
- 不要求 NavSea 手工 invalidate Native cache;
- 不新增“只为 NavSea 绕过 Renderer”的私有 API。
## 11. 建议新增/补强的 Core 测试
至少建议覆盖:
### Case A:无 placement 的 R0 + 有 placement 的 R1
- R0 stable;
- R1 发生 placement transition;
- 期望 R0 继续 reuse;
- R1 单独 fallback/live;
- 最终 Z-order 正确。
### Case B:两个 Retained 都稳定
- R0/R1 均 build 一次;
- 后续 reuse 持续增长;
- Continuous 段持续 live。
### Case C:单一 source fading
- fading source 只属于 R1;
- R1 fallback;
- R0 不 invalidation。
### Case D:Continuous source 高频 mutation
- mutation 只属于 C1;
- R0/R1 retained revision 不变化;
- cache 可继续 reuse。
### Case E:真正全局状态
- global light transition、3D、Debug、context/resource failure 等;
- 继续 fail-closed;
- 不为追求 reuse 放宽安全边界。
### Case F:topology 变化
- R-C-R-C → R-C;
- 旧 retained texture/compositor 正确释放;
- 不 composite stale resource。
## 12. Diagnostics 验收要求
修复后 diagnostics 必须能证明“局部失败不扩散”。
NavSea 重新做 R-C-R-C 真机测试时,至少需要看到类似:
```text
R0:
sourceReady=true
sourceStable=true
cacheState=reuse
reuse 持续增长
R1:
finalDecision=fallback
lastFallbackReason=static-placement
C0/C1:
正常 live
```
关键验收不是 Fast Path hit。
多 Continuous Segment 本来就可以继续走 full CPU mixed renderer;即使 `fastPathHit=0`,只要稳定 Retained 段能够独立 reuse,优化就是有效的。
## 13. 与 NavSea 最新 Trace 的关系
`Trace-20261007T224751.json` 是 UDP + 天气 UI 的 DevTools Trace。
该 Trace 中约 11–13 秒和 21–23 秒出现两组很高的 JS 峰值;调用栈显示峰值主要落在 React root work 与 `WeatherForecastStrip / WeatherTable`,其中 `WeatherTable` 两次重 render 分别约 0.91s / 1.07s,AIS decoder 并不是这两个峰值的主要执行栈。
代码同时显示 `WeatherForecastStrip` 会订阅 `smoothGPS`,并用经纬度 `toFixed(2)` 形成天气采样位置 key;但当前尚未完成对这两次具体 WeatherTable render 触发源的一一证明,因此本文不把“约 10 秒”直接归因于 GPS 跨格或任何单一 NavSea 机制。
这些 JS/UI 峰值属于 NavSea 侧独立调查项,不应作为 Particle RenderSegment 修改的直接证据。
Particle 本次应以 **RenderSegment diagnostics 的 R0/R1 `static-placement` 同步 fallback** 为主要证据。
## 14. 管理员需要给出的结论
请管理员审查后明确回答:
1. `.19/.20` 当前全局 `retainedPlacementStable` 是否确实过度保守?
2. 对“不含 placement layer”的 Retained Segment,是否可以安全解除全局 placementChanged/symbolFadeChange 阻塞?
3. `globalLiveFallback` 是否应去掉 placement 条件,转为 per-segment decision?
4. 若 placement 必须部分全局,最小安全局部化边界是什么?
5. 正式修改应基于 `release/particle.20`、合并回 `particle/main`,还是先在独立工作树完成?
6. 是否需要新 ADR/RFC?初步判断:若公共 API/C-001 语义不变,只是 Renderer 内部优化,可不新增跨项目 Contract;若管理员认为会改变 RenderSegment 公共语义,再交回 Integration 决定。
## 15. 当前 NavSea 状态
NavSea 临时 R-C-R-C 实验已经撤回。
当前恢复为原有保守方案:
```text
R0 [0,31) RETAINED
C0 [31,114) CONTINUOUS
```
因此管理员可以先只做 Native/Core 审查,不需要 NavSea 同步修改。
等 Particle 给出明确方案和可验证版本后,再由 NavSea 重新选择真实 Style 边界并做 R-C-R-C / R-C-R-C-R 真机验证。

View File

@@ -0,0 +1,285 @@
# STEP-095 V13 独立严格代码与性能调查 v1
日期:2026-09-25(Asia/Tokyo)。性质:独立静态审查;只写指定中文文档,不修复源码,不运行构建、测试、codegen、安装或设备操作。
## 1. 执行摘要与结论边界
V13 的通用分段解析、Style 顺序及保守 Fast Path 门控有源码依据,但不能据此批准 GPU 正确性或性能。本轮先检查 V12→V13 diff、真实调用链和生命周期,再阅读旧报告逐项挑战。新增优先问题为:离屏目标切换未重置 CPU stencil 掩码记录;Metal depth/stencil 清除契约未完整下传;LayerGroup 抛错后 retained build 仍可能标成功;V4 PLAY 缺少生产时钟推进调用。前三项的实际像素、故障注入和设备行为仍需验证。
旧 F01/F02/F03/F04/F07 的成本路径成立,但没有本轮 profiler 数据证明热点排序、OOM 或收益。F06 多 Continuous 退出静态 CPU Fast Path、F08 保守全局失效属于显式设计限制。F09 可给出计数跨 topology 错归的具体链,但不证明 stale texture 被复用。F05 的 Host `.10`/native `.13` 错配成立,历史 TS2307 不能仅归因于版本号;F10 只证明协调文档滞后。
新增成本还包括 V4 全网格物化、UI 线程解码、普通粒子 Metal 离屏等待及 drawable 重建。未找到足以定 P0 的证据;不宣称不存在其他缺陷。调查内容完成,Git 交付结果见阶段记录的收尾条目。**V13 产品运行态不判 PASS:本轮所有构建、测试、设备、性能验证均 NOT RUN。** RDC 仍须独立复核和验证。
## 2. 冻结基线、环境与范围
| 项目 | 核实结果 |
| --- | --- |
| 机器/目录 | `Mac-mini-tei.lan`,Darwin x86_64;`/Users/tei/dev/MapLibreParticleLab` |
| 分支/起始 HEAD | `particle/main`;`05878d662308b9a92c20c433c3cd2943d74ae718` |
| 起始状态 | 工作树 clean;子模块未见偏离记录提交的标记 |
| 远端 | cached origin 与实际 `git ls-remote` 都是上述 HEAD;origin 为 `https://git.eyaku.jp/tei/MapLibreParticleLab/` |
| 冻结源码 | `ee0b89e711bf01335b3aa71d36def785cd97fec3` |
| V13 功能/旧报告归档 | `016fd1fff6a33cf795c10d4f5c5413d308383677` / `45c4dca84928a57a2cf0226737c34cabd7fbfbfb` |
| V12 比较点 | tag 解引用提交 `7eaff6e12360e26591dceb5e011129eeab7ba4c7` |
| V13 tag | `.13` tag 对象 `bccbceddec70f7a2b6362bce653f9c577aa2db1d`,解引用为冻结源码,远端一致 |
| manifest sourceCommit | `ddf20dec9fe9cd05b77274827b6825829500dd5a`;至冻结提交为发布元数据收尾,生产源码一致 |
| 模型记录 | 用户在本次同一会话恢复指令中明确指定 `gpt-6-astra high`,与任务要求一致;这是当前会话的用户指定记录。未获得可独立核验的底层启动参数,不声称已独立验证实际模型/effort |
冻结源码至起始 HEAD 仅新增旧审查报告及 STEP-095 任务书;下文源码行号按冻结源码,与本轮起始工作树对应。V12→V13 整体 diff 为 45 文件、2393 行增加/203 行删除(含文档),主要新增 segment/parser/resolver、runtime diagnostics、renderer/orchestrator 调度与平台桥接,并非粒子数值 shader 的整体重写。
已读取 AGENTS、PARTICLE_BASELINE、项目规范、项目总则 README 所列当前有效规则及基线 v2,交叉阅读 STEP-088~094 的任务/设计/阶段/报告相关内容。总则仍含早期 `.3` 和 Linux 主线描述,本任务以 Mac `.13` 冻结点为准,不重写历史文档。官方 Core 基线 `18545ca10d296704b2cba675b182ff2c8da24588`,Android 官方 13.4.1、iOS 官方 6.28.0。
### 2.1 发布坐标(仅核对仓库记录)
来源:`release/maplibre-particle/release-manifest.json` 与 `release/maplibre-particle/ios/release-manifest.json`。
| 产物 | 坐标 | manifest 大小/SHA-256 |
| --- | --- | --- |
| RN | `@navsea/maplibre-particle-rn@13.4.1-particle.13`,MLRN 11.3.6 | 25,966 bytes;`3a726db06cb967fe56de77ca4a72feed98cd642a2d1b73fd51b1531d15ef7539` |
| Android OpenGL | `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.13` | 169,786,873 bytes;`ddf1deced00cc963d1d625c73c68c449232602113a1490349e69100473dda82b` |
| iOS Metal | `6.28.0-particle.13`,MapLibre XCFramework/SPM exactVersion | 8,418,198 bytes;`8d47d9f6d44479aff51299c213887bd9be204da320da4fd4fff15dd064930556` |
manifest 的 `PUBLISHED_PUBLIC_READBACK_PASS` 是 HISTORY。本轮没有下载、重算产物散列、重建或核实设备实际链接版本;文档提交不是新 V13 发布。
### 2.2 调用链和未覆盖范围
已沿 segment JSON/plan → runtime → orchestrator/renderer → cache → PaintParameters/GL/Metal target,及 canonical/QBIN/profile → Store/Clock/projection → RasterArraySource immutable Impl → WeatherColor/Particle RenderLayer → Field/State/Trail GPU 追踪;检查 Android Java/Kotlin/JNI、iOS ObjC++、RN typed API/JSON Spec、Host package/lock/plugin及 Integration 只读状态。阅读 resolver/cache/diagnostics/field/decoder/reseed 测试源码,不等同执行测试。
未逐行形式化验证全部官方 renderer/driver、Vulkan、业务 provider/HTTP/Route/ETA或所有并发交错;未使用设备、capture、sanitizer、fuzzer。此报告不是全仓无缺陷证明。
## 3. Finding 总表
D=源码可复核缺陷路径;C=源码确定成本;R=后果需运行验证;E=环境/文档偏差。置信度均指源码链而非事故发生概率。每项详细位置、修复与验收见后文。
| 编号 | 严重度/类别/置信度 | 触发与影响 | 旧报告关系 |
| --- | --- | --- | --- |
| N01 | P1/D+R/高 | 相同 tiles 跨 target,CPU stencil 映射复用,后段可能被裁掉 | 新增 |
| N02 | P1/D+R/高 | Metal retained depth/stencil clear 未完整转换,模拟器 attachment 缺口 | 新增 |
| N03 | P1/D+R/高 | group 绘制抛错被 catch,仍标 cache 成功,可能持续复用残图 | 新增 |
| N04 | P1/D/高 | V4 只 PLAY 不继续 set time,生产时钟无推进调用 | 新增 |
| N05 | P2/D/高 | JSON NUL/重复 strategy 被接受,严格解析失真 | 新增 |
| N06 | P2/D+R/高 | 结构合法但数值非法的场先入库,物化失败后可用投影被置空 | 新增 |
| N07 | P2/D/高 | 同 view 重复 attach,listener 和持有引用递增 | 新增 |
| C01 | P1/C/高 | full diagnostics 锁内深复制进入 fast/render | F01 |
| C02 | P1/C+R/高 | Metal retained build 与普通 State/Trail target 等待 | F02,新增扩大范围 |
| C03 | P1/C/高 | 内容 dirty 释放同尺寸可复用 GPU allocation | F03 |
| C04 | P1/C+R/高 | 多 R 全视口资源无总预算;Store 也缺 fieldKey 总预算 | F04,新增场预算 |
| C05 | P2/C/高 | ordinal 线性前缀遍历,fast update 仍扫全部 items | F07,新增扫描 |
| C06 | P1/C+R/高 | 同时间/mix-only 整网格物化;提交链可占用 UI | 新增 |
| C07 | P2/C/高 | tick 重建 drawable/attributes/tweaker,窗口切换复制场 | 新增 |
| O01 | P2/D/高 | 新 topology 先发布,旧 cache 析构向新 index 计数 | F09 |
| E01 | P1/E/高 | Host RN `.10` 与 Android 插件 `.13` 错配 | F05 |
| E02 | P3/E/高 | Integration 仍旧状态,实际接入 UNKNOWN | F10,降级 |
## 4. 新增正确性和生命周期发现
### N01:CPU stencil 记录跨 framebuffer 复用
证据:`src/mbgl/renderer/renderer_impl.cpp:798-847,938-945`;`src/mbgl/renderer/retained_underlay_cache.cpp:304-319`;`src/mbgl/renderer/paint_parameters.cpp:185-201,365-374`;`src/mbgl/gl/layer_group_gl.cpp:67-98`;`src/mbgl/mtl/tile_layer_group.cpp:118-119`。
最短链:同一个 PaintParameters → 第一个 R 清 stencil、写 mask 并记录 tileClippingMaskIDs → 第二个 R 新 target/清 stencil → tileIDsCovered 为真而跳过写 mask → draw 仍 Equal 非零 ID。CPU 记录属于前一 target,后一 GL target 实际为零。main buffer 清除后也未清 CPU 记录;updateStencilBufferAvailability 不是映射重置。
触发用例:同 vector source、同 tile 集合的两个 fill 分置相邻 R-R,首次都 build;或 R build 后同 tiles 的 C 绘入主目标。tile 集合变化可触发清记录,所以不是所有画面必坏。V13 任意 R-R 扩大暴露面,V12 多 target 也应回归,不能断言只由 V13 引入。
最小修复:target/pass 切换时明确失效 CPU mask/编号,或按 target generation 隔离,并保证 GPU clear 一致。验收:GL/Metal cache OFF 对照,同 source R-R/R-C-R 的首帧、rebuild、reuse 像素和 stencil capture。缺口源码已确认,实际像素 NEEDS_RUNTIME。
### N02:Metal depth/stencil 描述未完整下传
证据:`retained_underlay_cache.cpp:304-310` 请求 clearDepth=1、clearStencil=0;`src/mbgl/mtl/render_pass.cpp:13-38` 只转换 clearColor;`src/mbgl/mtl/offscreen_texture.cpp:32-83,139-148` 绑定附件但该路径未设置 depth/stencil clear/load/store。模拟器不创建 stencilTexture,也未将 combined depth/stencil texture 绑定 stencilAttachment;格式选择见 `src/mbgl/mtl/texture2d.cpp:99-104`。
触发:Metal retained tile/depth 绘制,尤其首次 target、simulator。源码确认契约缺口,但未测驱动默认内容/validation 行为,不宣称所有真机黑屏。isRenderable 仅验证 color,depth/stencil create 在 bind,早期就绪判断不能证明所有附件就绪。
最小修复:完整翻译 descriptor、正确绑定 combined attachment,并验证所有附件后允许 build;保持官方接入薄。验收:Metal API Validation、真机/模拟器 capture 核对 attachment/load/store/clear,故障注入验证安全 fallback。NEEDS_RUNTIME。
### N03:绘制异常未阻止 cache 发布为成功
证据:`src/mbgl/renderer/render_orchestrator.hpp:156-178` 的 visitor catch 只通知 observer;`renderer_impl.cpp:798-824` 随后无错误结果判断,finishBuild/markBuildSucceeded。
触发:R 的一个 layerGroup.render 抛错,observer 正常返回,其他 group 继续,残缺图像可变 valid 并在稳定帧 reuse。反例:资源准备阶段已有失败回退(`retained_underlay_cache.cpp:240-255`、`renderer_impl.cpp:662-677`),问题不等于所有资源失败都没有 fallback。
最小修复:build 汇总明确成功状态,失败 target 不发布/复用,可恢复错误进入完整渲染或约定安全失败。验收:第二个 group 故障注入,确认 successful build/reuse 不增长、无 stale composite、恢复和资源释放正确。NOT RUN。
### N04:V4 PLAY 没有生产时钟推进
证据:`src/mbgl/style/sources/raster_array_source.cpp:453-470`;`src/mbgl/particle/field/field_runtime.cpp:196-220`;`src/mbgl/renderer/layers/render_particle_validation_layer.cpp:1174-1189,1276-1278`。生产树 src/include/platform/packages 中 advanceForecastClock 仅见声明和定义,无调用。Android `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java:225-242`、iOS `platform/ios/src/MLNParticleFieldRuntime.mm:147-165` 的 wake 只 repaint。
最短链:设一次 requested time → PLAY 改 clock state → repaint/particle cadence → renderer 读取不变的 immutable projection.mix。粒子运动不能证明预报时间在播放。Host 每次显式 set time 可绕过,但不代表 PLAY 本身有效。
最小修复:在既有 map/source 所属线程接入唯一单调时钟,明确速度、pause/resume、后台和销毁语义,不建立第二套时间真相。验收:仅设一次时间,PLAY 的 .5/1/2 倍 mix、PAUSE、跨窗口和恢复;`test/particle/field_runtime.test.cpp:272-310` 手动 advance 单测不覆盖生产接线。本轮 NOT RUN。
### N05:Segment JSON NUL 截断与重复键
证据:`src/mbgl/renderer/render_segment_json.hpp:16-49`。Parse 用 C 字符串,strategy/startLayerId 用 GetString 未带 GetStringLength;`"RETAINED\u0000x"` 截成 `RETAINED`,`"boundary\u0000x"` 可解析到另一真实 ID。segment 两个重复 strategy 满足 MemberCount<=2,白名单不检唯一性,读取首值。
触发可由 primitive JSON/native 入口构造,TS typed API 不替代校验。普通未知 strategy/schema、missing/reversed 仍会 invalid/fallback。最小修复:长度感知解析/构造,拒绝 NUL/重复键,保持 invalid 清旧 plan 语义。验收:上述 payload、raw NUL 后缀、正常 unicode、未知字段、invalid→valid及两桥接错误归一化。NOT RUN。
### N06:数值非法的场在入库后才被拒绝物化
证据:`src/mbgl/particle/ingress/qbin_v2_decoder.cpp:41-73` 校验结构、CRC、scale/offset但不扫描 Float32 样本;`src/mbgl/particle/field/field_profile_validator.cpp:77-166` 匹配契约;`field_projection_materialization.cpp:38-67,97-135` 才检查解码数值;`raster_array_source.cpp:422-440,496-522` 先 submit,再物化,失败 next 对应投影为空。
触发:已有绑定字段,同时间更高 revision、CRC 正确、valid=1、样本 NaN/Inf 的 QBV2。结构/契约可接受,替换旧帧后物化失败,但 submit 返回 AcceptedInserted/AcceptedReplacedRevision。有限 scale 乘法溢出也应纳入验证。此处不声称 NaN 上传 GPU——物化拒绝正是反例;缺陷是 accepted/可渲染语义及旧状态保留脱节。
最小修复:明确 ingress 数值约束,入库前校验或事务式验证,保留合法无效 cell/通用未绑定 plane 语义。验收:好旧帧→坏高 revision,检查返回值、Store/投影保持、noData/validity、极值及恢复。NOT RUN。
### N07:Android attach listener 积累
证据:`packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt:30-63`。每 attach 新增捕获 concrete map 的 listener;显式 detach 只 dispose runtime,view detach 回调也不移除 listener。
同一个存活 view 重复 attach/detach,listener/引用及回调为 O(k)。未证明 view 回收后永久泄漏或 UAF;每次重新解析 view、没有全局 reactTag registry 是正面约束。最小修复:view/map 单一 listener、attach 幂等、明确 remove 生命周期。验收:同 view 100 次、换 view、卸载/后台恢复,listener/dispose/存活对象回基线。NOT RUN。
## 5. 性能、资源与观测
### C01:完整 diagnostics 深复制进入热路径
证据:`renderer_impl.cpp:198-208,500-505` → `retained_underlay_runtime_state.hpp:243-254` 锁内复制 styleLayers、segments.layers、字符串。成功 fast frame 可两次,不是所有帧固定两次。成本 O(L+S),锁竞争程度待测。修复:一致的轻量热快照与完整外部 pull 分开,不能无同步拆字段。验收:稳定 30/60 FPS 的完整快照次数应随外部 pull,测 alloc/frame、锁等待、CPU;无收益百分比。
### C02:Metal 等待不限于 retained rebuild
证据:`retained_underlay_cache.cpp:316-319` → `src/mbgl/mtl/command_encoder.cpp:29-31` → `mtl/offscreen_texture.cpp:86-91` commit/waitUntilCompleted,失效 R 逐段同步;稳定 reuse 没有 retained build wait。
新增链:`src/mbgl/renderer/render_target.cpp:71-119` 普通 target 也 present;`render_particle_validation_layer.cpp:1990-2111` tick 注册 State/Trail 写目标,初始化可能更多。因此 retained 稳定也可能有多个普通粒子 wait,属继承成本。修复前先 capture buffers、wait 栈及依赖,再设计有序异步提交,不能直接删 wait。验收:State A/B 无别名,Trail/composite 顺序与轨迹一致,CPU wait/GPU frame time 成对比较。NEEDS_RUNTIME。
### C03:内容失效与 allocation 销毁耦合
证据:`retained_underlay_cache.cpp:161-237` revisionChanged 同时 invalidate/release;`:240-255,365-374` 重建/释放 texture/compositor。触发为内容/相机等 revision 改变,不是稳定帧无条件分配。修复:contentDirty 与尺寸/context/格式的 resourceInvalid 分开,同尺寸有效 context 保留 allocation。验收:mutation、camera、resize、context loss 分开统计 build/create/release/peak;先保证 N01/N02 clear 正确。耗时收益待测。
### C04:多段 GPU 与多字段 CPU 无总预算
证据:`renderer_impl.cpp:506-524`;`retained_underlay_cache.cpp:240-255`;`src/mbgl/gl/offscreen_texture.cpp:18-35`、`mtl/offscreen_texture.cpp:20-55`。每 R 全视口 color/depth/stencil;RGBA8 color 理论下界 R×W×H×4 bytes,另有附件、alignment、主 framebuffer、State/Trail及延迟释放,不能把逻辑 release 当物理显存回收。
新增:`src/mbgl/particle/field/field_runtime.cpp:223-230` 对新 fieldKey 建 series;`include/mbgl/particle/field/field_runtime.hpp:115` 每 series 默认容量并非总 key/总 bytes 上限。QBIN 单文件64 MiB/解码32 MiB 上限也不约束累计持有量。
修复:map 预算、分配前溢出安全估算、超限明确 full-render/拒绝策略,不合并相邻 R、不淘汰仍绑定窗口。验收:R×viewport×字段数阶梯、allocation failure/context loss、30分钟以上 peak/回收。OOM/泄漏未证实,NEEDS_RUNTIME。
### C05:ordinal 前缀遍历和全层扫描
证据:`render_orchestrator.hpp:156-178` 的 multimap std::distance 是线性,S 次范围定位可能 O(S×G);`render_orchestrator.cpp:1202-1226` fast updateParticleLayers 仍遍历完整 items,非粒子只跳过 update;`:973-975` 按 retained 稳定性还可能扫 source fading。
修复:topology 改变时缓存 global ordinal/粒子列表,保持 generation 和原始全局深度编号。验收:固定粒子层扩大 L/G/S,测 CPU/调用次数并回归 reorder/delete。实际热点占比待测。
### C06:同时间/mix-only 仍全网格物化,UI 成本遗漏
证据:`raster_array_source.cpp:453-455,496-522` 在 sameProjection 前分别物化 color/particle;`field_runtime.cpp:196-200` 相同非零时间也返回 true;`field_projection_materialization.cpp:69-75,97-211` 分配/遍历 validity、T0/T1 scalar/vector。Wind 双绑定可重复同向量物化。mix 更新产生新 Impl/onSourceChanged,`render_orchestrator.cpp:361-365` 全局 SourceMutation 可进一步失效所有 R。
桥接:Android withMap UI 调度经 `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp:1501-1517` 同步到 Source;iOS `packages/maplibre-particle-rn/ios/WeatherRuntimeBridge.mm:105-120,235-240` 的 withMap 与 `platform/ios/src/MLNParticleFieldRuntime.mm:222-252` 也同步提交 Core。预读取文件不等于 CRC/解码/物化已经离开 UI。成本随格点/plane 增长,实际卡顿/ANR未测。
修复:相同时间早退,按端点 identity/binding 共享不可变物化、mix 单独更新;若 worker 解码,owner-thread 发布必须带 map/source generation,禁止 worker 直接改 Source。局部失效必须先有完整依赖。验收:同时间、mix-only、跨窗口、双绑定、最大网格的 CPU/alloc/UI阻塞/upload/rebuild计数。NEEDS_RUNTIME。
### C07:tick 对象重建及端点复制
证据:`render_particle_validation_layer.cpp:826-904` clearDrawables 后新 attributes/builder/tweaker;`:1668-1669,1990-2111` 更新 target 注册及 State/Trail/composite;`:657` createOrUpdate UBO 不证明跨新 drawable 复用;`:1494-1499,1538-1563` 场端点切换复制数组。
反例:State/Trail textures 和共享顶点不是每 tick 全部重建,field 复制主要在窗口边界,不能称 CPU 每帧模拟全部粒子或无条件上传场。修复:稳定 drawable 保留,仅更新 uniforms/texture,端点共享不可变存储。验收:tick/bootstrap/reseed/resize/端点轮换分别测对象、UBO、upload和峰值。NOT RUN。
### O01:旧 cache release 记入新 topology
证据:`retained_underlay_runtime_state.hpp:173-240` 先发布新 entries;`renderer_impl.cpp:500-524` 再 clear 旧 cache;`retained_underlay_cache.cpp:120-122,365-374` 析构按旧 segmentIndex 计数;runtime `:256-272` 寻找当前 entry。
旧 index 0 的 R 换成新 R,旧 release 可记入新 R;新 index 为 C/越界时 per-segment 计数丢失。不证明 GPU 未释放或旧图复用。另 runtime `:198` root fallback 只表示 resolution invalid,不能覆盖 cache OFF/unsupported/resource fallback;消费方不可读成 GPU 当前 retained 成功。
修复:计数带 cache identity/generation,聚合历史与当前段分开,明确两类 fallback。验收:真 cache R-R→C-R、相同 index 换边界、disable/restore析构计数,纯手动 counter 测试不足。NOT RUN。
## 6. 环境发现和旧 F01~F10 复核
E01 定位:`integration/react-native/MapLibreParticleRNTestHost/package.json:12`、`package-lock.json:12,1765-1766` 为 `.10`,`plugins/withParticleNativeAndroid.js:9` 注入 `.13`,当前 package/native 为 `.13`。默认干净 Host 不能证明 V13 全链。旧 typecheck exit2/TS2307/TS7006 为 HISTORY;缺模块和旧 API 是不同原因,本轮未复跑。修复方向:后续单独对齐 package/lock/native resolved version,干净安装、typecheck/codegen及设备调用;不能把临时工作树链接 PASS 当默认包 PASS。本轮不改依赖。
E02 定位:只读 `/Users/tei/dev/navsea-particle-integration` HEAD `39ad8415fbdb76c1822460bb09f3a3f35fe141e0`,`compatibility/MATRIX.md` 与 `status/` 仍含 Sept18 `.5`、iOS 等待、RN NOT_STARTED。它与本仓 `.13` 是不同时点,真实 NavSea 接入 UNKNOWN。降 P3 文档项,建议负责人以接入 commit/包/设备证据更新,不改 Core 协议;本轮不改 Integration。
| 旧项/原结论 | 当前意见 | 修正和依据 | 补证 |
| --- | --- | --- | --- |
| F01 P1 深复制 | CONFIRMED | C01,成功 fast 可两次而非每帧必两次 | alloc/锁/CPU NOT RUN |
| F02 P1 retained wait | CONFIRMED | C02,限定 build,补普通粒子 wait | 收益 NEEDS_RUNTIME |
| F03 P1 dirty 释放 | CONFIRMED | C03,稳定 reuse 不重建 | 内容/resize/context分测 |
| F04 P1 无预算 | CONFIRMED | C04,无预算不等于已 OOM | peak/压力 NEEDS_RUNTIME |
| F05 P1 Host 不可复现 | QUALIFIED | E01,错配确定,旧 TS2307非本轮且不可仅归版本 | 干净安装 NOT RUN |
| F06 P1 multi-C full CPU | QUALIFIED | 显式设计限制,建议 P2;不等于关闭 GPU retained reuse | 相同Style A/B NOT RUN |
| F07 P2 ordinal 线性 | CONFIRMED | C05,优化定位不能改全局编号 | S/G基准 NOT RUN |
| F08 P2 全局失效宽 | QUALIFIED | 保守设计,未证明 stale;未知依赖不能裁窄 | source/tile/glyph依赖 |
| F09 P2 错归风险 | CONFIRMED | O01具体先换entries后旧析构链;不证明stale texture | 真cache生命周期 NOT RUN |
| F10 P2 文档漂移 | QUALIFIED | E02降P3;不推定实际接入 | 负责人跨项目核对 |
没有为凑数将整个旧项标 DISPROVED;被否定的是“多C关闭所有GPU reuse”“计数错归证明旧纹理复用”“.10必然造成TS2307”“单测证明像素正确”等外推。旧报告未明确作这些断言之处,也不反向指控它作过。
## 7. 反例、边界和设计限制
1. `render_segment_plan.hpp:32-157` 采用完整 ordered IDs(含 hidden),边界存在且递增,全R/全C合法,相邻同策略不合并,V12经同模型映射;首段可省 startLayerId 是契约。普通 invalid 清 resolution、恢复重解析有依据。
2. reorder 不是“仅added/removed所以遗漏”:`src/mbgl/renderer/style_diff.cpp:9-42` 序列差异体现移动,`render_orchestrator.cpp:318-329` 重建 orderedLayers,未报告无可达链的 reorder bug。
3. `renderer.cpp:37-59` 在 full createRenderTree 前清 prepared tree,fast gate 检查 style/source 等输入;不能只见缓存引用便断言 UAF。context/destroy 极端交错仍需验证。
4. `renderer_impl.cpp:798-918` 保留 opaque reverse/translucent forward 与 global ordinal,compositor premultiplied alpha/depth disabled 有源码依据。但不能推导所有分段像素等价,N01/N02仍为 gate;不凭空宣称所有 alpha 顺序错误。
5. Cache OFF、不支持内容、资源准备失败已有普通路径;多 C 只退出 CPU fast,R可reuse。3D/debug/legacy/custom gate保守,不建议绕过求性能。
6. QBIN有文件/plane/cell/decoded上限、CRC/bounds/长度检查,Clock有速度/整数溢出检查;FieldSeries按时间/revision处理重复/旧版本并shared持有窗口。未找到可证实的任意写/越界链;N06是数值发布语义。旧canonical adapter已有有限性检查,不能一概否定。
7. State A/B、Trail、T0/T1和方向/速度各有路径;`render_particle_validation_layer.cpp:1671-1810` resize重建Trail、按zoom-out gesture决定reseed并保留其他状态,不代表orientation/zoom-out设备通过。nearest/linear采样及双端点validity需数值视觉基准,本轮未断言错误插值。
8. 共享海陆mask尚无验收证据:`field_projection_materialization.cpp:77-92` snapshot displayDomain为GLOBAL;`render_particle_validation_layer.cpp:278-293,1314-1324` 有synthetic coastal mask路径。不能据此证明Wave/Tide/Current共用正式Land/Ocean Mask;陆地覆盖显示不等于mask。列为架构/接入证据缺口,不擅改协议。
9. 桥接保持map-scoped,typed object经内部JSON,没有引入业务协议;普通invalid plan会关闭旧计划。符号接线不等于ABI/codegen或销毁竞态已测。
## 8. V12/V13 架构与成本比较
L=层数、G=LayerGroup数、S=段数、R=retained数、W/H=物理视口、P=格点。仅源码模型,不是测量。
| 维度 | V12 | V13 | 判断 |
| --- | --- | --- | --- |
| topology | 固定range,最多两个R | 任意独立R/C,相邻R不合并 | 扩大管理/资源上界,不自动提高FPS |
| GPU复用 | 固定retained部分 | 每R独立 | Layer fingerprint局部化,全局依赖仍保守 |
| CPU fast | 合格单连续区间 | 恰好一个C且gate满足 | 多C full CPU不等于GPU cache关闭 |
| diagnostics | 旧统计结构 | 完整Style和重复段层列表 | C01 O(L+S)复制 |
| 范围定位 | 固定数量 | S段前缀可能O(S×G) | 实际占比待测 |
| retained资源 | 至多两组 | O(R×W×H) | 物理峰值与OOM未测 |
| Metal等待 | 固定targets | 随失效R增加 | 普通State/Trail是继承成本 |
| 场/粒子更新 | 已有物化/drawable | 多数继承 | C06/C07不全归因V13 |
STEP-090 fast ON 21.7%/OFF97.0% RenderThread仅历史特定实验,不外推V13。历史一次退出及随后短时未复现,既不证明泄漏也不证明长稳。必须同设备、Style、源数据、视口、刷新率/热状态配对,先像素正确再比CPU/GPU。
## 9. 测试证据与 RDC 验证矩阵
| 项目 | 历史 | 本轮 |
| --- | --- | --- |
| STEP-094 GL构建、8 suites/43 tests | 报告PASS | HISTORY/NOT RUN;纯状态解析不能验证GPU |
| Field/QBIN/viewport69 tests、package TS | 旧审查PASS | HISTORY/NOT RUN |
| Metal-only iPhoneOS编译 | STEP-094 PASS | HISTORY/NOT RUN;不验证附件/像素 |
| Android APK、RN codegen | STEP-094 PASS,codegen用底层入口 | HISTORY/NOT RUN |
| Host工作树替换TS及旧默认失败 | 两种不同依赖环境 | HISTORY/NOT RUN,可同时成立 |
| 本轮静态/文档检查 | 不适用 | 已执行;Git结果见阶段收尾 |
以下所有运行项本轮均未执行。
| Gate | Android GL | iOS Metal真机/模拟器 | Vulkan/其他 | 证据要求 |
| --- | --- | --- | --- | --- |
| R/C/R-R/R-C-R/C-R-R/R-R-C/R-C-R-C-R | NOT RUN | NOT RUN | NOT RUN | cache OFF/ON截图、像素差、pass/ordinal |
| 同source stencil、半透明fill/line/symbol | NOT RUN | NOT RUN | NOT RUN | 首帧/rebuild/reuse、depth/stencil capture |
| hidden/reorder/delete/restore/invalid | NOT RUN | NOT RUN | NOT RUN | 下一帧无stale、revision与plan |
| OFF/资源失败/draw失败/context loss | NOT RUN | NOT RUN | NOT RUN | 失败不可reuse,资源配对与恢复 |
| PLAY/T0T1/坏数值/乱序/旧revision | NOT RUN | NOT RUN | NOT RUN | time/mix、返回码、旧窗口保持策略 |
| pan/zoom-out/pitch/resize/orientation | NOT RUN | NOT RUN | NOT RUN | state/trail/reseed及无额外field提交 |
| 30/60FPS,单C/多C、mutation | NOT RUN | NOT RUN | NOT RUN | CPU/GPU p50/p95/p99、miss/wait/alloc |
| 预算/峰值/30分钟以上长稳 | NOT RUN | NOT RUN | NOT RUN | R×视口×字段阶梯、bytes/后台/卸载 |
| attach/detach/双map/销毁中完成 | NOT RUN | NOT RUN | NOT RUN | owner thread、generation、listener |
| 干净包版本/ABI/codegen | NOT RUN | NOT RUN | 不适用 | resolved包/散列/生成接口/实际调用 |
RDC先记录HEAD/manifest/tag/构建配置;以下是既有精确命令,本轮未执行。新增缺陷尚未补测试,旧过滤集PASS不能关闭本报告。
```sh
cmake --build build-step088-opengl --target mbgl-test-runner -j3
build-step088-opengl/mbgl-test-runner --gtest_filter='RenderSegmentPlan.*:RenderSegmentDiagnostics.*:ContinuousLayerRange.*:RetainedDynamicBoundary.*:RetainedLayerGroupRange.*:RetainedUnderlayCache.*:RetainedUnderlayRevision.*:ParticleFastPath.*'
build-step088-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*'
cmake --build build-ios --target ios-sdk-static -- -quiet
```
Metal命令前确认构建目录MLN_WITH_METAL=ON、MLN_WITH_OPENGL=OFF;缺目录由RDC按规范配置,不复用未知缓存。RN先解决真实resolved `.13`前提,再按STEP-094/083现行入口typecheck/codegen。失败回传首个真实error、target、上下文和日志路径,成功仅exit/耗时/摘要。
## 10. 优先级与建议修复批次
只建议,不创建修复任务、不改源码。
| 批次 | 范围 | Gate/安全约束 |
| --- | --- | --- |
| A 正确性 | N01/N02/N03 target/Metal/失败发布,N04时钟另小提交 | 最小复现、故障注入、两平台OFF基线、PLAY端到端;不借机全面重构 |
| B 无设备可先回归 | N05/N06/N07/O01/E01 | 纯测试/桥接由RDC执行;版本可复现,不代替A的GPU gate |
| C CPU安全优化 | C01轻快照、C05ordinal/粒子列表、C06相同时间早退/端点复用 | 同步一致、Style唯一顺序、调用/分配次数证明 |
| D GPU资源/同步 | C02/C03/C04/C07 | 先profile,再预算和allocation/content分离;明确buffer依赖,不能直接删wait |
| E 较大架构 | multi-C fast、source依赖、异步大场、正式共享mask | 单独设计,未知依赖继续全局失效,不引入provider/HTTP业务调度 |
## 11. Git 与交付
仅四个允许路径:本文、`docs/50_Step记录/STEP-095_V13独立严格代码与性能审查_v1.md`、`docs/报告/STEP-095_V13独立严格代码与性能审查_Codex完成报告_v1.md`、根`粒子开发记录.md`末尾追加。创建前同主题输出不存在,使用v1,不覆盖旧报告。
起始HEAD `05878d662308b9a92c20c433c3cd2943d74ae718`。检查路径/引用/`git diff --check`后仅scoped staged/commit/push origin particle/main。实际SHA/message、push/remote/status及最终收尾提交自引用说明见阶段记录第4节;推送未完成前不写Git PASS。冻结源码至最终文档提交的差异只含既有两份文档及本轮四份文档,不含源码/shader/API/依赖/build/manifest/tag/产物。

View File

@@ -0,0 +1,231 @@
# STEP-096 V13 隔离盲审调查 v1
本报告仅使用 `/Users/tei/dev/MapLibreParticleV13Blind096` 内的当前规范、任务书与源码。发现由本轮独立阅读、追踪调用链及反例检查形成;未读取外部仓库、其他轮次审查、历史报告、会话日志、网页或既有审查结论。未构建、未执行测试、未运行设备、未修改源码。
## 1. 输入、结论边界与证据等级
- 合同:[STEP-096 任务书](../40_任务/STEP-096_V13隔离盲审_Codex任务_v1.md)。已读取根 AGENTS、PARTICLE_BASELINE、项目规范目录、总则 README 指定的当前有效文件。
- 唯一目录:`/Users/tei/dev/MapLibreParticleV13Blind096`。目标导出 SHA:`ee0b89e711bf01335b3aa71d36def785cd97fec3`,来源为任务书声明;本轮没有 Git 对象可供独立核验,未运行 Git。branch、当前 HEAD、Git clean/remote 状态均为 NOT AVAILABLE。
- [release manifest](../../release/maplibre-particle/release-manifest.json#L4) 声明 particleRevision=13、sourceCommit=`ddf20dec9fe9cd05b77274827b6825829500dd5a`,RN/Android 为 `13.4.1-particle.13`,iOS 为 `6.28.0-particle.13`。manifest 的 sourceCommit 是产物来源声明,不能替代本次快照导出 SHA,也不能据此断言导出错误。
- 当前基线总则仍包含 `.3` 发布坐标;本轮以快照源码及 `.13` manifest 描述实现。此为文档口径差异,未据此推断线上产物错误。manifest 的发布成功字样不是本轮公网回读证据。
- 当前工作机器观察值为 `Mac-mini-tei.lan`;工作日期为 2026-09-25。实际 TUI 启动命令、screen 归属、模型配置及 reasoning 设置的独立启动记录均 NOT AVAILABLE;不访问目录外补证,不声称核实了 RDC 启动要求。
“确定缺陷”指限定触发条件下源码控制流或数值关系已闭合,不表示已复现用户事故。“源码确定成本”指可证明的调用、遍历、分配或等待,不表示 profiler 排序。“设计限制”指当前能力或上界不足。所有 GPU 画面、耗时、卡顿、OOM、崩溃、性能收益均 NEEDS_RUNTIME;所有构建和测试均 NOT RUN。未发现可由本轮证据成立的 P0。
## 2. 独立架构地图
| 路径 | 实际责任和所有权 | 审查定位 |
|---|---|---|
| RN RendererRuntime → 平台 bridge → Map/Renderer → RenderOrchestrator → segment plan | Host 提交策略配置;Style 层顺序仍是绘制顺序来源,策略在该顺序上划分区间 | [RN 入口](../../packages/maplibre-particle-rn/src/RendererRuntime.ts#L22)、[分段计划](../../src/mbgl/renderer/render_segment_plan.hpp)、[Renderer](../../src/mbgl/renderer/renderer_impl.cpp#L583) |
| Renderer → RetainedUnderlayCache → gfx RenderPass/OffscreenTexture → GL/Metal | Renderer 持有各 retained 段缓存;缓存拥有离屏纹理/合成资源;同帧 PaintParameters 穿过多个目标 | [缓存生命周期](../../src/mbgl/renderer/retained_underlay_cache.cpp#L201)、[渲染调度](../../src/mbgl/renderer/renderer_impl.cpp#L798) |
| RN FieldRuntime → Android/iOS bridge → WeatherRuntime/MLNParticleFieldRuntime → RasterArraySource | bridge 定位地图并派发;Source 持有 encoded store、绑定、forecast clock;发布不可变 Impl/投影供 RenderLayer 读取 | [RN API](../../packages/maplibre-particle-rn/src/FieldRuntime.ts)、[Source 提交](../../src/mbgl/style/sources/raster_array_source.cpp#L422) |
| encoded ingress → QBIN V2/V3 → profile validator → EncodedFieldStore/FieldSeries → binding/window | magic 分派格式,校验结构和 profile;每个 fieldKey 保存时间序列;时间窗选取后物化数组 | [V3 解码](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp)、[profile](../../src/mbgl/particle/field/field_profile_validator.cpp)、[series](../../src/mbgl/particle/field/field_runtime.cpp#L144) |
| projection → WeatherColorLayer / RasterParticleLayer → GL/Metal shader | 色场与粒子分层;颜色消费标量或向量幅值;粒子持有场纹理、State A/B、Trail A/B 和 drawable/tweaker | [物化](../../src/mbgl/particle/field/field_projection_materialization.cpp#L201)、[粒子更新](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1489)、[颜色更新](../../src/mbgl/renderer/layers/render_weather_color_layer.cpp#L205) |
正常路径存在实际保护:Style diff 处理增删/重排;无效分段与不支持场景可 fallback;稳定 retained 帧可复用;解码有结构、尺寸、CRC 等检查;同时间身份冲突和陈旧修订会拒绝;超出时间覆盖范围不会凭空外推。Source 使用共享不可变帧/投影,使读者可持有旧快照;粒子 tweaker 使用 weak_ptr,不能仅因资源与 drawable 互相引用的表象断言泄漏。
固定边界的实现情况:正式 Host 提交路径未要求 Native 承担 provider/manifest 下载;旧 fixture/local FileSource 路径应单独理解。RN 是控制入口,绘制仍在 Native Core。粒子模块有独立 field/ingress/render 层,但 Renderer/RenderOrchestrator 中也存在较多集成逻辑;没有上游对照输入,无法量化“薄接入”变更比例。Wave/Tide/Current 的共享地理掩码要求见 B09。GL 与 Metal 不能由共享 C++ 自动推出行为一致;Vulkan 完整粒子链未在本轮闭合。
## 3. 独立发现总表
编号按本轮源码证实顺序,排除的候选不占编号;优先级是建议处理顺序,不是实测事故等级。
| 编号 | 优先级 | 类型 | 置信度 | 发现及作用边界 |
|---|---|---|---|---|
| B01 | P1 | 确定缺陷 | 高 | 切换离屏/主目标未重置 tile stencil 的 CPU 覆盖记录 |
| B02 | P1 | 确定缺陷 | 高 | Metal RenderPass 没有落实 gfx 的 clearDepth/clearStencil 请求 |
| B03 | P2 | 源码确定成本 | 高 | Metal 每次离屏 present 显式等待 GPU 完成 |
| B04 | P2 | 源码确定成本 | 高 | retained 内容失效同时释放同尺寸 GPU 资源 |
| B05 | P2 | 源码确定成本 | 高 | 时间更新先全量物化场,再判断投影是否相同 |
| B06 | P1 | 确定缺陷 | 高 | encoded PLAY 无生产路径推进 forecast clock |
| B07 | P2 | 确定缺陷 | 高 | 8-bit 对称场编码不能表示零,产生有符号偏差 |
| B08 | P2 | 确定缺陷 | 高 | Mercator 东向 U 位移缺少纬度修正因子 |
| B09 | P2 | 设计限制 | 高 | 当前掩码为合成 fixture,未形成共享地理 Ocean Mask |
| B10 | P2 | 源码确定成本 | 高 | 平台 UI 派发链同步解码并物化 encoded 数据 |
| B11 | P2 | 设计限制 | 高 | encoded store 仅限制每序列帧数,无全局字节/key 上界 |
| B12 | P2 | 确定缺陷 | 高 | retained 段绘制抛异常后仍可能标记缓存构建成功 |
| B13 | P1 | 确定缺陷 | 高 | 粒子场更新门槛漏掉合法时间窗内的 V/有效性变化 |
### B01 — stencil 覆盖记录没有随渲染目标切换
**触发与调用链。** 多个 retained 段,或 retained 构建后主目标绘制,复用相同瓦片集合且需要 stencil 裁剪。[Renderer buildRetainedRange](../../src/mbgl/renderer/renderer_impl.cpp#L798) 共用 PaintParameters;[beginBuild](../../src/mbgl/renderer/retained_underlay_cache.cpp#L304) 创建清 stencil=0 的离屏 pass,GL 只更新 stencil 可用性;随后[主 pass](../../src/mbgl/renderer/renderer_impl.cpp#L839) 也清 stencil=0。[updateStencilBufferAvailability](../../src/mbgl/renderer/paint_parameters.cpp#L100) 不清覆盖表;[tileIDsCovered](../../src/mbgl/renderer/paint_parameters.cpp#L147) 和[早退](../../src/mbgl/renderer/paint_parameters.cpp#L185) 只看 tile ID,不看 attachment。下一目标仍会用[旧的 Equal stencil ID](../../src/mbgl/renderer/paint_parameters.cpp#L365)。[GL LayerGroup](../../src/mbgl/gl/layer_group_gl.cpp#L67) 正常调用掩码生成,也无法绕过这个早退。
**所有权、正常/错误/销毁。** 覆盖表属于同帧 PaintParameters,stencil 内容属于每个目标附件。下一帧重建 PaintParameters 不能修复同帧跨目标;[显式 clearStencil](../../src/mbgl/renderer/paint_parameters.cpp#L156) 会重置记录,但上述目标切换没有调用。资源创建失败有 fallback,与本问题不同;正常创建成功即可触发。在 GL 已清为 0 的新目标上,旧正 ID 与实际内容不一致的路径已证实。Metal 的清屏另受 B02 影响。
**反例与边界。** 同一 attachment 内复用覆盖记录是正确优化;瓦片集合改变可能重绘掩码;无裁剪图层、没有跨目标构建或全部退回主绘制的帧不满足条件。不能据此称所有 retained 帧都缺图,实际画面 NEEDS_RUNTIME。
**安全修复/最小验证。** 在目标/attachment 世代切换时失效掩码元数据及 ID 分配状态,保留同目标复用;不可在已绘制内容中途随意清 stencil。构造 R/C/R 和 R/R 共用瓦片的 fill/line 层,逐目标检查掩码写入及截图,与关闭 retained 对照;含稳定复用、相机变化、主目标后续 live 层。
### B02 — Metal 没有传递 depth/stencil 清屏语义
**触发与证据。** retained beginBuild 明确请求 depth=1、stencil=0;[Metal offscreen bind](../../src/mbgl/mtl/offscreen_texture.cpp#L62) 新建 descriptor 并挂接 color/depth/stencil 纹理。[Metal RenderPass 构造](../../src/mbgl/mtl/render_pass.cpp#L13) 只处理 clearColor,没有读取 clearDepth 或 clearStencil;[GL 对应构造](../../src/mbgl/gl/render_pass.cpp#L14) 把这些请求交给 clear。调用链为 Renderer → cache.beginBuild → gfx descriptor → Metal RenderPass → MTL descriptor,丢失发生在最后的转换。
**所有权与反例。** 附件属于 offscreen texture,pass/command encoder 管理本次命令。创建和销毁附件均不等于每次按请求清屏。主平台 descriptor 可自行配置清屏,不能证明新建离屏 descriptor 已正确配置;无 depth/stencil 的 pass 不受这两个字段影响。未使用外部 SDK 默认值推断确定黑屏,也未把未显式设置 StoreAction 直接定性为丢帧。模拟器 stencil attachment 分支另需设备验证。
**影响/修复/验证。** 确定的是 gfx 请求未落实,裁剪/深度表现 NEEDS_RUNTIME。统一转换各附件的 clear/load/store 语义,分别覆盖有无 clear 值、有无附件及模拟器组合格式。用非默认 depth/stencil clear 值做离屏后续比较测试,并核对 GPU capture;不得以 color 正常替代 depth/stencil 验证。
### B03 — Metal 离屏 present 中存在同步等待
**触发与链。** [RenderTarget::render](../../src/mbgl/renderer/render_target.cpp#L71) 在每个 target 绘制结束后 present;[Metal command encoder](../../src/mbgl/mtl/command_encoder.cpp#L29) 调 resource.swap;[OffscreenTexture::swap](../../src/mbgl/mtl/offscreen_texture.cpp#L86) commit 后执行 waitUntilCompleted,再重置命令缓冲。粒子稳态在[目标注册](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L2097) 中至少包含 StateWrite 和 TrailWrite,初始化还有额外读目标;retained 构建也有 present。
**所有权、边界和反例。** 等待保护命令/资源完成,是确定同步点,不是已测出的长时间阻塞。仅实际执行这些目标的帧计入;停绘、未激活层不计,retained 稳定复用不计新的 cache build 等待,GL 不经过此 Metal 函数。完成/销毁安全不能通过简单删 wait 保证。
**修复/最小验证。** 研究同队列命令依赖与完成回调持有资源,合并或延后提交,保留读写顺序及销毁保护。Metal capture 记录单粒子层每帧 command buffer、wait 次数和 CPU 等待时长,再测多粒子层、首帧、retained 重建及移除层;性能收益 NEEDS_RUNTIME。
### B04 — 内容失效与资源释放绑定
**触发与链。** 相机矩阵/内容 revision 改变而 viewport 尺寸不变。[Renderer revision 更新](../../src/mbgl/renderer/renderer_impl.cpp#L583) → [cache evaluate](../../src/mbgl/renderer/retained_underlay_cache.cpp#L201) → releaseResources → [prepareResources](../../src/mbgl/renderer/retained_underlay_cache.cpp#L244) 重新创建离屏资源。[releaseResources](../../src/mbgl/renderer/retained_underlay_cache.cpp#L365) 清掉纹理、合成组及资源键。
**正常/错误/销毁与反例。** 缓存对象持有资源,失效已实际释放所有权;稳定 valid 分支会 Reuse,不能称每帧都分配。viewport、格式、context 改变或最终销毁确需释放;资源创建异常已有 fallback。本 finding 针对同尺寸、同 context 的内容失效成本,不主张旧图继续有效。
**修复/验证。** 分开“内容有效性”和“附件可复用性”,保留匹配资源键的附件并重绘。固定 viewport 平移/缩放,对比 offscreen 创建次数、峰值显存、帧时间;再覆盖 resize/context 重建,防止错误跨 context 复用。成本路径已证实,分配耗时与收益 NEEDS_RUNTIME。
### B05 — 时间更新重复物化整个场
**触发与链。** Host 高频 setEncodedForecastTime,包括相同非零时间。[setRequestedForecastTime](../../src/mbgl/style/sources/raster_array_source.cpp#L453) → refreshV4RendererState;[clock setter](../../src/mbgl/particle/field/field_runtime.cpp#L196) 不因同值返回 false;[refresh](../../src/mbgl/style/sources/raster_array_source.cpp#L496) 先物化两类投影,再用 sameProjection 比较。[validity](../../src/mbgl/particle/field/field_projection_materialization.cpp#L69) 展开 bitset,[vectorSnapshot](../../src/mbgl/particle/field/field_projection_materialization.cpp#L105) 遍历向量;颜色 UV 与粒子各有自己的 T0/T1 物化。[sameProjection](../../src/mbgl/style/sources/raster_array_source.cpp#L65) 还会比较实际数组。
**所有权和反例。** 新数组在临时共享快照中,比较相同后可释放;发布新 Impl 时旧读者继续持有旧快照。无投影、无绑定、无法解出窗口会少做或跳过相关分支。相同内容可避免发布或 GPU 重传,故不等于每次都上传纹理;每个动画帧也不一定调用该时间 setter。完整性校验是必要成本,问题是同内容重复重建。
**成本/修复/验证。** N 个格点、两个时间面、颜色 UV 与粒子同时激活时,四份 vector+valid 基础数组约为 36N 字节的新逻辑存储,另有比较/校验遍历和旧快照重叠。缓存以完整不可变内容身份(含两分量、validity、grid、解释规则)为键,将 mix/time 与数组物化分离。统计同值时间、仅 mix 变化、跨窗、修订替换时的分配与上传次数;不得牺牲 B13 所需的完整身份。
### B06 — encoded PLAY 没有接入 forecast clock 推进
**触发与链。** 提交两帧、绑定、设置时间后,经公开 API 切 PLAY 并等待 repaint。[Source 播放设置及 advanceForecastClock](../../src/mbgl/style/sources/raster_array_source.cpp#L459) 中,播放设置只改 clock 状态,推进由独立方法完成。对 src/include/platform/RN 生产路径检索 advanceForecastClock 仅有[声明](../../include/mbgl/style/sources/raster_array_source.hpp#L240)和定义,没有调用方。Android [repaint timer](../../platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java#L225) 与 iOS [timer](../../platform/ios/src/MLNParticleFieldRuntime.mm#L147) 只触发重绘;粒子[读取 v4 mix/cadence](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1262) 不推进该 clock。
**正常/错误/销毁与反例。** clock 由 Source 所有;平台 timer 的生命周期清理不提供时间增量。[advance 实现](../../src/mbgl/particle/field/field_runtime.cpp#L209) 本身存在且处理溢出;[测试](../../test/source/raster_array_v4_host_api.test.cpp#L297) 直接手动调用,所以不能证明公开 PLAY 可自行推进。Host 持续设置 forecast time、C++ 调用方自行 advance、旧 sourceTimeline 路径均是反例;粒子在固定场中移动也不代表预报时刻在播放。
**修复/验证。** 为 encoded clock 指定唯一推进所有者和单调时钟来源,处理暂停/恢复、rate、地图移除、颜色单层及多层,避免每层重复推进。仅调用一次 setTime 和 PLAY,观察 requestedForecastTime/mix 随 elapsed 改变;暂停保持、恢复不跳跃、不同 rate 成比例,窗口末端行为明确。公开路径缺口可静态确定,运行表现 NEEDS_RUNTIME。
### B07 — 零值在 8-bit 场纹理中变成负值
**触发与证据。** 有效向量为零或接近抵消。[粒子编码](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L174) 用 uint8 转换 `(x / scale * 0.5 + 0.5) * 255`,scale 至少为 1;[颜色编码](../../src/mbgl/renderer/layers/render_weather_color_layer.cpp#L45) 同类处理;[GL 解码](../../shaders/particle_gpu_validation.fragment.glsl#L76) 还原为 `(channel * 2 - 1) * scale`,Metal [解码](../../include/mbgl/shaders/mtl/particle_gpu_validation.hpp#L48) 同理。x=0 被截为 127,解码为 `-scale/255`,两个零分量都带偏差,最小 scale=1 时向量长度也高于 1e-5 的近零阈值。
**边界和反例。** 场快照的 CPU float 正确不代表上传表示正确;无效点被 valid 分支拒绝,不计入这个有效零场问题。不同数值/缩放下误差相对大小不同;方向归一化可放大近零偏差。理论截断误差小于 `2*scale/255`,这是假定合法编码区间的算术界,不是 GPU 实测。重用或销毁纹理不会改变表示误差。
**修复/验证。** 选取可精确表达零的带符号编码或 float/half 表示,并评估纹理兼容性;仅改四舍五入仍无法让奇数 255 的对称映射精确表示零。以有效零场、微小速度、正负对消、纯东/北场做 GPU 解码读回和粒子位移检查,GL/Metal 同测。实际漂移像素/速度 NEEDS_RUNTIME。
### B08 — Mercator 东向位移少一个 sec(latitude)
**触发与链。** formal profile 使用 eastward/northward、m/s 的 U/V 语义,见[profile 校验](../../src/mbgl/particle/field/field_profile_validator.cpp#L25)。上传缩放在 shader 中还原后,[GL formal 位移](../../shaders/particle_gpu_validation.fragment.glsl#L169) 取 `dx=U*dt/C`、`dy=-V*dt/(C*cos(latitude))`;[Metal](../../include/mbgl/shaders/mtl/particle_gpu_validation.hpp#L133) 同样处理。这里 C 为地球周长,输入链未见对 U 预乘 sec(latitude)。
**推导、反例和边界。** Mercator x=λ/(2π)+0.5,而局部东向速度满足 dλ/dt=U/(R*cosφ),故 dx 应为 U*dt/(C*cosφ)。赤道、U=0 时不受此项影响;60° 纬度当前东向投影位移为应有值的一半。统一 visual time scale 不修复东西/南北相对畸变。此为固定输入语义下的数学缺陷,实际视觉速度、极区处理 NEEDS_RUNTIME;与资源销毁和错误 fallback 无关。
**修复/验证。** 在一致纬度与极区夹紧策略下对两个分量使用正确度量转换,保留既定视觉时间倍率。0°/45°/60° 的等物理速度东西向和南北向场,比较 CPU 解析预测与单步 GPU 位移;含符号、反向、极区边界与跨日期变更线。
### B09 — Ocean Mask 尚未形成正式共享地理输入
**触发与证据。** Wave/Tide/Current 需要 OCEAN_ONLY,或启用当前 mask 模式。[makeCoastalMaskFixture](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L281) 用格点坐标表达式生成合成海岸,formal 资源初始化[仍安装该 fixture](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1314)。物化[统一写 GLOBAL](../../src/mbgl/particle/field/field_projection_materialization.cpp#L77)。[顶点 shader](../../shaders/particle_gpu_validation.vertex.glsl#L56) 的 v_world 源自 quad 坐标,粒子合成[据此采样 mask](../../shaders/particle_gpu_validation.fragment.glsl#L262);[颜色分支](../../shaders/particle_gpu_validation.fragment.glsl#L113) 在该处理前返回。
**所有权、正常路径与反例。** mask 纹理由粒子资源拥有,随资源重建/销毁;缺少正式地理 mask 数据入口和两类层共享采样语义。默认关闭 mask 的 GLOBAL wind 不因此错误,Host 数据 nodata 也可能已有陆地缺测,但这不等价于独立共享 Land/Ocean Mask。不能把本限制扩展为所有风场不可用。
**修复/验证。** 由 Host 决定业务域策略,Core 接收明确地理坐标、网格和版本的共享掩码,两层统一采样;不把 provider/下载引入 Native。加载真实小型固定海岸 mask,平移、缩放、旋转后地理边界应固定;分别验证 Wind GLOBAL 与 Wave/Current OCEAN_ONLY,以及 nodata 叠加、删除与替换。当前设计缺口已证实,设备画面 NEEDS_RUNTIME。
### B10 — encoded 提交在 UI 派发链同步完成重工作
**触发与链。** RN 提交较大 QBIN。[Android bridge](../../packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt#L310) 经 runOnUiQueueThread 定位并调用 runtime;[Java submit](../../platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java#L97) → [JNI](../../platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp#L1501) 拷贝 bytes 并同步调用 Source。iOS [withMapForTag](../../packages/maplibre-particle-rn/ios/WeatherRuntimeBridge.mm#L105) 使用 UI block → [runtime submit](../../platform/ios/src/MLNParticleFieldRuntime.mm#L222) 拷贝并解码。V3 [CRC](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp#L66) 对字节做位循环,后续解码/校验/投影也在同步调用链内。
**边界、错误与反例。** Android 本地文件读取在 UI 派发之前,不能笼统写成全部文件 IO 都在 UI;小文件和 Duplicate 后的工作量不同,格式失败会提前返回。解码有大小上限,但允许的输入仍可较大。临时 bytes、decoded frame、Source 持久状态的所有权清楚;问题是所在线程及连续占用,并非已证明泄漏。未观测 ANR 或卡顿。
**修复/验证。** worker 只做不可变 bytes 解码/校验,带 map/style/source 世代令牌返回所有者线程提交;限制队列并处理取消、地图销毁和过时结果,不能把整个可变 Source 移到 worker。在中小/近上限输入、CRC 失败、连发及页面退出下记录 UI 区间耗时、峰值内存和错误回调。性能影响 NEEDS_RUNTIME。
### B11 — 每序列八帧不是全局内存预算
**触发与链。** 单 Source 连续提交不同有效 fieldKey。[EncodedFieldStore 成员](../../include/mbgl/particle/field/field_runtime.hpp#L151) 是 map 和 perSeriesCapacity=8;[submit](../../src/mbgl/particle/field/field_runtime.cpp#L223) 可为新 key 创建 series;[FieldSeries](../../src/mbgl/particle/field/field_runtime.cpp#L168) 只检查本序列容量。Source [提交](../../src/mbgl/style/sources/raster_array_source.cpp#L422) 不要求该 key 已绑定。V3 [输入上限](../../src/mbgl/particle/ingress/qbin_v3_decoder.cpp#L19) 限单帧 encoded/decoded bytes,不限 store 总量。
**所有权和反例。** store 持有共享帧,直到显式 clear/clearAll 或 Source 销毁;渲染读者可能延长旧快照寿命。单帧 decoded 上限 32 MiB 与八帧组合形成每序列最高 256 MiB 的逻辑上界,但不表示每个 profile 都能达到该值。key 数无总上界,不能据此保证 Source 总内存。Host 固定少量 key 并主动清理时可控;本轮没有 OOM 证据。
**修复/验证。** 在提交前计入全局 key/字节预算,包含共享去重与临时峰值;明确容量拒绝语义,保护当前绑定窗口,避免静默淘汰。循环提交多个合法 key,验证达到预算后稳定拒绝且现有窗口可用;clear 后可恢复,地图销毁后所有者释放,读者完成后最终释放。设计上界不足已证实,实际内存峰值 NEEDS_RUNTIME。
### B12 — 构建过程错误没有阻止 retained 缓存成功提交
**触发与链。** retained 范围内某 LayerGroup 在 render 时抛出被捕获的异常,observer 记录错误后正常返回。[RenderOrchestrator 遍历](../../src/mbgl/renderer/render_orchestrator.hpp#L156) 捕获异常并 onRenderError,不把失败状态返回调用方。[buildRetainedRange](../../src/mbgl/renderer/renderer_impl.cpp#L798) 在 opaque/translucent 遍历后继续 finishBuild、markBuildSucceeded;[成功标记](../../src/mbgl/renderer/retained_underlay_cache.cpp#L328) 使其可在后续稳定帧 Reuse。
**所有权、错误/销毁与反例。** 缓存持有可能只完成部分绘制的纹理,直到下次失效或销毁。资源准备阶段[有 catch/fallback](../../src/mbgl/renderer/retained_underlay_cache.cpp#L240),不属于本路径;无异常、observer 再抛出异常、随后 revision 立刻改变都不满足持续复用条件。此处只讨论同步可捕获 render 异常,不将异步 GPU 错误视作同一证据。
**修复/验证。** 让范围绘制返回成败或设置本次构建错误标记,失败不提交 valid 缓存,安全 fallback/下帧重试;保留 observer 通知,不引入每帧 GPU 等待作为错误检测。用一次性抛异常的假 LayerGroup 和正常返回的 observer,检查 BuildFailed、禁止 Reuse、下一帧成功恢复,再由 GL/Metal 场景验证画面。
### B13 — 粒子更新身份没有覆盖第二分量和有效性
**触发与可构造输入。** 同 fieldKey 的三个不同 validTime 帧使用相同 fieldID/generation/dataVersion,U 内容相同、V 依次变化(或 validity 变化),网格和 schema 兼容。此输入在[FieldSeries 新时间插入](../../src/mbgl/particle/field/field_runtime.cpp#L146) 合法:newerRevision 检查针对同一 timestamp,未要求不同时间递增 generation。窗口从 T0/T1 切到 T1/T2。
**完整调用链。** [materializeParticleProjection](../../src/mbgl/particle/field/field_projection_materialization.cpp#L201) 仅以 t0/t1FirstPlane 身份传给 baseSnapshot;[baseSnapshot](../../src/mbgl/particle/field/field_projection_materialization.cpp#L77) 用 fieldID+该身份组成 fieldID,保留 producer generation/dataVersion。[Source sameProjection](../../src/mbgl/style/sources/raster_array_source.cpp#L65) 比较实际数组,可正确发布新的 V/validity。但[粒子 sourceChanged](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1489) 只调用[fieldIdentityChanged](../../include/mbgl/particle/field/field_data_adapter.hpp#L111),其只比 generation/fieldID/dataVersion。两个槽身份都未变时,不进入内部 sameFieldGpuContent/纹理更新分支,继续使用旧场纹理。
**所有权、反例和边界。** Source 新投影存在、Renderer 仍持有旧 resources.field/nextField 及纹理,直到其他身份变化、重建或销毁。每时次主动递增 generation、U 也变化、初次资源创建都能触发更新;纯 mix 变化且窗口内容确实不变应保留纹理。此 finding 不指向颜色层:颜色投影的实际内容比较是反例,已排除“颜色仅看 U 而漏掉 V”的候选。
**修复/最小验证。** 粒子更新门槛使用包含 U/V、validity、grid 与解释语义的完整不可变内容身份;窗口时间状态单独更新,保持相同内容可复用。采用上述三帧并让 V/validity 可见地不同,seek 跨窗,检查 CPU 选窗、GPU 上传/复用判定及读回;增加不同时间同 generation、仅 V 改变、仅有效性改变、完全相同内容四组。错误门槛可静态证明,旧场画面 NEEDS_RUNTIME。
## 4. 反例检查及未成案候选
- **颜色投影身份:排除。** Source sameProjection → sameSnapshot → sameFieldGpuContent 会比较两分量/validity 实际数组;不能仅因第一 plane 参与 fieldID 就断言颜色不更新。B13 另有粒子特定的前置门槛证据。
- **粒子资源强引用环:排除。** [ValidationDrawableTweaker](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L770) 持 weak_ptr;[layerRemoved](../../src/mbgl/renderer/layers/render_particle_validation_layer.cpp#L1140) 移除目标。本轮未形成泄漏闭环。
- **关闭缓存后辅助段状态残留:排除简单结论。** [synchronizeRetainedUnderlayState](../../src/mbgl/renderer/renderer_impl.cpp#L142) 覆盖段缓存同步;不能仅查主缓存 setter。
- **纯重排未被处理:排除。** [Style diff](../../src/mbgl/renderer/style_diff.cpp#L19) 与[orchestrator 更新](../../src/mbgl/renderer/render_orchestrator.cpp#L318) 处理移除/添加并重建有序层;分段不是独立 Z-order 数据源。
- **任意交错不受支持:排除笼统结论。** 单 continuous 段的 fast path 限制不等于普通 segmented path 不支持多段;[普通绘制](../../src/mbgl/renderer/renderer_impl.cpp#L938) 仍按计划遍历。透明混合、深度与遮挡整体等价性尚需运行验证。
- **异常输入必然越界:未成案。** QBIN 有 MaxCells、字节预算、偏移/结构/CRC 检查,FieldSeries 有冲突/陈旧/兼容性拒绝。未完成形式化证明或 fuzz,不能声称完全安全,也未凭大输入符号制造越界 finding。
## 5. 性能与资源模型(静态模型,非 benchmark)
设视口为 W×H,retained 段数 K,格点数 N,活跃粒子层数 L,每层有效粒子数 P,encoded 字节数 E,fieldKey 数 F。
| 路径 | 静态成本模型 | 稳定路径/限制 |
|---|---|---|
| retained | 每段至少一张 RGBA8 颜色纹理约 4WH 字节,另加实际 depth/stencil 附件、对齐及 drawable;总量随 K 线性增长 | 稳定内容复用;Build 时绘制该段并合成;B04 增加同尺寸分配抖动 |
| 粒子 | State A/B 随状态纹理面积增长,Trail A/B 随视口面积增长;积分和粒子绘制约随 P 增长,全屏衰减/合成随 WH 增长 | 初始化/重播种还有额外工作;不可只凭 particleCount 推断全部成本 |
| Metal 提交 | 实际执行的粒子层稳态至少两次离屏同步点/层,加当帧 retained Build 数量及其他 target | 这是显式 wait 次数下界模型,不是每次都发生长等待;需 capture 核对实际启用分支 |
| 时间 setter | 激活投影物化和数组相等比较为 O(N),颜色 UV+粒子约 36N 字节基础新数组 | 单纯 draw 并不必然调用 setter;不同 scalar/vector 绑定成本不同 |
| QBIN | CRC 和解码大体随 E/样本数增长;提交链还会临时复制并可能物化 | 上限约束单帧;失败可以提前结束,不可套用最坏值到所有输入 |
| encoded store | 每 key 最多八帧,但总量随 F 增长;另有 materialized snapshot、上传图像、在途旧读者 | 需要显式 clear;现有容量拒绝保护单序列,未构成全局预算 |
未取得 profiler、锁竞争、allocator、GPU capture 或设备温度数据。不提供热点排名、FPS、毫秒、节省比例或确定 OOM 阈值。查到同步等待和 UI 同步调用,不等于证明死锁;本轮未形成有闭环证据的锁死 finding。
## 6. 测试源码覆盖与缺口
阅读测试源码只表示理解其断言,以下全部 NOT RUN。
| 已阅读的代表性测试 | 能说明的设计意图 | 不能替代的验证 |
|---|---|---|
| [retained 状态测试](../../test/renderer/retained_underlay_cache.test.cpp#L116)、[失败测试](../../test/renderer/retained_underlay_cache.test.cpp#L479)、[segment 测试](../../test/renderer/retained_underlay_cache.test.cpp#L657) | Build/Reuse/Fallback、范围与交错计划 | 真实 attachment stencil、Metal 清屏、异常经 orchestrator 传递、透明/深度像素等价 |
| [clock 测试](../../test/particle/field_runtime.test.cpp#L272)、[Host API clock](../../test/source/raster_array_v4_host_api.test.cpp#L297) | 手动 advance 的时间算法 | 平台 PLAY 是否接入生产推进者 |
| [materialization 测试](../../test/source/raster_array_v4_host_api.test.cpp#L392)、[颜色幅值](../../test/source/raster_array_v4_host_api.test.cpp#L433) | 快照不变性、mix、重建计算 | GPU 8-bit 数值误差、B13 跨窗同 producer 身份、数组分配次数 |
| [binding 测试](../../test/particle/field_binding.test.cpp#L84)、[V3 解码测试](../../test/particle/qbin_v3_decoder.test.cpp#L346) | 内容身份/选面、格式失败和边界 | 全 renderer 资源切换、UI 延迟、全局 key 预算及竞态销毁 |
未覆盖或未闭合:Vulkan/WebGPU 完整粒子实现、所有 Style 图层组合、真实设备 Metal simulator/device 差异、GPU 厂商驱动行为、平台业务调用频率、RN 安装/codegen/ABI、所有 error string 和 promise 回调分支、跨线程竞争注入、完整 fuzz、产物字节与 manifest 的对应关系。当前审查是有边界的静态追踪,不是逐行穷尽全仓或安全证明。
## 7. 后续可执行验证矩阵(交给 RDC,全部 NOT RUN)
本轮禁止执行以下构建/测试。RDC 在独立获准的验证阶段准备测试 runner 与设备;新增回归用例/修复属于后续代码任务,不能把下表当作本轮已提供可运行补丁。
| 项目 | 准备与执行动作 | 最小通过条件 |
|---|---|---|
| B01 | GL、Metal 各加载同瓦片 fill/line,策略 R/C/R、R/R、C/R/C;首帧/重建/复用逐帧对照关闭缓存 | attachment 切换均生成对应掩码,像素裁剪等价 |
| B02 | 离屏附件预置非默认值,创建带 depth/stencil clear 的 pass,再以比较绘制检查;device/simulator 各一次 | clear 值与请求一致,无依赖未定义旧内容 |
| B03/B04 | 1/2 粒子层、1/3 retained 段;固定视口平移 300 帧,记录 capture/分配/CPU 等待 | 给出次数、时长分布;资源复用变化有证据且无画面回归 |
| B05 | 两时间面,固定时间重复 100 次、mix 小步变化 100 次,再跨窗和修订 | 输出物化/分配/上传次数;相同内容复用且变化可靠可见 |
| B06 | 仅颜色、仅粒子、两者共存;setTime 一次后 PLAY、PAUSE、不同 rate | forecast time/mix 正确推进,每地图只推进一次 |
| B07 | 有效零场、微值、正负对消、东西南北常量场,GPU 单步读回 | 零保持零,近零阈值一致,误差有明确预算 |
| B08 | 0°/45°/60°,相同 m/s、相同 dt,比较解析 Mercator 位移 | U/V 投影度量一致,符号正确,极区有限值 |
| B09 | 同一固定地理 mask 同时供颜色/粒子,平移缩放旋转,切 GLOBAL/OCEAN_ONLY | 两层海陆边界一致且固定在地理位置 |
| B10 | 合法中小/近上限 QBIN、坏 CRC、连发、加载中移除地图 | UI 耗时可量化;回调一次;无过时提交/释放后访问 |
| B11 | 多 key 连续提交直至预算,clear 后再提交 | 可预测拒绝、峰值有界、当前窗口不破坏、最终释放 |
| B12 | 假 LayerGroup 首次抛异常,observer 记录后返回,下一帧正常 | 首次不得缓存成功;下一帧重试并恢复 |
| B13 | 同 generation/dataVersion/U 的三时次,分别仅改 V、validity,再 seek 跨窗 | 新窗口 GPU 内容正确;完全相同内容仍可复用 |
| 兼容 | 增删/重排/隐藏边界层,透明层交错、3D/custom fallback、resize/context 重建 | Style 顺序一致,fallback 可恢复,无残留目标 |
已有 C++ 定向测试的执行入口需使用平台生成的实际 runner;[test/CMakeLists](../../test/CMakeLists.txt#L1) 的 mbgl-test 是库,[macOS 配置](../../platform/macos/macos.cmake#L84) 定义 mbgl-test-runner。RDC 已构建并确认路径后可执行下列命令(本轮未执行;路径必须由 RDC 填写):
```sh
RDC_TEST_RUNNER=/absolute/path/to/mbgl-test-runner
"$RDC_TEST_RUNNER" --gtest_filter='Retained*:ContinuousLayerRange.*:RenderSegment*:ParticleFastPath.*:ParticleField*:ParticleQbin*:ParticleEncoded*:Source.RasterArrayV4*'
```
这只运行现有匹配测试,不覆盖全部上述新增场景。RDC 应先确认 runner 的过滤输出确实命中测试,保存命令、exit code、测试数量和日志路径;设备场景记录设备/后端/版本、输入 fixture、截图或 capture。实际构建目录、签名、设备 ID、安装产物及历史日志均 NOT AVAILABLE,本轮不臆造构建命令或成功状态。
## 8. 分批建议与停止边界
1. **先补正确性闭环:** B01/B02 的目标附件状态、B06 的唯一时钟推进者、B13 的完整内容身份;加入最小回归后再做跨后端运行验证。B12 同批修复失败提交语义,避免错误缓存长期复用。
2. **再统一数值与域语义:** B07/B08 的数值表示/投影度量,以及 B09 的共享地理掩码;用解析输入和小型固定 fixture 定量验收。
3. **最后按测量做性能与预算:** B03/B04/B05/B10/B11,先记录同步、分配、线程耗时和峰值,再分别改命令提交、资源复用、物化缓存、工作线程和容量策略;避免多项同时修改而无法归因。
本轮仅交付三份中文盲审文档,不修改实现或测试。完成后停止,由 RDC 冻结三份原稿 SHA-256 和时间,再独立移交、追加开发记录并做正式仓库 scoped 提交及远端核验。本会话不参与解除盲态比较,不根据外部材料回写发现。
配套:[Step 记录](../50_Step记录/STEP-096_V13隔离盲审_v1.md) · [完成报告](../报告/STEP-096_V13隔离盲审_Codex完成报告_v1.md)

View File

@@ -0,0 +1,56 @@
# STEP-111:R 段 Placement 结果等价与独立刷新可行性调查 v1
**日期:2026-10-10|状态:PARTIAL(初步可行性判断,不是 Codex 完整设计或运行验收)**
## 1. 范围及真实证据
- Mac `/Users/tei/dev/MapLibreParticleLab`,`particle/main`;调查时 HEAD `7ec9ac8c1635c7a3b607abc2ed4be4fd764e7b6d`。Integration `main` 与 `origin/main` 一致于 `80667d6c343411d03a760425438e3d292f452118`。
- 使用可见 GNU Screen `7445.codex`,真实界面显示 **GPT-6 Astra high**;Codex 进行两轮源码浏览/调查后长期无新事件,第二轮缩窄至直接写报告仍无输出,随后终止会话;未生成任何要求的 STEP-111 正式调查文档。此文件由 GPT/RDC 对当下代码和既有 STEP-110 证据重新核对后撰写,**不冒称 Codex 已完成 STEP-111**。
- 未改 C++/Swift/Kotlin/JS/测试代码,未编译、未测试、未跑设备;本文件是方案评估,不是新增 Native 功能。
## 2. 现有实现的确定事实(RDC 逐行核验)
1. `src/mln/renderer/render_orchestrator.cpp:775-810`:Continuous 渲染使用 `placementController.placementIsRecent` 得到全局 `placementChanged`,随后视情况新建 Placement、调用 `placeLayers(layersNeedPlacement)` 并更新 Controller;`symbolFadeChange` 和过渡也作为全局状态处理。这里的 `placementChanged` 表示重算触发,**不证明每一个 R 的最终输出改变**。
2. `include/mln/renderer/render_segment.hpp:78-90`:R 中只要出现 `layerType == "symbol"` 就被标为 placementCapable;Gate 对这种 R 要求全局 `!placementChanged && symbolFadeChange==0 && !placementTransitionActive`。没有 per-R 最终 Symbol 结果比较。
3. `src/mln/renderer/renderer_impl.cpp:638-686`:对每个 RETAINED 段已经独立计算内容 fingerprint、Source ready/stable/revision、segmentStable、placementCapable,并在 `cache->evaluate` 决定复用、构建或 fallback;`static-placement` 是 Gate 未通过时的失败原因。逐 R Cache 管理已存在,**缺的是含 Symbol 段的逐段结果等价证据**。
4. STEP-110 已核对的 `src/mln/text/placement.cpp` CollisionGroups/opacity/fade 说明:Collision Domain 与 RenderSegment 边界不等价;跨 Source 碰撞开关、同 Source 跨段、text/icon Fade 等可能让别的段影响本段可见结果。STEP-102/106 仅能证明 placement-free 的安全复用,不直接证明含 Symbol R 的安全复用。
## 3. 推荐的目标语义(待设计与实测)
`R0-C0-R1-C1-R2-C2` 六段按图层顺序合成;若 R1 内 Symbol 的**最终绘制结果**与其上次成功 Build 的缓存版本等价,允许 R1 复用,而不因某个 R0/R2/C 段触发 global placementChanged 自动 Fallback。跨段碰撞真的改变 R1 Symbol 的显隐、锚点、位置、opacity 或其它绘制依赖时,R1 必须失效。
**不等于**每个 R 拥有独立 Collision Index。第一阶段可以仍全图计算 Placement,之后对不同 R 的已完成结果分别判等,再控制各段纹理的 Build/Reuse;这只争取避免不必要的段重绘,不能自动省略全局 prepare/Placement、非 R 的 C 绘制或保证 Fast Path hit。
### 待验证的候选签名(不是已实现结构)
建议研究在 `placeLayers` 完毕之后、`renderer_impl.cpp:638-686` Cache 决定之前提供 `per-R PlacementResultRevision` 或严格结果比较,关联已缓存 Build 版本,而不只与上一帧比较。候选依赖包括:
- 按层序可稳定归属到 R 的 text/icon 实例、Tile/Bucket/Source 世代与 placement/collision 可见性、锚点、偏移、实际绘制位置;
- Fade/Opacity/Transition 的可绘制值或明确的“稳定可复用”条件;纹理/glyph/sprite/atlas 的有效世代;
- Camera/projection/zoom/viewport/pixelRatio、Style 顺序与合成边界;这些可优先复用已有 Camera/Content/Resource Gate,不应盲目重复扫描;
- 新旧结果缺失、ID 不稳定、资源生命周期不可证明、哈希碰撞或任意未知场景均 **fail-closed**。
上述字段只是**候选调查清单**,现有代码尚未证明可以低开销取得稳定、完整的 per-R 视觉语义。简单“Symbol ID/位置没变”或单一 64-bit hash 相等都不足以证明最终像素安全等价。
## 4. RCRCRC 反事实场景
| 情况 | 希望达到的复用决策 | 当前证据边界 |
|---|---|---|
| C1 只有粒子/颜色更新,R0/R1/R2 视觉稳定 | R0/R1/R2 独立复用 | 当前 global Gate 可能挡住含 Symbol R;仍须取得最终结果不变证据 |
| C1 Symbol 改变,仅使 R2 发生碰撞结果变化 | R2 重建;R0/R1 如证实未变可复用 | 事件来源不能替代逐 R 结果比较 |
| C1 Symbol 改变,同时影响 R0 与 R1 | R0/R1 失效、不得复用旧遮挡 | 必须保留全局 Collision 正确性 |
| R1 自身 Layer/Tile/Style 更改,R0/R2 不变 | R1 失效;其余分别判等 | 现有内容 revision 可局部区分,但 Symbol 仍有全局 Gate |
| Camera/Zoom/Pitch、Annotation、Fade、Glyph 变化 | 受影响 R 失效;未知则全体安全回退 | 不得将各段等同独立碰撞域;须验证跨平台一致性 |
## 5. 难度分级(GPT/RDC 初估,尚未经 Codex 最终复核)
| 阶段 | 复杂度 1–5 | 输出与主要难点 |
|---|---:|---|
| A:只读设计 + 逐 R trace/reason | 2–3 | 诊断 event 来源与 per-R Cache 状态,不改变业务 |
| B:Native per-R result fingerprint 的 shadow compare | 3 | 找齐最终 Placement 数据、稳定身份/世代、控制逐帧遍历成本;**只记录,不参与复用** |
| C:用已验证等价结果替换含 Symbol R 的 global-only Gate | 4 | 与 Cache 生命周期/资源/层序/Fade 联动,必须零 false-reuse;Android OpenGL、iOS Metal 对照 |
| D:真正把全图 Placement/Collision 改成分段增量计算 | 5 | 全局碰撞依赖与跨 Tile/Source 的更新传播,风险明显更高、不能简单按 R 切分 |
工程周期目前**无法从静态代码得到可信承诺**。复杂度 3 表示可做有限验证原型;4 代表需多阶段视觉/性能门槛;5 代表核心架构重构,不能与缓存命中优化混为一谈。
## 6. 后续建议及未完成项
1. 下一次正式任务先补只读源码数据所有权审计:Placement 完成后在哪个对象/时间点能列出每 R 所属 symbol 的最终 placed/opacity,并确定 crossTileID、tile/glyph 资源失效契约。输出字段级“已有/缺失/必须新增”清单。
2. 再授权仅做 **shadow-mode** 内部诊断:per-R `resultComparable/resultChanged/unknown` 与 global Gate 同步采样,**不改变 Gate**;采样成本与候选数量需量化,防止 O(N) 比较本身抵消收益。
3. 真实 RCRCRC(每个 R 含 Symbol)覆盖同 Source/跨 Source 碰撞开关、Annotation、Fade、Camera、Tile、Layer 顺序及前后多帧;全缓存 ON/OFF 视觉一致,**false reuse 必须为零**。
4. 通过后才另行授权 C 阶段优化,先看独立 R build/reuse/fallback 与 CPU/帧变化;Fast Path 对单 C、pending、non-particle repaint 等另有门槛,不作为此优化的当然收益。
5. 若出现跨平台公共语义/公共接口改变,回 Integration RFC/ADR 处理;本阶段不得更改现有 API/Contract。
**未完成:** Codex 未形成字段级可行性结论;无 per-R Placement 结果签名、运行对照、性能证据或真实 Native 修复。不可标“调查 PASS”或“独立刷新已实现”。

View File

@@ -0,0 +1,426 @@
# STEP-112 V23 粒子 Full Render 循环与 Symbol 缓存准入全面源码调查 v1
日期:2026-10-11(JST)。状态:本轮只读调查与文档交付完成;综合判定 **PARTIAL(现场逐帧归因及安全修复仍未验证)**。核心结论、证据等级和剩余事项见第11章。
## 1. 调查基线与证据规则
- 当前机器:Mac.lan,Darwin 24.6.0,x86_64;仓库 `/Users/tei/dev/MapLibreParticleLab`。
- 分支 `particle/main`,起点 HEAD `41b416c261fa72bd21171328045036a124694f47`。
- 实际模型 `gpt-6-astra`、推理 `high`:当前会话 `01a12686-49ee-70b1-953e-f95f8a4bd9ca` 的本地 rollout `turn_context.model/effort` 已核对。任务书建议模型由用户本轮明确要求覆盖。
- 起点只有 STEP-110、STEP-111 两份任务书未跟踪,无 tracked diff;不改动它们。子模块状态均为空格前缀,无未初始化/偏移标记。
- Integration `main=80667d6c` 使用用户提供的 RDC 核验事实,不重新同步或修改跨项目文件。
- 已按顺序读取 AGENTS、PARTICLE_BASELINE、8 份项目规范、总则 README 与四份现行总则、STEP-112、STEP-102/106/109/110/111 的相关任务/记录/报告。初始化基线及现行路线 v2 中旧版本坐标不冒充当前 V23;后续按 Git 实际源码比对。
- 证据分为 **源码证明**(指定基线静态控制流)、**FZG1 实测**(本任务引用历史现场输入,非本轮重测)、**条件推断**(前提明确的推演)、**仍需设备验证**(频率、真实配置、像素/性能)。
- 只允许写本主报告、STEP-112 阶段记录、完成报告、开发记录追加。禁止源码/测试/Style/脚本/跨项目写入,禁止编译测试、设备操作、Git 提交推送及分支/worktree 变更。
## 2. 已核实的入口事实
**源码证明**:`src/mln/renderer/renderer.cpp:45-64` 先同步 retained 状态,再尝试 Fast;失败才清除旧 prepared tree、`createRenderTree`、`prepare`、Full render、保存新 prepared tree。Fast 失败与粒子请求下一帧属于不同决策。
**源码证明**:`src/mln/renderer/particle_fast_path.hpp:99-126` 顺序短路。`cache-invalid` 在第 3 项返回,后续 revision、camera、staticStable、placement、pending、非粒子 repaint、C 层支持、scene 等条件没有因此得到通过证明。
## 3. 核心发现:Symbol R 首次 Build 的控制流矛盾
**源码证明(正常 Continuous Full 路径)**:含结构性 Symbol 的 R 没有通过当前逐段 Placement Gate 的窗口。这比“Fade 的 `==0` 可能太严格”更强,且不依赖 Source 变化或实际 Symbol 是否移动。
1. `src/mln/renderer/render_orchestrator.cpp:792-807`:`placementChanged=true` 时执行 Placement;否则无条件 `setPlacementStale()`。
2. `src/mln/text/placement.cpp:160-175`:旧 Placement 禁用 transitions 时 `placementIsRecent()` 必为 false,故必进入 changed=true 分支;若进入 changed=false 分支,旧 Placement 必启用 transitions,随后 stale=true,使 Controller `hasTransitions()` 必为 true。
3. `src/mln/renderer/renderer_impl.cpp:593-594,655-659` 读取同一 Controller 的 transition;`include/mln/renderer/render_segment.hpp:85-90` 要求 `!changed && fade==0 && !transition`。
4. 因而 changed=true 被第一项挡住;changed=false 被 stale→transition=true 挡住。对结构性 Symbol R,两个分支均 false。`RenderTreeImpl::prepare`(`render_orchestrator.cpp:133-138`)仅更新 buckets,不清 Controller stale;没有中间解除路径。
此结论限定正常单线程 Renderer 调用链,不将任意人工注入/未来改动当作当前可达状态。隐藏/空 Symbol 仍按结构归为 capable(`render_segment.hpp:75-90`),所以也不能绕开这层保护。
**源码证明**:原始 `Placement::symbolFadeChange` 是相对 commitTime 的归一化时间增量加 zoomAdjustment,而非“本帧是否改变”布尔量(`placement.cpp:1282-1289`);transitions 关闭或 duration≤0 返回 **1**。正常 Full 的 `prepare()` 不改写它。只有已通过 Fast 的 `prepareParticleFastFrame()` 才把 RenderTree 字段设为 0(`render_tree.hpp:105-109`),不能反过来帮助首次 Build。
**FZG1 实测(任务书输入)**:末态 true/0/false 与“transitions 开启、此次重新计算、静止 zoom、先前实际 Fade 已结束”的状态相容;与通常 transitions 关闭后的 true/1/false 不同。不能仅凭此末态推断每帧走默认 300ms 周期或 Host 确实关闭 transitions。
**条件推断**:在 retained enabled、正常 Continuous Full、R2 含 Symbol 的前提下,逐帧 `segmentSupported=false` 可令 Build 始终为 0;`retained_underlay_cache.cpp:195-205,359-366` 即使此前没有有效纹理也每次记录 fallback/invalidation。故 1787 次 invalidation 不表示曾成功建成 1787 份纹理。
以下第4—10章已补齐调度、全部 Gate、状态机、时间表、反事实和提案;运行时无新增证明,不能据此声称修复 PASS。
## 4. 版本核验与 A:调度、Full、GPU 和 Idle
### 4.1 当前 main 与发行 V23
**源码证明**:执行只读 `git diff --name-only 87f3d1c59a52581fb8339f0ea7d106b9f4b94fef HEAD -- <相关路径>`,在 renderer/text/map、RasterArraySource、render_segment、TransitionOptions、Android WeatherRuntime/frontend、iOS FieldRuntime、GL/Metal shader 范围内,仅两份文件不同:`render_particle_validation_layer.cpp` 和 `render_weather_color_layer.cpp`。逐 hunk 核对为 STEP-109 的 immutable snapshot 身份快比较,未改变本调查的调度、Placement、Cache、Fast Gate。以下行号一律对应本轮 HEAD,不照搬 V23 两份 Layer 的旧行号。
V23 Source Lock 为 `87f3d1c…`(既有 STEP-110/108 发行证据),Android/RN `13.6.1-particle.23`、iOS `6.29.0-particle.23`。当前 main 包含尚无实机性能验收的 STEP-109;不得称它已在 V23 APK 生效。本轮没有重新下载制品或核对设备内 binary。
### 4.2 可核查调用图
下图箭头“条件”都不是每次必然发生。路径简称只用于图;实际位置见后表。
```mermaid
flowchart TD
H[Host 数据/Style/Camera 更新] --> U[Map::Impl::onUpdate]
W[WeatherRuntime 定时唤醒:条件成立] --> T[Map::triggerRepaint]
T --> U
U --> F[RendererFrontend update / 平台 requestRender]
F --> R[Renderer::render]
R --> G[tryRenderParticleFastFrame:24 项 Gate]
G -->|通过| Q[复用 prepared tree / 更新粒子 / GPU render]
G -->|失败| C[createRenderTree / Layer prepare / 全局 Placement 判定]
C --> P[RenderTree prepare / bucket opacity 与动态属性]
P --> B[各 R evaluate:Build / Reuse / Fallback]
B --> D[离屏 Build 或保序 composite/live / GPU 粒子]
D --> O[onDidFinishRenderingFrame]
Q --> O
O -->|needsRepaint 或 camera transition| U
O -->|loaded 且无需 repaint/transition| IDLE[onDidBecomeIdle]
```
| 环节及证据强度 | 文件、函数、行号与条件 |
|---|---|
| **源码证明**:粒子是否活动 | `src/mln/renderer/layers/render_particle_validation_layer.cpp:1112-1127`,`isParticleAnimationActive` 检查 active、正式 Source 的 T0/T1、visibility;`needsRepaint` 再检查 V4 cadence 或旧 timeline。active=false、无双端点、visibility=None 不请求粒子 repaint。它不含“必须 Full”标志。单帧输入可投影成可用双端点,不能以业务仅一帧推断无数据。|
| **源码证明**:粒子暂停和预报暂停不同 | 同上;`RasterArraySource::tickForecastClock`,`src/mln/style/sources/raster_array_source.cpp:487-510` 在 forecast 非 Playing 时不推进预报;这不把粒子 active 设 false。风场时间冻结仍可流动。无向量或语义不匹配另由 Layer `update:1247-1258` 拒绝 GPU 更新。|
| **源码证明**:Core cadence | `raster_array_source.cpp:149-175,208-227,260-281,1237-1255`;非 AUTO 以 `1/fps` 检查,30 对应约 33.33ms;consume 才推进 cadence revision。AUTO 每次可 due,不等于固定 30。|
| **源码证明**:Android SDK 唤醒 | `platform/android/MapLibreAndroid/src/main/java/org/maplibre/android/maps/WeatherRuntime.java:228-234,250-269`;有效可见粒子且非 AUTO,或 V4 forecast Playing 且已提交字段时,Handler 每次安排 34ms(60 为 17ms),调用 `map.triggerRepaint()`;disabled/disposed 停止,隐藏粒子但 forecast Playing 仍可唤醒。这里是 SDK Java 调度,不是 NavSea JS 每粒子循环。|
| **源码证明**:iOS 对应唤醒 | `platform/ios/src/MLNParticleFieldRuntime.mm:173-193`;一次性 NSTimer 0.034/0.017s,触发 `triggerRepaint` 后重新调度。两端意图相同,调度抖动不保证相同。|
| **源码证明**:进入地图更新 | `src/mln/map/map.cpp:93-95` 调 `Impl::onUpdate`;`src/mln/map/map_impl.cpp:108-153` tick forecast、更新 camera transition、装配 UpdateParameters、调用 frontend.update。非 Continuous 且无 still request 直接返回。|
| **源码证明**:Android 跨线程链 | `android_renderer_frontend.cpp:118-133,149-152`(路径前缀 `platform/android/MapLibreAndroid/src/cpp/`)AsyncTask 保存最新 params,再 `MapRenderer.update/requestRender`;`map_renderer.cpp:127-149,153-157,221-247` 请求 Java 渲染、后端 scope 中调用 `Renderer::render`。多个 update 可能合并为一次实际 render。|
| **源码证明**:iOS frontend | `platform/darwin/src/MLNRendererFrontend.h:28-54` 保存 params、setNeedsRerender,backend scope 中 render;不同于强制 Full 命令。|
| **源码证明**:Fast/Full 分岔 | `src/mln/renderer/renderer.cpp:45-64`;Fast 成功直接 return;否则 create/prepare/render/commit tree。`renderer_impl.cpp:324-327` 只在 `!particleFastFrame` 增加 Full CPU 计数。`FullRendererFrame` 在 :323 两种路径都会计数,名称不能混用。|
| **源码证明**:Full repaint 汇总 | `render_orchestrator.cpp:775-823,1107-1128`;light、Layer transition/repaint、Controller transition/stale、fading tiles 的 OR;Continuous 时赋 `RenderTreeParameters.needsRepaint`。|
| **源码证明**:通知与 Idle | `renderer_impl.cpp:1070-1074` → Android forwarding `android_renderer_frontend.cpp:38-44` → `map_impl.cpp:259-288`;needsRepaint 或 transform transition 时 onUpdate,否则 loaded 时通知 Idle。Observer 的 RenderMode::Full 表示 loaded,不表示执行 Full CPU 路径。|
| **源码证明**:GPU 推进 | Layer `update:1271-1283,1994-2055`;未到 cadence 可只重组 composite,不推进 state/trail;到期才准备 State/Trail pass。`renderer_impl.cpp:807-810,968-980,1047-1053` 执行 render targets 和 present。一次 repaint、一次 Full CPU、一次 simulation advance、一次成功呈现不是同一计数。|
**仍需设备验证**:FZG1 约 30FPS 的真正限速因素是 targetFps=30、平台帧率限制、耗时瓶颈或组合,任务快照不能区分。源码支持“Core 33.33ms 阈值 + SDK 34ms 唤醒”的候选机制,不能把 34ms 直接换算为实测 1792 次 Full 的唯一原因。
**源码证明**:所追踪的 PlacementController 仅在 render 链上比较时间,并无独立定时器调用强制 Full;stale/尚有 Fade 会通过 needsRepaint 延续正常地图帧。没有外部输入且粒子关、其它 transition 完成、资源 loaded 时可 Idle;WeatherRuntime 后续 wake 或 Tile 完成仍能再次唤醒,Idle 不是永久无任务保证。
### 4.3 Full 与 Fast 工作比较
| 工作 | Full | Fast | 证据 |
|---|---|---|---|
| diff Style/Source、prepare RenderLayer、CrossTile/Placement、新 RenderTree | 执行相应流程;不等于每帧都 placeLayers | 跳过,复用旧 tree | `renderer.cpp:53-61`;`render_orchestrator.cpp:445-539,737-848` |
| Source/bucket/atlas upload | 遍历 | 跳过整批 | `renderer_impl.cpp:470-487` |
| Layer update | 全部 render items | 仅 isParticleAnimationLayer | `renderer_impl.cpp:491-509`;`render_orchestrator.cpp:1458-1483` |
| tweaker/upload/postRender | 全范围 | 单 C 范围;render targets 仍处理 | `renderer_impl.cpp:707-749,1031-1040` |
| R GPU | 可 Build、Reuse composite 或 Live | 正常命中应复用;仍进入 cache evaluate/render | `renderer_impl.cpp:637-692,813-862,972-1028` |
| C GPU、State/Trail、最终 framebuffer/present | 执行 | 仍执行 | 同上及 :1047-1053 |
Fast 不是零 CPU 或零 GPU;Full 不是所有 R 从零重画。PREFIX 中 R0/R3 已复用而 Full CPU 仍增长,正与此结构相符。
## 5. B:Fast 全 Gate 矩阵
下面按 **实际首错顺序**列出全部 24 项。简称:`F=src/mln/renderer/particle_fast_path.hpp`;`I=src/mln/renderer/renderer_impl.cpp`;`O=src/mln/renderer/render_orchestrator.cpp`。表中“Tick”特指静态场、仅粒子 State/Trail 时间推进,不包括 forecast 播放导致新 Source impl;“Full恢复”是假定其它门槛已满足的一次新 Full 能否建立相应证据;FZG1 列区分输入实测与静态推断。
| # / 条件与失败名(F 行) | 输入来源/通过条件 | Tick 本身改变? | 一次新 Full 后可能恢复? | FZG1 证据 |
|---|---|---|---|---|
| 1 prepared tree / no-prepared-tree (100) | I:247,290-302,tree 非空;reload/miss 清旧 :305-316 | 否 | 是,create 成功后保存 | **FZG1实测** cache-invalid 的那些判定已过此项 |
| 2 retained enabled / retained-disabled (101) | I:207-208,248,runtime snapshot.enabled | 否 | 需控制面启用,Full 不自动开启 | **FZG1实测** 同上已通过 |
| 3 所有 R valid / cache-invalid (102) | I:220-228,249,cache 容器与 plan 大小相同,每个 R cache 非空且 isValid | Tick 不改内容;Full Placement gate 可间接 invalidate | 无 Symbol 可建;当前含 Symbol 的 R 不可(第3章) | **FZG1实测** 首拒;R2 Build/Reuse=0 |
| 4 Build/Static revision / revision-mismatch (103) | I:220-230,250,每 R buildRevision==observedStaticRevision,prepared aggregate static revision==tracker.current | 否;Full 稳定状态变化可推进 tracker | 通常可以,但缓存未建仍失败 | **未知**,被 #3 掩盖 |
| 5 Content revision / content-mutation (104) | I:236-237,251,prepared 与 O 的 aggregate content revision 相等 | 固定场否;forecast/source 变化可改 | 是,Full 处理后 commit 新值,异步新变更再失效 | **未知**;有历史 Source Mutation 增量,无逐帧对应 |
| 6 Viewport / viewport-changed (105) | I:214-216,253,Transform size 与 prepared 相等且 backend renderable size 未变 | 否 | resize 后重建可 | **未知**,静止场景不等于逐帧已核验 |
| 7 Camera / camera-changed (106) | I:217,252,精确投影矩阵相等 | 否 | 新相机完成 Full 后可,持续交互继续失效 | **未知** |
| 8 non-camera signature / update-parameters-changed (107) | I:151-196,218-219,254,完整签名相等,字段见下段 | timePoint 不入签名;forecast 可能更换 sources | 是,新 params 被 prepare/commit;源持续变则持续 miss | **未知** |
| 9 Style loaded / style-not-loaded (108) | I:255,UpdateParameters.styleLoaded | 否 | 需真实加载完成 | **未知**(不能以段 ready 代替所有) |
| 10 Boundary/RangeConfig / boundary-changed (109) | I:209-234,256,唯一 C 的 begin/end、configRevision 与 prepared 相等 | 否 | plan 有效时可;多 C 另被 #22 拦截 | **未知**,已知 PREFIX 数字来自现场 |
| 11 Topology / topology-changed (110) | I:235,257,prepared topologyRevision==tracker.current;tracker I:542-563、cache.cpp:62-69 | 否 | Full 新拓扑和 cache 可重建 | **未知** |
| 12 staticStable / static-unstable (111) | I:238-244,258;O:1156-1170,所有 R 稳定、无 light、R 动态 needsPlacement 时 Controller 无 transition、R 无 transition/repaint、**任一 Source** 无 fading tiles | 粒子不直接改 R;时间和 stale 可阻塞 | 取决于 Controller/资源,当前 Symbol 周期不保障恢复 | **条件推断** 部分帧会失败,实际序列未知 |
| 13 Placement transition / placement-or-fade (112) | I:259-263;O:1281 → PlacementController::hasTransitions(now) 为 false;不读旧 tree 的 changed/fade 代替 | 仅时间推进可结束真实 Fade,但 stale 必须新 placement 清 | 稳定重算帧可清 stale;不重算会置 stale | **FZG1实测** 末态 false;不是所有 Fast 时刻都 false |
| 14 Pending / pending-changes (113) | I:264;O:1285,pendingChanges.empty;O:1341-1344 入队、1486-1490 消费 | 正常粒子 changes 于 render 中生成/消费;非必然积压 | 通常可以,异步继续到来则不保证 | **未知** |
| 15 non-particle transition / non-particle-transition (114) | I:265;O:1270-1273,无 light 或任意 Layer.hasTransition | 否;普通 Style transition 可活跃 | 等 transition 真结束再 Full | **未知** |
| 16 non-particle repaint / non-particle-repaint (115) | I:266;O:1275-1278,非粒子 Layer 均不需 repaint | 固定 V4 场通常否;V3 WeatherColor 播放可 true(Layer :177-180) | 活跃动画不会因 Full 自行结束 | **未知** |
| 17 fading tiles / fading-tiles (116) | I:267;O:1282-1284,所有 Source 无 fading tiles | 否 | fade 真结束且状态消费后可 | **未知**;与 #12 重复但诊断次序不同 |
| 18 ParticleNeedsRepaint / no-particle-repaint (117) | I:268;O:1274,至少一个 renderable 且 isParticleAnimationActive;**这里不是 cadenceDue** | active 粒子满足;每次 advance 不取消 active | 数据就绪/显示后可;粒子关不能通过 | **条件推断** 风场活动大概率满足,未逐帧取证 |
| 19 still image / still-image-request (118) | I:269,stillImageRequest=false | 否 | 请求消耗后可 | **未知** |
| 20 MapMode / unsupported-mode (119) | I:270,Continuous | 否 | Static/Tile 不自动转换 | **条件推断** 现场连续地图符合,仍需记录 |
| 21 Debug / debug-options (120) | I:271,NoDebug | 否 | 用户关闭后可 | **未知** |
| 22 C 支持 / unsupported-continuous-layer (121-123) | I:272,624-627;O:1241-1250,有且仅一个 C、每层 startsRetainedDynamicRange、至少一个 particle | 否 | 固定含普通 Symbol/Line 的 C,多少次 Full 都不恢复 | **源码证明+现场布局前提** PREFIX C[13,33) 必失败 |
| 23 scene / unsupported-scene (124) | I:273,619-630,preparedSceneSupported=true 且唯一 C;其内部含 loaded、无3D/debug、所有 R stable、aggregate Placement stable、无 legacy/custom、C 支持 | 否;Full 会重算并保存 false | 仅修 R2 局部 Gate 不够;需一致安全判据 | **条件推断** R 有活动 Symbol 或 C 普通层使其 false |
| 24 Annotation / annotation-mutation (125) | I:202-205,274,AnnotationManager 无 pending changes(weak guard) | 否 | 正常更新消费后可;新 annotation 继续阻塞 | **未知**;不能把末尾 Symbol 存在等同 pending=true |
#8 的逐字段完整范围(I:154-180,186-196):pixelRatio、mode、debugOptions、styleLoaded、spriteLoaded、transformChanging、gestureInProgress;light/images/sources/layers/fontFaces/fileSource 对象身份;glyphURL;transition duration/delay/enablePlacementTransitions;prefetchZoomDelta、crossSourceCollisions、fastPFOREnabled;tileLodMinRadius/Scale/PitchThreshold/ZoomShift/Mode。viewport、camera 矩阵独立比较;timePoint 不在签名。**对象身份改变可触发 Full,即使负载内容相同;反过来单纯 triggerRepaint 且签名相同没有“必须 Full”语义。**
所有输入在 I:246-275 装配,但 F:100-125 返回首错;现有记录 I:276-280 只有 candidate/reason。#3 的失败不能证明 #4—24 成功。修完 R2 仍会遇到 PREFIX 的 #22、prepared scene #23,以及真实资源/Source/Annotation 等独立门槛。
### 5.1 第二重全局锁
**源码证明**:Full 的 `retainedPlacementStable`(I:590-592)依据动态 `retainedRangeUsesPlacement`(O:1135-1141,即 needsPlacement),而逐 R gate 依据结构性 Symbol。活动 Symbol R 存在时:重算帧 aggregate placementStable=false;不重算帧 Controller stale=true,aggregate staticStable=false(O:1158)。二者分别让 `retainedSupported`(I:619-623)为 false,写入 `particlePreparedSceneSupported`(I:630)。这是独立于逐段 cache 的 **Fast scene 锁**,不是 main-buffer 的 global-live 判定;后者 I:998-1028 已不含 aggregate placement,允许无 Symbol R 继续 composite。
假设以后仅让 R2 valid,旧 preparedSceneSupported=false 仍会在 #23 阻塞;不能等时间到就认为 prepared flag 自己恢复。Fast 的动态 transition 检查已避免直接沿用旧 tree changed/fade(I:259-263 注释),但没有解除 Full 产出的 scene 保守状态。隐藏 Symbol 可能使动态 usesPlacement=false、scene 不受它影响,结构性每段 gate 仍会拒绝首次 Build。
## 6. C:R2 Build / Invalidate / Fallback 状态机
### 6.1 从层结构到每段决策
**源码证明**,按执行顺序:
1. `include/mln/renderer/render_segment.hpp:48-90` 由 Style 元数据列举 Source 依赖与 Symbol 能力;type=symbol 就 capable,不看 visibility、当帧 bucket 或可见像素。越界 range 保守 capable;custom/custom-drawable 依赖 unknown。动态 `needsPlacement()` 则要求 CrossTileIndex::Required 且 placementData 非空(`src/mln/renderer/render_layer.cpp:30-32`),两者不等价。
2. `render_orchestrator.cpp:1173-1202` 按段依赖查询 Source loaded/fading/revision,CustomMVTVector 暂无 revision contract 而 fail-closed;`retainedSegmentIsStable:1205-1216` 检查 light、相关 Placement transition 和本段 Layer transition/repaint,不再把无关 Source fade 当每段阻塞。对比 Fast 的 `retainedRangeIsStable:1156-1170` 仍检查所有 Source fade。
3. `renderer_impl.cpp:516-535` 拓扑代际或 cache 数量变化时释放/重建每段 cache 容器;:558-559 的 key 为 viewport、begin/end、cameraMatrix。:641-652 组合 immutable layer impl+index fingerprint、source/global revision、pixelRatio。fingerprint 不是 Symbol 最终视觉签名。
4. :653-666 将 segmentStable、结构性 placement gate、style/sprite/image ready、Continuous/no3D/noDebug/noLegacy/noCustom 合并为 segmentSupported。
5. :668-680 首错优先级:source not ready → source unstable → style/sprite/global resource → placement → transition → legacy → unsupported → boundary → 3D → debug → mode。`static-placement` 是此处 gate 的名字,不是“R2 像素变了”的探测器,也不证明其它更后项通过。
6. :681-691 调 evaluate;只有 Build/Reuse 才 prepareResources;失败改为 resource-or-compositor。所有 supported 条件通过仍不代表最终纹理已经绘制成功。
### 6.2 状态转换和计数
所有状态均据 `src/mln/renderer/retained_underlay_cache.cpp`;这是源码状态机,不是本轮运行截图。
```mermaid
stateDiagram-v2
[*] --> Invalid: 创建 / 资源释放
Invalid --> Fallback: supported=false
Ready --> Fallback: supported=false / invalidate
Fallback --> Fallback: 下帧仍不支持 / 再计 invalidation
Invalid --> BuildPending: enabled 且 key存在 且 supported
Fallback --> BuildPending: 准入恢复
Ready --> BuildPending: key或revision变化
BuildPending --> Ready: 资源准备 + 离屏绘制成功
BuildPending --> Fallback: resource或draw失败
Ready --> Reuse: valid 且 key/revision相同
Reuse --> Ready: 纹理保持有效
Ready --> Invalid: releaseResources
```
| 分支 | 行号及真实后果 |
|---|---|
| 无 key | :173-182,原来 valid/key 存在才 invalidate/release,返回 Disabled;与 R2 static-placement 不同 |
| runtime disabled | :184-193,记 fallback;控制面同步 :129-163 另负责 invalidation/release |
| supported=false | :195-205,**无条件** fallback++、invalidate;revisionChanged 才 release。即使 valid 本来 false,也再次 invalidation++。未走 first-frame 和 BuildPending 分支 |
| revision 变化 | :171-172,207-213,invalidate/release、装新 key;这里 staticRevision 参数实际是每段 content 合成 revision,区别 aggregate tracker |
| 初次无 key / key 改变 | :214-219,first-frame / viewport / camera-or-boundary;设 key。普通 key 变化未必释放同尺寸 GPU allocation,但 valid 失效 |
| buildRevision 不一致 | :220-224,invalidate/release;不能复用旧代际 |
| valid=true | :225-236,reuse++,返回 Reuse;不代表 main-buffer 必 composite,还要看全局 live gate |
| invalid 且可支持 | :238-243,BuildPending,返回 Build;此时 buildCount 未增 |
| GPU 资源 | :246-303,离屏 RGBA unsigned-byte texture(带 depth/stencil 参数)及 composite drawable;失败返回 false |
| Build 真成功 | renderer_impl.cpp:813-862,beginBuild→R opaque/translucent→finishBuild→markBuildSucceeded;cache.cpp:332-345 设置 valid、buildRevision、build++、Ready |
| Build 失败 | cache.cpp:347-356,fallback++、invalidate、release,reason=resource-or-compositor;需要该 reason/资源计数,不能从 Build=0 单独推断 GPU 坏了 |
| invalidate / release | :359-380,前者 valid=false、记原因和计数;后者清 key/buildRevision/texture/compositor,不必增加同一种 invalidation 计数 |
`lastInvalidationReason` 是最后一次记录,Reuse 或成功 Build 不在此函数中自动抹掉它;采样必须连同本帧 decision、gate、frameId 使用。`plan.valid=true` 也不等于 `cache.isValid=true`。
### 6.3 FZG1 数字支持什么
**FZG1 实测(任务书第二区,历史输入)**:PREFIX 约60秒,Full +1787,R0 reuse +1787,R2 build/reuse=0/0,fallback/invalidation≈1787,R3 reuse +1755,Fast=0,首拒 cache-invalid。
**源码证明 + 条件推断**:每次 Full 的 R2 结构 capable → 第3章矛盾使 placement gate=false → supported=false → evaluate 直接 Fallback/invalidate → 正常 main-buffer live → 新 prepared tree 保存但 R2 invalid → 下一粒子帧 Fast #3 再失败。该闭环不需要假设 Source 每帧变化,也不需要假设 GPU 资源损坏。
**仍需设备验证**:本轮没有60秒每帧原始 trace,不能把一次末态 static-placement 扩成每帧唯一首因;若某帧 Source not ready,:668-672 会先报 Source。提供的 build 是绝对值还是窗口增量、topology 重建是否清计数,也应在后续原始 diagnostics 中核对。若“Build=0”只表示窗口增量,不能排除窗口前构建过;当前源码正常含 Symbol Full 无准入窗口则给出更强的机制约束。
Full 中 Symbol 正确显示,是 main buffer 的 live 绘制结果;cache Build 是单独离屏 render pass,:817 的 decision!=Build 会直接不画离屏纹理。没有自动把 main framebuffer 的 R2 像素拷贝成缓存的路径。R3 少于1787的32次复用差额可能来自资源/Source/全局条件,**没有足够逐帧证据归因**。
### 6.4 Collision 与资源边界
- **源码证明**:Placement::placeLayers 按完整 layer 列表逆序碰撞(`placement.cpp:201-206`);CollisionGroups::get(:45-62)在 `crossSourceCollisions=false` 时才按 Source 隔离,开启时使用无过滤组。:46-48 的英文注释与实际 if 的开关文字不一致,本文依执行分支,不照抄注释。相同 Source 跨段仍同组;关闭跨源碰撞也不等于关闭段内碰撞。
- **源码证明**:`Placement::commit:687-749` 内部同名局部变量 placementChanged 根据 text/icon placed 变化更新 fadeStartTime;外层 RenderTree.placementChanged 表示是否运行新计算。两个变量作用域和语义不同;全局 commit 也没有比较 R2 最终纹理。
- **源码证明**:Source/Tile mutation 可按依赖映射每段,glyph/image/style/context 未能局部定位时推进 global epoch(`render_orchestrator.cpp:243-275,327-347,1519-1521,1555-1559,1582-1594`;`render_segment.hpp:114-133`)。Camera/viewport/pixelRatio/拓扑另有 key/revision 保护。Source ready 不等于每个 Symbol shader 输入稳定。
- **条件推断**:别段符号或 Annotation 可能改变 R2 text/icon 可见性;同一位置/ID 也可能 opacity、variable anchor、orientation、line reprojection 改变。只有新一次完整 Placement 后证明最终有效输出及资源代际等价,才有候选复用条件。任何无法比较项必须 fail-closed。
## 7. D:Placement/Fade 真语义与可达状态
### 7.1 两个时钟、一个 stale 标记
**源码证明**:TransitionOptions 默认 enablePlacementTransitions=true(`include/mln/style/transition_options.hpp:12-26`),默认 duration=300ms(`include/mln/util/constants.hpp:59`)。`Placement` 构造保存当次 options、commitTime、placementZoom(`placement.cpp:180-192`)。因此 UI 当前 option 与 Controller 中旧 Placement 的 option 在下一次新建前可能暂时不同,诊断必须同时记录。
当 transitions 开启且 duration=D>0:
- `symbolFadeChange(now) = (now - commitTime)/D + prevZoomAdjustment`(:1282-1289);这是无 clamp 的增量,可能>1,零只说明刚 commit 且 zoomAdjustment 为零等特殊条件。
- shader 使用 `clamp(baseOpacity ± symbolFadeChange,0,1)`,符号实际已终态时 uniform 仍能变化;Fade 终态不以 uniform==0 定义。
- `Placement::hasTransitions` 用 **fadeStartTime** 而非 commitTime:`now-fadeStartTime < D`(:1319-1322);commit 没有 text/icon placed 改变时继承旧 fadeStartTime(:748-749)。新计算与新的真实 Fade 不是同义词。
- `placementIsRecent` 用 commitTime+updatePeriod>now(:160-167);普通 period=`max(300ms,D)*(1-zoomAdjustment(currentZoom))`(:1307-1312);zoomAdjustment=max(0,(oldZoom-newZoom)/1.5)(:1292-1298),zoom-out 可缩短到零甚至负值;此处没有下限 clamp。
- 新 Symbol buckets 且非 tiltedView 才使用 30ms **periodOverride**(O:778-789),不是把 Fade duration 改成30ms,也不是所有 Source 变化都立即重算。
- Controller `hasTransitions` 在 transitions 开启时 **stale 优先返回 true**(:170-175)。它含“需要重新评估”的语义,不能直接当作真实 opacity 正在变化的精确检测器。
- delay 保存在 options/signature,但上述三个 Placement 时间公式没有读取 delay;不能将一般 Style paint transition 的 delay 机械套给 Symbol placement fade。
### 7.2 transitions 开启的逐帧示意
**条件推断(非 FZG1 实测时间线)**:固定 camera、无新 bucket、D=300ms、约33.33ms一帧,t0 首次 placement 后内容稳定。忽略调度抖动,t0+300ms 的边界实际取决于 Clock。
| 时刻/情况 | RenderTree changed | fade increment | Controller transition | Symbol R gate | 解释 |
|---|---:|---:|---:|---:|---|
| t0 新可见 Symbol | true | 0(静止 zoom) | true,fadeStart=t0 | false | 真实淡入开始;base opacity 可能0 |
| t0+33…267ms,不重算 | false | 约0.11…0.89 | **true,stale**(即使真实 Fade 早已结束也如此) | false | O:805-806 无条件 stale |
| ≥t0+300ms,重新计算且显隐未变 | true | 重新归零 | false(旧 fadeStart 已到期、setPlacement 清 stale) | false | 可出现现场 true/0/false,但 changed 阻塞 |
| 再下一帧,显隐仍未变 | false | >0 | true(重新置 stale) | false | 不是新像素变化的证据 |
| 后续每到普通 period 重算 | true | 通常0 | 若无新显隐变化则false | false | 循环重复,未提供 Build 窗口 |
| 新 bucket 触发30ms override | 到期时true | 新 commit 通常0 | 视 actual placed/Fade 而定 | false | 约30FPS时可能每次新增 bucket 帧均到期,需事件证据 |
| zoom-out、period≤帧间隔 | 可每次true | prevZoomAdjustment≥0,可非0 | 取决于真实Fade | false | 不使用固定300ms频率推断 |
### 7.3 transitions 关闭(及 duration=0 的区别)
**源码证明**:已有 Placement.transitionsEnabled=false 后,`placementIsRecent` 总是 false(:163),所以**每次到达 Continuous Full 都新建并执行 placeLayers**,即便 layersNeedPlacement 为空也调用函数;Controller.hasTransitions=false(:171),`symbolFadeChange=1`(:1289)。因此正常关闭配置是 **true / 1 / false**,不是 true/0/false。这并不意味着有独立线程每帧强迫 Full:必须先有 render request 且 Fast 失败。
若仅 duration=0 而 enablePlacementTransitions=true,recent 仍使用至少300ms(zoom/override 除外),fade=1;真实 Placement.hasTransitions 通常false,但 non-recompute Full 仍 stale=true。仍不能通过 Symbol gate。
**源码证明**:Android JNI `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp:1012-1017` 明确传 duration、delay、enable;iOS `platform/darwin/src/MLNStyle.mm:521-539` 可分别设置 transition 和 performsPlacementTransitions。当前任务没有生产 Host 此项逐帧配置证据;不能拿 SDK 默认值替代 FZG1 实际值。Android getter :1000-1008 对 unset duration 返回0,若只记录 getter 值还需区分“未设置的默认300ms”和“明确设置0”。
**对任务假设的明确判定**:`symbolFadeChange==0` 不能代表“Fade 已稳定”,此语义误读已被源码证伪;但缓存无窗口的充分原因是 changed/stale 的互斥准入,不是仅这个比较符。任务要求解释末态 true/0/false:该值与开启配置的稳态重算相容;在未改写字段的正常 Full 中,不支持直接解释为“关闭 transitions”。Fast 重置树字段为 false/0,而不是 true/0。若日志声称关闭且仍 true/0/false,应优先核对同帧采样、运行二进制/选项生命周期,不能改源码迎合假设。
### 7.4 真值表:P=!changed && fade==0 && !transition
下表针对 placementCapable=true;无 Symbol R 直接通过本 gate,但仍受其它保护。以 c=changed、z=(fade==0)、t=Controller transition:
| c | z | t | P | 正常 Full 可达性/意义 |
|---:|---:|---:|---:|---|
| 0 | 0 | 0 | 0 | Continuous 正常 no-recompute 分支不可达:会 stale;抽象状态不足以证明安全 |
| 0 | 0 | 1 | 0 | 开启且未重算、时间增量>0;Fade 活跃或已结束都可 |
| 0 | 1 | 0 | **1** | 唯一逻辑通过组合;**正常 Continuous Full 不可达**。已命中 Fast 的树重置可有此形态,但不能自举 |
| 0 | 1 | 1 | 0 | 同 timestamp 重复 render、增量仍0而 stale;可达也不通过 |
| 1 | 0 | 0 | 0 | transitions关闭(fade=1);或开启duration0/zoom修正且无实际Fade |
| 1 | 0 | 1 | 0 | 新显隐变化伴随非零zoom修正等 |
| 1 | 1 | 0 | 0 | 开启、静止zoom、重算但旧Fade结束;现场末态相容 |
| 1 | 1 | 1 | 0 | 开启、新显隐/Fade、刚commit |
Bucket 增减、跨段碰撞可能改变 `Placement::commit` 内部 placed 状态、重置 fadeStart;其是否真实改变 R2 需逐 Symbol 数据。即便完全不改变,也落入上述无窗口分支。没有 Source 变化也足以卡住;Source 变化不是这个机制的必要条件。
### 7.5 Shader 与 Full 后安全 Build 的必要证据
**源码证明**:`Placement::commit:694-724` 将上一 placement 的增量折入新的 OpacityState;`OpacityState:18-24` 保存 base opacity 和 placed 方向。`RenderTreeImpl::prepare`(O:133-138)→ `Placement::updateLayerBuckets:752-757` → `SymbolBucket::updateVertices`(`src/mln/renderer/buckets/symbol_bucket.cpp:346-366`)更新 opacity 与动态属性;`Placement::updateBucketOpacities:962-1029` 处理 duplicate、default、placed hidden 和属性编码。
`renderer_impl.cpp:462,755-766` 把树的 fade 送入全局 shader uniform。OpenGL:`shaders/symbol_sdf.vertex.glsl:153-158`、`symbol_text_and_icon.vertex.glsl:152-158`、`symbol_icon.vertex.glsl:127-129`;Metal:`include/mln/shaders/mtl/symbol.hpp:157-164,372-374,644-646`,都将 opacity 基值加/减 fade 并 clamp。Metal fragment :257-263,495-530 再乘 paint opacity;GL `shaders/symbol_sdf.fragment.glsl:77` 和 `symbol_text_and_icon.fragment.glsl:53-57,94` 也乘 paint opacity。零 fade 可以对应 base=0 的新符号或 base=1 的已完成符号,数值自身不足以判稳。
**条件推断(提案前提,尚非安全证明)**:可候选在完整 Placement 和 bucket 更新、shader 输入就绪后,使用一个与 Build 世代绑定的“输出已终态且不会仅随时间变化”证据,再建立 R2。必须同时覆盖 text/icon、duplicate、variable anchor/orientation、沿线重投影(placement.cpp:776-959)、paint opacity transition、glyph/image atlas、Tile/Source/camera/context、层序/深度/混合边界。仅 `!Controller.hasTransitions` 不足以替代这些证据,因 Controller 的 stale 是待重算语义;仅删除 changed 或把 ==0 改成≥1 也无法建立与缓存像素绑定的不变量。
## 8. E:S1—S5 反事实与因果闭环
### S1:仅粒子持续推进,所有静态输入就绪且不变
**源码证明/条件推断**:以任务要求的“仍有含 Symbol 的 R2、粒子30FPS”为前提,而不是把“纯粒子”误解为地图根本没有 Symbol。
- f0 无 prepared tree,必 Full;R2 gate 拒绝,R0/R3 若无 Symbol 且其余条件通过可 Build。
- f1 Fast 首先看到 R2 invalid;Full 中要么 changed=true,要么 stale=true,再次拒绝 R2。R0/R3 可 Reuse。
- f2…重复;启用 transitions 的重算频率由 period/zoom/bucket 决定,关闭时每次 Full 重算。**即使无任何外部 Source mutation,当前 Symbol R 仍无法从第一次正常 Full 自举。**
- 反例边界:若全部 R 都无 Symbol,单 C 只包含受支持的 WeatherColor/Particle,所有 Gate 满足,正常 Full 能建缓存、随后 Fast 可以命中;Placement 不需每300ms独立强迫新 Full。若 f0 还有真实全局 Fade,则等待必要 Full 结束后才可能命中。
- **仍需设备验证**:以上状态机不等于已测到30FPS、具体 CPU 成本或缓存最终像素正确。本轮未执行场景。
### S2:粒子关闭,只剩偶尔地图刷新
**源码证明/条件推断**:关闭 active/visibility 或清掉必要场数据会取消粒子 needsRepaint,#18 不能命中 Particle Fast。若同时停止 forecast、没有其它动画及 pending 资源,就失去高频 SDK wake/repaint 链;最终 loaded 且无 transition 可 Idle。期间每个真实 Full 仍进行 recent 判定,关闭 transitions 时每个 Full 都算 Placement,开启时仍可能用几帧完成 Fade/stale。
**FZG1 实测**:关闭粒子对照约83次Full/分钟来自任务输入。**不能证明**这83次都由导航、Placement 或 Tile 任一单源造成;也不能证明 Placement 从不运行。若只隐藏粒子却仍 forecast Playing,WeatherRuntime :252 仍唤醒,因此“粒子关=无天气高频唤醒”必须同时核对预报状态。83次与1792次的差异支持高频粒子场景增加 render 请求,但不提供精确按来源分摊。
### S3:真实 Host Source Mutation + Annotation + Tile 异步输入
**源码证明**:Style Source 更新先由 `src/mln/style/style_impl.cpp:348-351` 通知 onUpdate;新 sources identity 会令 Fast #8 失败。Full 的 O:525-537 每批非空 sourceDiff 推进一次 aggregate content revision,并为每个增删改 Source 推进 local revision。Tile callback O:1555-1559 则每次推进 aggregate/local 并 invalidate;Glyph/Image 无明确 source 时推进 global。Annotation dirty(`annotation_manager.cpp:213-227`)使 #24 拒绝,Full O:360-363 消费 dirty。
**理论量化(条件推断,非测量)**:
- 一批 Full sourceDiff 含 m 个 Source 修改:aggregate 增1、local advance m 次;m 不等于 m 个 Full。
- N 次独立 onTileChanged:当前实现 N 次 aggregate/local revision advance;若在下一 render 前合并,仅需要一次承载最新状态的 Full,若穿插 N 帧则可每帧拒 Fast。Fade、stale、继续到来的异步输入又可能延长 Full 序列,故不能用 N 推出总 Full=N 或每次 mutation 固定只需1帧。
- 已 valid 的无 Symbol R,若仅依赖 Source S,S 的新 revision 使下一次可支持的 evaluate invalidate/build;其它无 Symbol R 可复用。global epoch 变化则所有段的新 revision 都不同。每段 Build次数≤它实际有资格 Build 且绘制成功的 Full 次数;当前 Symbol R 无资格,所以 Build仍0并非 mutation太频繁的唯一解释。
- 只在 C 的 Source 变化可使整帧 Full,却不必令独立 R 的 local revision改变;碰撞关系仍可使 Symbol R 输出变化,不能以 source依赖表忽略跨段 Symbol。
**重要独立来源**:V4 forecast Playing 时 `Map::onUpdate` → `tickForecastClock` → `refreshV4RendererState`;后者 `raster_array_source.cpp:569-608` 在投影 sampleState/端点变化时换 Source impl 并通知,即使 T0/T1 纹理可复用,也可能令 #8 每次失败。这是 Native forecast 的真实输入更新,不等于静态风场粒子 advection,也不能无证据统归 NavSea 导航 Mutation。S1 必须固定 forecast 时间;S3 诊断应分别计 external Source 和 forecast projection。
### S4:布局对照,保留真实层序
| 布局及证据 | 分段 | R 缓存 | C / Fast 独立门槛 |
|---|---|---|---|
| 原 RC(STEP-110 现场历史) | R[0,31)、C[31,113),R含地名 Symbol | Symbol R当前无Build窗口 | 后段含大量普通业务层,若有普通 Symbol/Line,#22必拒;不能把 RC1/RC2 的所有字段与旧快照自动认作同一采样 |
| 原 RCRR(四段的泛型/候选基准) | 若为 R[0,31)、C[31,33)、R[33,107)、R[107,113),须由原始 plan 确认;**本轮任务未给该原配置完整边界,不将此数字当实测** | 首R和R2若含Symbol仍被阻塞 | 条件下 C仅 WeatherColor+Particle 则类型Gate可过,但 scene及所有R仍要过。泛型RCRR只说明策略排列,不保证边界和类型 |
| **RCRR_PREFIX(任务书现场输入)** | R0[0,13)、C1[13,33)、R2[33,107)、R3[107,113) | R0复用1787、R2不建、R3复用1755;与Full mixed执行相容 | C1含普通Symbol/Line+WeatherColor31+Particle32;`O:1247` 遇普通层返回false,#22确定被#3掩盖 |
| **理想保序反事实,未实施** | R[0,13)、R[13,32)、C[32,33)、R[33,107)、R[107,113) | 新R[13,32)及R2含Symbol,当前一样卡住;WeatherColor31也进入R | 恰好一个C且只有Particle可消除类型障碍;不消除Symbol Gate、scene、资源/source/annotation。若forecast/WeatherColor继续变化,新R必须重建/回退 |
`singleContinuousSegment` 在 `renderer_impl.cpp:55-63` 限定唯一 C;`RenderLayer::startsRetainedDynamicRange` 默认false(`render_layer.hpp:113-120`),Particle/WeatherColor 才 override true(对应 layer头 :31-33/:27),普通 Symbol/Line 即便暂时隐藏也不会因不可见自动成为受支持 C。多 R 本身不是禁止项;多 C 是当前 Fast 的独立阻塞。STEP-106 的 R-C-R-C 是缓存局部性测试,不是生产 Fast 性能证明。
**仍需设备验证**:补原始 RC1/RC2/RCRR plan JSON、113层完整 id/type/source/visibility/顺序,才能逐层定位实际布局;本轮没有跨项目改 Style。理想配置只做反事实,不能承诺 WeatherColor永远静态,更不能删海图Symbol或把普通层批量挪C作为优化。
### S5:动态安全回退
| 变化 | 必须处理的 R / Full 条件(源码证明+条件推断) | 不可盲复用的理由 |
|---|---|---|
| pan/zoom/rotate/pitch、viewport/pixelRatio | 屏幕缓存 key 改变的所有R重建;Fast camera/viewport/signature拒绝 | 旧屏幕纹理、anchor/reprojection不再对应当前相机;定图仍需碰撞正确 |
| 相关 Tile/source更新、缺失/fading | 依赖段 Source gate/revision失效;Fast aggregate/content/fading可能整帧Full | 异步完成意味着可见内容改变;非依赖段只在全局证据允许时继续复用 |
| Glyph/Sprite/Image/fontFace | 当前实现 global epoch及ready;通常所有R保守失效 | atlas/字形纹理可能变化,sourceReady不足以覆盖 |
| text/icon opacity Fade或paint transition | 受影响Symbol R不能冻结随时间变化的像素;现有全局Controller/gate更保守 | uniform与base opacity共同决定alpha;不能从fade=0/Controller=false单独得出结果等价 |
| 跨段Collision变化 | 所有碰撞影响到的Symbol R必须刷新;当前没有可靠per-R证据时保守回退 | source不等于segment;跨源关闭只隔离不同source,同source仍相互影响 |
| Annotation插入/更新 | 拓扑/Source/pending及碰撞具体属性分别处理 | 自动 points Symbol由 `annotation_manager.cpp:176-192` 添加,默认iconAllowOverlap/iconIgnorePlacement=true,**不能断言它一定排斥R2**;结构gate照样capable,实际影响还看其它Symbol/配置 |
| Style属性/顺序、Layer增删、Plan边界 | 本段fingerprint或全局topology变化;对应cache释放/重建 | 层序和混合语义不能被texture复用改变 |
| context/resource/compositor失效 | 已损坏资源全部重建;失败保持live/fallback | valid标志不能替代真实资源准备和draw成功 |
## 9. A—D 提案比较(均未实施)
优先顺序:**A诊断补齐 → B建立严格生命周期准入原型并同步处理scene → C按需求扩展per-R等价;D先交Integration确认公共“强制Full”语义,再决定实现**。C范围布局问题与这些方案独立:保序单粒子C是候选配置,任何产品Style变更都另行授权。
| 提案 | 首次Build / 全局scene / C Gate | 正确性与风险 | CPU / GPU / 内存代价 | 候选修改文件、复杂度、双平台与最小验证 |
|---|---|---|---|---|
| **A 全Gate+R2状态诊断,优先P0** | 不改变Build或scene/C结果;一次取齐被短路掩盖项 | 不改变fail-closed;读状态需同frame和phase,不能额外调用有计数副作用的needsRepaint造成伪观测 | CPU约O(现有gate数+R数),复用已算结果;固定环形缓冲有界内存;GPU零额外工作。采样成本须实测 | `particle_fast_path.hpp`、`renderer_impl.cpp`、`render_orchestrator.cpp/.hpp`、`placement.hpp/.cpp`、现有diagnostics结构;**2/5**。Shared Core同字段,Android/iOS只分别接低频导出。最小验证见第10章,记录schema无需新增公共API即可先做Native内部诊断 |
| **B Full后安全Build,优先P1** | **目标解决首次Build**;必须同时替换每段准入和prepared scene的旧证据,单改其中一处无效;不解决普通C层Gate | 保留全图Placement与全部资源保护,以已经完成的Symbol shader输入/终态证据授权。先shadow,不直接删除changed/stale/fade条件。危险为早冻结Fade、漏collision/atlas/paint状态、引用过期bucket | Full本来就做Placement,可在commit/bucket完成点记录代际,稳态尽量O(1)/段;首次离屏多一次R绘制,后续composite;每R现有全屏RGBA颜色约4WH字节,另depth/stencil和backend开销,更多R线性增加 | `placement.hpp/.cpp`、`symbol_bucket.cpp/.hpp`、`render_orchestrator.cpp/.hpp`、`renderer_impl.cpp`、`render_segment.hpp`、cache状态结构;按所需证据可缩减,非已定设计。**4/5**。GL/Metal shader不预设需要改;必须两后端同提交检查opacity精度和texture合成。最小:静止Symbol R开启/关闭transitions、首次可见、fade长尾、camera/context/资源失效,false-reuse=0 |
| **C 更细per-R结果等价,优先P2** | 可以为B提供首建/重建的证据;单纯“新旧签名相等”没有旧Build时不能自举;scene也必须接纳相同证据;不解决C Gate | 与STEP-111一致,仍全图碰撞,再按R比较结果;stable ID、世代和实际GPU输入齐全才允许reuse。hash相等不是完整安全证明;未知必须回退 | 每次Full placement后O(受影响symbol数),避免每粒子tick遍历;签名/版本及必要精确快照占O(symbol数)或O(段数+变化集);降低R GPU重绘但不自动省CPU prepare | `placement.hpp/.cpp`、`cross_tile_symbol_index.cpp/.hpp`、`symbol_bucket`、`render_layer`、`render_orchestrator`、cache/renderer接口。**4/5**;深改分段Collision为**5/5且不推荐本轮**。双平台共用结果证据、验证shader差异。最小:同Source跨R、跨源开关、C1仅影响R2/影响多个R、duplicate/variable-anchor/沿线字/Annotation/重载;以缓存关对照 |
| **D 外部Full与Particle Tick分流,Integration先行** | 单独不解决Symbol首次Build、scene或C类型;可明确某次更新必须Full,并在纯tick时优先Fast | 用户要求外部明确Full不能以数据相同去重/延迟;需明确序号、确认时点、合并规则、生命周期与平台一致性,禁止把重要资源更新当tick | 内部原因bit/epoch通常O(1);若每个命令都必须独立一次Full会增加CPU/GPU,额外队列可能有界内存需求。收益取决于频率,不能承诺2 Full+28 Fast | `UpdateParameters`、`Map::Impl`、Renderer入口、Android frontend、Darwin frontend及必要RN公共层;**3/5**(跨项目/生命周期可到4)。现有triggerRepaint不是此契约。最小:相同数据两次明确Full、命令与tick/异步tile竞态、禁用销毁重建、Android异步合并和iOS,检查每个accepted序号按决议完成;不得私建API |
### 9.1 B/C 的数据所有权与最轻量可行方向
**源码证明**:`Placement` 持有 placements、opacities、variableOffsets、placedOrientations(`placement.hpp:184-201`),以 crossTileID 索引;`getSymbolPlacement`(`placement.cpp:1301-1304`)查具体Symbol。`placeLayers` 后 commit 已完成,但最终bucket opacity/duplicate和动态投影更新在 RenderTree.prepare,GPU drawable/tweaker又在之后。`Placement` 至多保留上一代(:190-192),cache可能跨更多代存在,因此只能与上次成功Build版本绑定,不能仅比较相邻两次Placement。
`getPlacedSymbolsData` 对普通 Continuous Placement 返回静态空数组(:1325-1327),TilePlacement有专门实现(:1378-1387);不能把现有public查询当完整R2结果快照。STEP-111的候选字段仍需要逐项实现与验证,本任务不宣称已设计出可直接上生产的签名。
**候选轻量方案(条件推断)**:先争取“静态输入代际相同 + 完整Placement结束 + 所有目标symbol有效opacity已终态 + 无未消费资源/桶变更”的全局生命周期证书,授权该次Full离屏Build并保存buildEpoch;下次tick只比epoch和既有Gate。比每次tick全symbol hash轻。但必须证明碰撞/hidden/duplicate/variableOffset/paint动画均被证书覆盖;如不能低成本取得这一证明,继续fail-closed,退回shadow per-R比较,而非改用Controller.stale=false假装稳定。
更细C候选字段分工:Source/tile/bucket generation、crossTileID及R归属、text/icon placed/hidden、实际anchor/orientation/动态顶点和编码opacity需要结果证据;Camera/viewport/pixelRatio、Style顺序、Source/global资源代际优先复用已有key/revision;paint opacity/transition必须覆盖时间依赖。首次帧没有参考就Build,未知或资源重建丢弃证书,真实结果变化则重建。内存与比较开销需要新增计数验证,不能从“理论可hash”推出安全或高性能。
### 9.2 D 的 Interface Blocker
**源码证明**:当前 `Map::triggerRepaint`(map.cpp:93-95)只调用onUpdate;`UpdateParameters`(`src/mln/renderer/update_parameters.hpp:25-61`)没有 externalFull/原因序号字段;Fast签名不比较timePoint,入口每次都先试Fast。`packages/maplibre-particle-rn/src/RendererRuntime.ts:12-27` 只有retained控制、continuous range/segment plan和diagnostics,未提供“外部指令强制Full并确认”的方法。Android frontend AsyncTask/最新params设计还有合并行为,不能自动保证每次命令一帧。
**Interface Blocker(提案,不是当前调查阻塞)**:若用户希望公共Host“明确Full”命令具有不可被相同数据去重/延期的语义,Integration必须确认:完整更新是CPU prepare/全图Placement重新评估,还是强制所有R GPU重画;两个相邻命令能否同一Full满足、如何回执、如何与异步资源到达排序。既有C-001/C-002没有该公共语义。本轮仅只读 `/Users/tei/dev/navsea-particle-integration/contracts/PARTICLE_NAVSEA_BOUNDARY.md`、`CONTRACT_VERSION.md` 和COORD-005决议;C-001边界继续有效、C-002是QBIN窗口增补。不得通过Native私有通道绕过协调,未修改Integration,也未发送跨项目消息。
纯内部性能优化可先留在Shared Core,不需要借口更改Host职责;明确Full请求一旦获授权,不能用“内容相同”跳过或推迟它,普通粒子tick则优先Fast并保留相机/资源/Placement等安全Full。**2 Full+28 Fast只是目标例子,非当前实现承诺或验收门槛。**
## 10. 下一阶段最小诊断提案与独立验收证据
**仅提案,未插桩、未执行、未创建下一Step。** 当前源码已能证明控制流矛盾;诊断用于确认FZG1实际选择的配置/路径、其它Gate和主要CPU成本,并为安全改造建立基线。
### 10.1 同帧字段与采样点
| 位置 | 最小字段 | 判别目的 |
|---|---|---|
| Map::onUpdate / SDK cadence wake | map generation、update/request序号、SDK wake/observer repaint/Host mutation/forecast tick来源、targetFps、active/visibility、forecastPlaying、sourceId、Clock时间 | 区分下一帧请求与外部数据更新;调度来源标识仅Native内部提案,不私建公共命令 |
| I:199-278,Fast判定前 | frameId、UpdateParameters.timePoint、实际render时间、24项bitset、首错、changed signature字段mask、prepared tree/scene标志、所有R valid/buildRevision/staticRevision、config/topology/content revision | 一次记录所有Gate;多request合并和同一params重渲染可识别;避免只看cache-invalid |
| O:778-810,Placement前后 | **当前options与旧Placement options** enable/duration是否unset/delay、commitTime、fadeStartTime、now、zoom/prevZoomAdjustment、override及原因、bucketAdded/Changed、recent、stale前后、outer changed、commit实际placed变化计数、raw fade和Controller/Placement各自hasTransitions | 验证开关分支、300/30ms和zoom影响;区分实际Fade与stale |
| RenderTree.prepare后 / I:637-691 | tree fade、shader fade、每R capability、动态usesPlacement、placementGate、sourceReady/stable、fingerprint、content revision、supported、fallback各项bitset、cache前状态、evaluate decision | 确认是否Gate阻止首次Build、是否其它失败被placement reason遮蔽 |
| cache Build/execute / I:813-862,998-1028 | BuildPending/成功/失败、valid/key/资源世代、texture/compositor create/release、build/reuse/invalidation/fallback累计及delta、actualExecution、globalLiveReason、GPU pass耗时(如平台已有可靠计时) | 拒绝Build与GPU失败分开;Reuse与实际composite分开 |
| frame完成 | FullCPU/fast/simulationAdvance计数、createTree/placement/prepare/layerUpdate/encode耗时、repaint原因bits、loaded/Idle | 不能用Full计数代替Placement次数或simulation次数 |
字段不存在时新增只读诊断accessor应另行授权;不可通过额外一次placeLayers获得“测量值”。重用本来算出的gate bool与needsRepaint值,避免观察过程增加计数或推进cadence。
采样建议:每组先10秒预热、再60秒统计;前3秒及状态变化前后保留连续逐帧详情,60秒完整保留紧凑gate/段决策和计数;用固定容量Native环形缓冲、超限显式记录dropped计数。日志导出1Hz摘要或窗口结束统一写,不每帧JSON/JS bridge、不每Symbol默认打印。以实测结构大小和容量给出内存上限;对trace开/关同场景比较CPU/frame,目标开销预算先建议≤1%,达不到则缩减详细采样但保留全部gate bitset。这个预算不是当前已达到指标。
### 10.2 最小场景与判断
1. FZG1原V23、同场/同camera:粒子开/关,分别固定forecast暂停;保留原plan和所有113层元数据。复现计数只用于基线,不要求改变现有结果。
2. 同输入分别enablePlacementTransitions=true/false;true下记录D=默认/显式值,false预期Full组合true/1/false。每个Symbol R应在每个Full看到gate=false;若有正常Full gate=true,是本报告不变量反例,应保留原始帧/版本并停止假设式修复。
3. PREFIX,记录#22普通层第一个拒绝的id/type/index,同时#3失败;原RC和真实RCRR分别保留配置,不复用旧布局推断。
4. 无外部Mutation的固定field、forecast暂停,与导航/Annotation更新、Tile完成、forecastPlaying分别对照,按来源和phase对齐revision。不得靠降低粒子FPS制造好看CPU。
5. 后续B/C shadow原型才验证:首建可达、fade长尾/variable anchors/跨源碰撞/同源跨段/相机/Style/清场/context;缓存开关同帧结果对照,**false-reuse=0**,不能用总FPS掩盖陈旧字/图标、错层或缺字。Android OpenGL和iOS Metal同源码各跑,不能用一端替代另一端。
独立验收需要:源码commit及制品SHA/实际SDK版本、plan JSON与Style层表、全部gate bitset、Placement两套配置和时间、R状态机计数、trace开关开销、CPU阶段耗时、同帧视觉/必要图像差异证据、原始日志路径。任何提升Fast命中/CPU降低数字必须来自新实验。本轮不提供尚不存在的诊断开关命令,也不执行构建、测试或设备动作。
## 11. 最终结论、未确定事项与责任边界
### 11.1 已证明
1. **源码证明**:粒子/SDK只请求下一次正常地图渲染;每次先试Fast,没有粒子专属“必须Full”命令。当前Full CPU频率是请求频率与准入失败共同作用,不等于静态像素每帧变化。
2. **源码证明**:正常Continuous Full对结构性Symbol R的三项gate无可达通过分支:changed或stale→transition二者必挡其一。它足以解释在静态输入下仍无法首次Build;不是仅仅“可能比较符用错”的猜测。
3. **源码证明**:fade=0不是终态证明;关闭transitions正常返回1。FZG1 true/0/false快照不能用来断定关闭配置或每帧300ms周期。
4. **源码证明**:还有prepared全局scene锁与C范围类型锁;PREFIX普通Symbol/Line C不支持Fast。仅修R2不能承诺Fast命中。
5. **源码证明**:缓存invalidate计数不要求曾valid;Full可复用其它R并live画R2,正常主屏显示不等于离屏Build成功。
6. **源码证明**:V23相关核心gate与本轮main一致;STEP-109仅改变两Layer的field深比较快路,不解除以上限制。
### 11.2 假设判定与历史报告关系
| 假设 | 判定与证据强度 |
|---|---|
| 粒子每次tick直接改变静态R内容 | **不成立为必然关系,源码证明**;纯simulation不改R,forecast/Host更新另算 |
| C1每帧碰撞使R2符号真实变化,才导致Build0 | **未证实且非必要条件**;gate控制流在零mutation也不可达 |
| `symbolFadeChange==0`等于Fade结束 | **源码证伪**;它是增量,不是稳定布尔量 |
| FZG1 true/0/false证明关闭transitions | **源码不支持**;关闭的正常Full为true/1/false |
| 删除placementChanged或改==0即可安全修复 | **不支持**;stale/scene/C/资源/碰撞/opacity仍独立存在 |
| 现有Gate是合理保守且没有任何进展性问题 | **需细化**:fail-closed保护正确,但正常Symbol R Full首次Build不可达是可证的功能/性能准入限制;是否按产品契约命名为Bug由后续设计确认,不是视觉错误或可直接放宽安全的证据 |
STEP-110/111保留原样。它们强调“来源不等于视觉影响、缺逐R安全证据”的结论仍成立;本轮新增的是 **changed/stale互斥导致准入不可达** 的完整控制流证据,不能把旧PARTIAL说成已实现修复,也不重写历史来伪装一致。
### 11.3 尚不确定 / 仍需设备验证
- FZG1每一帧enable/duration、bucket添加、zoom修正、调用来源和24项Gate真实值;末态仅一个快照。
- 原RCRR完整边界和RC1/RC2精确同版Style列表;本报告不把推测布局写为实测。
- 导航Source、Native forecast投影、Annotation、资源异步输入各自占多少Full/CPU;83与1792不是按来源归因数据。
- R2每个Symbol实际最终输出是否与某个候选缓存时刻完全等价、可接受证书的充分字段及实现成本。
- B/C实现后的首建、Fast hit、CPU/GPU/内存收益、GL/Metal像素一致性和安全回退;本轮都没有实现或运行结果。
**交付判定:本轮A—E源码调查与提案已完成;综合结论保留PARTIAL(生产逐帧归因、完整原始布局及安全修复/性能未验证),不标实现/运行/性能PASS。** 已有决定性源码机制证据,不再以猜测“等Fade结束就可能Build”作为建议。下一阶段最小工作是A的同帧诊断和B的安全证据设计;维持现有fail-closed,不直接修Native。
Codex调查阶段只交付四份文档,不承担Git操作;STEP-112任务书第十一节要求RDC独立进行仅文档scoped提交、推送和远端回读。Git最终状态以RDC收尾补记为准。

View File

@@ -0,0 +1,78 @@
# STEP-114 Fast Path 安全准入与闭环判定 v1
日期:2026-10-11(JST)。结论:**PARTIAL;安全子集源码与独立最小场景已交付,真实 Fast 尚未验收。**
## 1. 基线、证据等级与阅读
Mac / Darwin 24.6.0 / x86_64,`/Users/tei/dev/MapLibreParticleLab`,`particle/main@b02e54de3d45fcf612126ae8f037cba204f590ea`。源码版本 `13.6.1-particle.23`,没有发行或改 Source Lock。实际模型 GPT-6.1-Sol / high 已从当前本地 rollout 的 `turn_context` 核实。
已读根 AGENTS、PARTICLE_BASELINE、项目规范全部八份、项目总则 README 与四份有效总则、本任务、STEP-112 调查/完成报告、STEP-113 完成报告/Step(含两轮 RDC 追加)、实际 Shared Core 和 Test App 源码。Integration 只读 AGENTS、README、WORKFLOW、TEAM_MODEL、C-001/C-002、Contract 版本规则、RFC-001/002/004、ADR-001、COORD-005 决议;不写跨项目文件,不改 RN/JS API/ABI。
SOURCE-PROVEN 表示本轮源码控制流;RETAINED-EVIDENCE 表示本机保留的旧 RDC JSON;IMPLEMENTED 表示已改源码;NOT RUN 表示未编译/运行。旧设备证据不是本轮改动的设备验收。
## 2. R 的真实 SourceType 与初建失败点
不能从 `unknown-dependency` 推断 source 一定是 CustomMVTVector:它还覆盖未知依赖、缺 revision 登记等情况。
本轮只读 `artifacts/maplibre-particle/android-validation/step113/source-change-after-full.json`:Native source 表为 mapple/raster、navsea_delivery/vector、org.maplibre.annotations/annotations、rn-weather-fields/raster-array、step113-source-probe/geojson。R0 `[0,63)` 的依赖为 mapple、navsea_delivery、**tide**;第2层 `tide-spots` 是 `symbol`、sourceId=`tide`,source 表却没有 tide。R0 的 sourceRevision=0、sourceReady=false、Build=0、fallback=unknown-dependency。
源码 `style/conversion/source.cpp::convertVectorSource` 创建 `VectorSource`;其构造函数指定 `SourceType::Vector`;`RenderSource::create` 创建 `RenderVectorSource`。因此该 Test App 的正式 PBF source **不是 CustomMVTVector**。任务书列出的 CustomMVTVector 限制是独立安全约束,本轮完整保留。没有改旧报告,也没有补造 tide 数据、删原正式 PBF 层或修改服务 Style。
`retainedSegmentSourceState` 原先先读取 revisionFor,缺少 tide 的登记会返回 nullopt,从而提前返回默认 unknown-dependency。现在在读取 revision 前发现缺失 RenderSource 时明确返回 `missing-source`;本例的ready/stable=false、revision=0 不变。有旧tombstone记录的缺源也提前返回revision=0,而不暴露旧登记hash;它仍不得Build/Reuse,RevisionBook登记机制不变。这是诊断修正,**不改变缓存准入**。source 存在但无 revision、CustomMVTVector、未知 layer dependency 仍 fail-closed。
后续即使 Host 合法补齐 tide,R 仍包含大量 Symbol,Placement 首建阻塞不会自动消失。缺源与 Placement 是不同失败点。
## 3. 已支持的本地 GeoJSON/circle 子集
FAST_SMOKE 选择本地 GeoJSON 非空点与 circle R,不新增 source 类型授权。其现有追踪链:
| 输入或事件 | 源码链与保护 |
|---|---|
| GeoJSON setGeoJSON | `GeoJSONSource::setGeoJSONData` 替换 immutable Impl 并通知 SourceChanged;Fast non-camera signature 比较 sources;Full `diffSources` 推进全局 content 和本 source revision |
| Source 增删/同ID重建 | `createRenderTree` 的 sourceDiff added/removed/changed 都 advanceSource;RevisionBook 保留 tombstone;PreparedTree 在 Full 更新对象前释放 |
| 异步 GeoJSON 数据/布局 | `RenderGeoJSONSource::update` → GeoJSONTile::updateData;capturedData 和 weakPtr 防陈旧/销毁回调;GeometryTile correlationID 保护 pending;`onLayout` 每次更换 layoutResult 都 onTileChanged;RenderSource → orchestrator 推进 source-local revision 和全局 content,再 invalidate |
| Tile 未就绪/淡入 | `isLoaded`、hasFadingTiles、source assessment、#17 与 R 稳定性检查继续保留 |
| Layer paint/layout/顺序 | immutable layers signature、per-R content fingerprint、resolved plan/topology;有 transition/repaint 仍阻止 |
| Camera/viewport/pixelRatio | #6/#7/#8、RetainedUnderlayKey 与 revision state;旧 texture 不能按新矩阵复用 |
| Style reload | styleLoaded/signature、配置/层序/Source 新身份、PreparedTree 生命周期保护;trace 在实际看到 loaded→false 时重置 |
| Image/Sprite | imageDiff、spriteLoaded、imageManager readiness、missing/unused image callbacks 推进全局 revision;#8 先捕获新 images 输入 |
| Glyph/fontFaces/URL | non-camera signature、glyph URL/font identity 追踪、onGlyphsLoaded 推进全局 revision;不删除资源门禁 |
| Annotation | #24 pending 检查、source/tile/图片变化与层表;实际 Symbol 继续纳入全场结构检查 |
| context/reset/destroy | markContextLost、Renderer.clearData 清 PreparedTree/trace,orchestrator 推进 revision;析构 detach observer 前排空 source/tile 工作;Test App 取消 callback、dispose runtime、删除自有 marker,MapView 原生命周期仍由 Activity 执行 |
这一闭环是现有实现的源码依据,**不是本轮已跑出的 Source mutation/GPU PASS**。异步过渡中 Full 也可能继续展示尚未更新的旧 bucket;要求 Fast 与同一时刻合法 Full 等价,而非承诺提交 API 后同步完成画面更新。成功布局后必须失效并正确重建;不可用两张稳定末态截图代替事件顺序证据。
R source policy、RevisionBook、GPU Build/Reuse 算法本轮零语义修改。新增 C++ 用例验证既有 revision/cache 状态机组合;unit 中显式推进 epoch/标记构建只用于状态机测试,不是实际 Native 帧或 GPU 成功的证据。
## 4. C、Annotation 与 Scene 聚合
`continuousRangeSupportsParticleFastPath` 要求每个 C layer 都 startsRetainedDynamicRange,并至少有粒子动画层。WeatherColor 与 RasterParticle 符合;普通 circle、Symbol、内建 Annotation Symbol 不符合。`particlePreparedSceneSupported = retainedSupported && fastContinuousSupported`,并要求单一 C。#22/#23 可以共享 C 不支持根因。
本轮没有修改 #22/#23、C GPU 复合流程、层序或 AnnotationManager。不能证明非粒子 C 与既有 Fast prepare/postRender 等价,故不放行。
独立 FAST_SMOKE 在确认 `map.annotations.isEmpty()` 后,仅从本场景 Style 删除自动生成且空的 `org.maplibre.annotations.points` layer,保留内建 source;若缺层或 remove 失败则 BLOCKED。Native annotation 更新规则未变,正式 PBF 五场景不删除该层。baseline 层序应为 background、R circle、weather-color、raster-particle,实际 JSON 仍需 RDC 核对。
ANNOTATION_BLOCKED 重新加载一个正常 Style,保留自动内建层并用现有 `map.addMarker` 加真实 marker;可单次修改其 position。内建 Symbol 位于 C,#22/#23 必须继续阻止 Fast。从该场景切回 baseline 时只删除本场景拥有的 marker,不能清除其他 Annotation。若存在其他 Annotation,baseline 拒绝启动。该场景转换含合法 Style reload,不是生产 Annotation 零重载支持的证明。
## 5. Placement:安全局部修复与 BLOCKED
正常 Continuous Full:placementIsRecent=false 则 placeLayers/commit,placementChanged=true;recent=true 则 setPlacementStale,controller.hasTransitions=true;关闭 transitions 时正常 Full changed=true、fade=1。含结构性 Symbol 的 R 无可证明的首次 Build 稳定窗口。
本轮保持 Symbol R 的三项 gate、scene placementStable、全图碰撞、fade/transition 和跨 source 规则。完整 GPU symbol 输入、bucket/atlas/opacity/duplicate/anchor 的结果证书仍不存在;建立它需要独立结果签名或更大生命周期设计。**Symbol R 准入修复 BLOCKED,不能删除 changed/stale/fade 条件。**
唯一行为修复限定于**完整非空层表没有任何结构性 Symbol**:此时 controller 的 stale/transition 没有 Symbol 像素消费者,单独阻止 #13 不代表画面可能变化。`particleFastPathHasPlacementTransition` 按完整 Style 表检查;隐藏/空数据的 Symbol 与 Annotation Symbol 一律保守保护。production 还核对层表数量与 renderLayers 数量,不一致保持原 transition;空表也保守保留。
这是 Shared Core 内部实现优化,无 Android/iOS 分叉,不新增控制 API、不禁止正常 Full Placement、不修改原始 controller。其余23项 Gate、source/cache/scene 条件全部继续生效。新增 Symbol 尚未进入旧表时,immutable layers 的 non-camera signature 不相等,且 Annotation pending 单独阻止 Fast;Full 更新后表重新包含 Symbol。未取得新层表前不得只凭这个 helper 放行。
反例边界:同场只要存在一个隐藏 Symbol、空 Symbol、C Annotation Symbol,仍使用原 controller transition;缺层表、source mutation、未完成 tile layout、image/glyph 更新、camera/viewport、style reload、debug/context 问题都不能被本修复抵消。未来若引入新的 Placement-capable layer 类型,应先扩展结构检测及测试;当前源码仅 Symbol 的 LayerTypeInfo 使用 CrossTileIndex::Required。
## 6. 实施结果、验证方案和限制
- IMPLEMENTED:缺源明确诊断、无 Symbol 场景的 #13 限定检查;3个非 Jest C++ 用例源码;独立4场景及 UI/Intent、真实 Native 快照与操作前后自动采样;中文文档和追加记录。
- 保留 fail-closed:CustomMVTVector、未知 revision、缺源;非粒子 C;所有 Symbol R Build/Placement 保护。
- NOT RUN:Native/Kotlin 编译、C++ 测试、Android build/设备/GPU/Fast 命中、像素对照、性能、iOS Metal 设备验证。
- 保留:正式 STEP-113 五个 PBF 场景实现原样、STEP-102 fixture 原样、STEP-110/111 未跟踪文件原样。新增菜单明确标识独立 smoke,不冒充63/113层等价。
验收步骤和命令见 `docs/60_测试/STEP-114_独立FastSmoke场景与RDC验收_v1.md`;Native 定向命令也直接写在本轮完成报告。baseline 必须取得真实 Native Full>0、Fast 增长、CacheBuild>0、Reuse 增长、completed fast-rendered、24 PASS/knownMask 完整/decisionMismatch=0;否则维持 PARTIAL,不能标 Fast Path 已修复。
Source/Image/Camera/缓存禁用/Reload 的反向证据需保存事件前/提交后/100/500/1500ms 快照,必要时以更早的精确帧复核;未捕获瞬间 gate 不能用计数猜测。后台/恢复/Teardown、Symbol/Annotation 与原 PBF 回归、双平台共享源码独立检查仍为 RDC/用户待执行项。

View File

@@ -0,0 +1,73 @@
# STEP-116 STEP-113 原 PBF Fast Path 阻塞安全修复审查 v1
日期:2026-10-11(JST)。结论:**部分实现(PARTIAL);空 Annotation 生命周期受控修复已落源码,缺源和 Symbol R 准入继续 BLOCKED。没有新的设备 Fast 证据。**
## 1. 当前事实与范围
仓库 `/Users/tei/dev/MapLibreParticleLab`,`particle/main@e2d202b560ad0165101546597c43f2ef0ccf4542`,Darwin 24.6.0 / x86_64。起点 tracked clean,只有 STEP-110/111 和本次 STEP-116 任务书未跟踪。版本仍 `13.6.1-particle.23`,不能用版本号相同推断 APK 源码相同。Integration main `80667d6c343411d03a760425438e3d292f452118` 为用户提供的 RDC 核验事实,本轮只读相关规范,没有 fetch 或跨仓写入。
已读取 AGENTS、PARTICLE_BASELINE、项目规范全部八文件、总则 README 和四份有效总则、STEP-116 任务及 STEP-112/113/114/115 当前记录/报告和相关源码;Integration AGENTS、README、WORKFLOW、TEAM_MODEL、contracts、rfc、adr、instructions、status、compatibility 只读。本轮不使用子代理,不修改公开头文件、公开 API/ABI、Host/RN/JNI 控制协议、NavSea 或公网 Style。
## 2. 缺源 R:证据成立,空依赖准入不实施
保留文件 `artifacts/maplibre-particle/android-validation/step113/source-change-after-full.json`,SHA-256:
`24a06fa1cb072a628c650723c58a880895ec656f4972fa55d3cb064bfb2aabda`
Native 原层 index=2、ID=`tide-spots`、type=`symbol`、source=`tide`。R[0,63) 依赖 `mapple/navsea_delivery/tide`。Native 实际 Source 表是 `mapple:raster`、`navsea_delivery:vector`、`org.maplibre.annotations:annotations`、`rn-weather-fields:raster-array`、`step113-source-probe:geojson`;没有 tide。该文件属于旧 SOURCE_CHANGE 完整窗口,不冒充本次 STATIC_RC 实测。原记录中 fallback=unknown-dependency;当前 STEP-114 代码已提前报 missing-source。
`RenderOrchestrator::retainedSegmentSourceState` 在查 revision 前先检查缺源及 CustomMVTVector;普通 Vector 不走 CustomMVTVector 拒绝分支。`renderSegmentDependencies` 不因隐藏或当前桶为空删依赖。`RenderSegmentRevisionBook::revisionFor` 对未登记来源返回空值,保留删除墓碑用于同名重建;墓碑存在不能代替当前 Source 的 readiness。
源码链路审查:
| 链路 | 当前实现与限制 |
|---|---|
| 零贡献候选 | createRenderTree 的 source/layer 匹配循环不会给缺源层生成 LayerRenderItem,updateList 默认 false;这支持“本轮没有对应新 RenderItem”,不等于全部旧 GPU 对象已完成禁用的证书 |
| 旧 GPU 生命周期 | RenderLayer::markLayerRenderable 调用 activateLayerGroup 提交 ChangeRequest;Symbol 还处理 collisionTileLayerGroup,layerRemoved 停用两类 group。CPU 状态、待处理 requests、旧 buckets 与实际执行时点需要一起核验;无 source 的循环边界也不能只靠 bool 推断 |
| Source 创建/删除/同名恢复 | createRenderTree 的 sourceDiff 递增全局内容及对应 source revision;prepare 输入的 immutable sources 身份变化使 NonCameraSignature 不匹配,必须先 Full |
| 同名更新/异步完成 | changed sourceDiff、onTileChanged → TileMutation → 内容/source revision + observer invalidate;feature-state 同样走 source 变更,不能按 HTTP 下载或 loaded 时间代替 |
| 全局输入 | layerDiff / 层序、imageDiff、Glyph URL/fontFaces、onGlyphsLoaded、Sprite/ImageMissing、context/style cleanup 保留原失效链 |
因此本轮**没有把 missing-source 当 empty**,没有登记虚构 tide revision,没有补假源或删除 tide-spots。后续需要“该 R 在成功 Full 中实际参与 GPU 绘制的 group/碰撞 group 已排除、无待应用资源变化”的有界证据,以及 source 出现/恢复后下一次尝试必定 Full 的回归。当前仅 Source 表缺失不足以承担这项新准入契约。服务器正式 Style 缺源仍是 BLOCKED。
本轮只在现有诊断 JSON 的每段增加可选 `missingSourceIds`,由同次 snapshot 的依赖和 Source 表计算,包含缺失来源的具体 ID。查询不写 revision、不构造缓存、不修改首错和 24 Gate。
## 3. 空 Annotation C:局部修复与保护
旧 STATIC_RC Native 66 层:原63层 + weather-color + raster-particle + 自动 points Symbol。C 从 rn-weather-color 开始,普通 Symbol 不支持 particle dynamic range,阻挡 #22,并影响聚合 #23。SOURCE_CHANGE 的额外 circle 也必须保留。
仅在 Step113PbfGateScene 的 STATIC_RC 第二次 Style 加载完成时执行一次空层调整:
1. 生产基础 ID 不含 points,生产Sources不得占用org.maplibre.annotations;SDK `map.annotations` 必须为空;实际层必须是 SymbolLayer、sourceId 为 org.maplibre.annotations,内部 Source 必须存在。来源由本次原始JSON无内部ID、Native加载路径与实际层共同核对,避免把生产同名Source误当空SDK来源。
2. 使用 `Style.removeLayer(Layer)` 对象重载,保留可重加的 wrapper;Source 保留,原63层定义/顺序及生产 Sources 原样。
3. 记录 `emptyBuiltinAnnotationLayerRemoved`、实例数0、Source保留、移除层ID/作用范围,保存 productionSources;poll/export 继续使用真实 Native 计数。静态预期正常 STATIC_RC 65 层,不能当实际计数。
4. 不周期删除任何层。后续普通 Style.addLayer 保持原行为,native immutable layer/signature/topology 变化继续 Full;Style 更换后不对新 Style 再做移除。
5. stop 在相同 Style 且该 ID 仍缺失时重加原 wrapper,已有同名层不覆盖。Style 身份改变或 Annotation/points 重新出现时,3秒 poll 撤销场景验收条件并报 BLOCKED;poll 是报告保护,不承担恢复可见内容的时序保证。
关键修复位于内部 `src/mln/annotation/annotation_manager.cpp/.hpp`:原 updateStyle 只在 Source 不存在时创建 points,Source 保留而层被删除会让后续 Marker 无绘制层。提取私有 ensurePointLayer,独立检查 Source 和层;首次添加 SymbolAnnotation、更新已有 SymbolAnnotation、Style loaded 三个入口修复缺失层。更新没有 geometry/icon 变化时也把 dirty 置真。已有同名 Layer 不被覆盖。
时序:Map::addAnnotation / updateAnnotation → AnnotationManager 修复 Style layer vector → Map::Impl::onUpdate 捕获新的 immutable layers → Fast NonCameraSignature/annotationMutation 拒绝旧树 → Full 执行正常 Annotation 渲染。恢复不依赖3秒poll,也没有在 renderer thread 的 updateData 内重写 Style。Shape Annotation 原接口/图层仍按原逻辑处理,碰撞、Fade、Glyph/Image 和 C 判断完全保留。
风险:这是 Shared Core 内部生命周期修复,所有后端共用;需 RDC 编译并跑真实图像用例,确认无锁/重入问题及旧 Annotation 行为回归。用户主动提供同名层时尊重其语义;若其配置本身无法绘制,该情况不被当作 Test App 空层安全条件。恢复失败在日志保留,不写成功。
## 4. Symbol R:首次 Build 仍 BLOCKED
已读 PlacementController、Placement::placeLayers/commit/symbolFadeChange、RenderTreeImpl::prepare、SymbolBucket::updateVertices、renderer Full 准入和 scene 聚合。
| Continuous Full 分支 | 当前 Symbol R gate |
|---|---|
| placement 不 recent | placementChanged=true;placeLayers 后 commit、setPlacement,changed 本身阻断 |
| placement recent | setPlacementStale;hasTransitions 返回 true,阻断 |
| transitions 关闭 | placementIsRecent=false,Full changed=true;symbolFadeChange 返回1,不是 settled=0 |
RenderTreeImpl::prepare 在 bucket updateVertices 中更新 opacity/dynamic attributes,并置 uploaded/dynamicUploaded/placementChangesUploaded 等状态。Placement commit 只提交 CPU placement/opacity 结果,不证明当前 R 的最终 GPU 输入一致。per-R `renderSegmentPlacementGatePassed` 和 `retainedSegmentIsStable`,全局 retainedPlacementStable / retainedSupported / preparedSceneSupported 均保留。隐藏/缺源/当前 placementData 为空的 Symbol 仍结构上具备 Placement 能力。
**需要的额外设计证据**:成功 Full 对应的 GPU 上传/绘制完成代次、R 使用的 bucket/tiles/跨瓦片ID与排序、跨Source碰撞与所有参与碰撞层的 Placement结果、text/icon opacity和dynamic vertices、atlas/glyph/image代次、Style/Source/layer identities与revision、camera/projection/viewport/pixelRatio、uniform fade及debug/3D条件、待处理requests/异步变更纪元。证书必须绑定一次成功 Cache Build,变化即 invalid/Full,并对 GL/Metal 验证等价及 shadow。现有代码没有完整证书,本轮不做跨层重写,不用毫秒阈值、loaded、无手势或counter装配代替。
## 5. 测试源码与结论边界
新增四个 `Step116Safety.*` C++ 用例:缺源/Source加入未loaded/异步revision/删除墓碑和恢复;JSON缺源审计与Placement并存、查询无副作用;Symbol两个Continuous分支及关闭transition保护、无变化/Source与全局资源revision;Annotation/层序/disable/reload多失败 shadow 全集。revision用例验证的是账本契约,不伪造实际 Glyph/Tile 回调或 GPU完成。
新增三个 `Annotations.Step116*` 用例:空points移除后真实添加在render前恢复并比对既有point_annotation图像;已有Marker no-op update恢复且出图;已有同名层不覆盖、reload恢复且出图。这些是测试源码,全部 NOT RUN;设备的annotation/Style、async mutation、GL/Metal回退还须独立验收。
本轮实现后预期消除原 STATIC_RC 的空内部 C Symbol;无法承诺 #23 清除,也无法承诺 R Build/Fast>0。旧快照、AST或diff检查均不构成新制品安全、像素或性能 PASS。

View File

@@ -0,0 +1,137 @@
# STEP-117 PBF R 首次缓存 Build 安全证据 v1
日期:2026-10-11(JST)。结论:**安全准入 BLOCKED;本轮保持 missing-source 与 Symbol placement fail-closed,交付七项非 Jest C++ 回归源码。没有实现 R 零贡献准入或 Symbol 结果证书,没有新的 Native/Android PASS。**
## 1. 工作区与证据等级
Mac.lan / Darwin 24.6.0 / x86_64,仓库 `/Users/tei/dev/MapLibreParticleLab`,`particle/main@43e5f5b91598b9c3d3f1c788052d4bbd2d616668`。起点 tracked clean;STEP110/111 与 STEP117 任务书未跟踪。实际模型 GPT-6.1-Sol(gpt-6.1-sol)/ high,由当前 rollout `01a12912-9a0c-7712-9624-793f04b3f681` 的 `turn_context.model/effort` 核实。Integration 本地只读 HEAD 为 `80667d6c343411d03a760425438e3d292f452118`,没有跨仓写入或远端操作。Android VERSION 仍 `13.6.1-particle.23`。
已读根 AGENTS、PARTICLE_BASELINE、全部项目规范、总则 README 与四份有效总则、STEP117 任务、STEP112–116 关键任务/调查/Step/完成报告/验收文件;Integration AGENTS、instructions、README、WORKFLOW、TEAM_MODEL、contracts/rfc/adr/status/compatibility。本报告用以下等级区分事实:
- **源码事实**:当前 HEAD 的控制流和所有权;不等同运行安全证明。
- **保留实测**:STEP116 原设备 JSON 与报告;不是 STEP117 新二进制。
- **测试源码**:本轮已写、全部未编译和未运行。
- **待证明设计**:尚未实现的证书、影子比较与平台验证。
## 2. 原 120 帧重新核对
原文件:`artifacts/maplibre-particle/android-validation/step113-retest-20261011_121308/static_rc/native-full.json`;274,693 字节;SHA-256:`cd491d2acca97119f1d4f2d6f77eb7b229c17b90d4be1181a59202308429b219`。本轮只读解析完整 JSON,逐帧复算 failMask,不修改或纳入 Git。
实测源码 `c48a72a9d492ea0d05e4d58464f9643453158d50`,APK SHA `a8b7f4e0f8e026bde8835e18a04876062be84cce3a3749a511cb08605d9f8a35`,设备 FZG1-2 / `192.168.200.139:5555`。STEP116 报告记录安装 SHA 相同、90.008 秒 FastDelta=0 / FullDelta=920。本轮未接设备。
原正式 63 基础层保留;Native 65 层,R[0,63)、C[63,65);C 仅 `rn-weather-color`、`rn-weather-particles`。正式 Style fingerprint 为 `e0f9c2d1df38ae5d5953b561c52efe2f8590336de80b988e1d2935580d30b996`。生产 sources JSON 只有 mapple/raster 与 navsea_delivery/vector;Native 另有 org.maplibre.annotations/annotations、rn-weather-fields/raster-array。没有 tide。R index=2 的 `tide-spots` 为可见 Symbol,source/source-layer 均为 tide。
| 实测项 | 复核结果 |
|---|---|
| 120 帧范围 | sequence 5631–5750;119 completed Full / 0 Fast / 1 pending |
| 24 Gate | 120/120 knownMask=0xffffff、decisionConsistent=true;decisionMismatch=0 |
| #3 / #4 / #23 | 各失败120帧 |
| #12 / #13 | 各失败105帧 |
| #22 | 最近窗口失败0;生命周期累计6,不称启动全程零失败 |
| R cache | Build=0 / Reuse=0;textureCreate=0 / compositeCreate=0;fallback=missing-source |
| R dependency | mapple、navsea_delivery、tide;missingSourceIds=[tide];placementGatePassed=false |
| pre Placement 状态 | 105帧 false/非零/true;15帧 true/0/false(changed/fade/transition) |
| post Placement 状态 | 104帧 false/非零/true;15帧 true/0/false;pending 的 post=null |
| fade 范围 | pre/post 均为0至0.9914085268974304 |
这解释了一个容易误判的现象:15帧的 #12/#13 通过,并不建立 R cache;这些帧仍有 #3/#4/#23,且上一完成树的 changed=true。Fast #13 使用当前 controller transition,R 首建使用当前 Full 的三条件,二者取样点与职责不同。不得拼接 pre 与另一帧 post 来造出通过状态。
## 3. 缺 tide:新 RenderItem 为零与 GPU 零贡献的区别
### 3.1 Style 与 Source 创建
`src/mln/style/style_impl.cpp::parse/addSource/addLayer` 先加入 parser.sources,再加入 layers。addLayer 在 source 存在时检查兼容性;source 不存在时仍保留原层(源码 TODO verify source)。这正是原 Style 能保留 tide-spots 而没有 tide 的路径。removeSource 会拒绝仍被 Layer 引用的 source;同名恢复不能依靠违法删除调用,合法整份 Style 更换及后续创建也必须覆盖。
`RenderSource::create` 根据真实 SourceType 创建 RenderVectorSource、RenderRasterSource、RenderGeoJSONSource 等。原 navsea_delivery 是普通 Vector,不能将它归因为 CustomMVTVector;后者的未知 revision 准入限制继续保留。
### 3.2 Full 的注册、停用和绘制时序
| 环节 | 当前源码事实 | 能支持的结论 / 不能支持的结论 |
|---|---|---|
| createRenderTree source/layer 匹配 | `render_orchestrator.cpp` 在已有 sourceImpls 的外循环中收集 LayerRenderItem;缺源层不匹配,updateList 初始false | 本轮缺源层无新 RenderItem;不能单凭此证明旧 group 已移出 registry |
| renderability 更新 | 每次 source 更新后比较 isLayerRenderable 与 updateList,调用 markLayerRenderable | 有 source 的正常 Full 能排队停用;所有 sources 为空时此循环不执行,不能推广为任意状态的清理保证 |
| RenderLayer::markLayerRenderable | 更新 CPU bool;activateLayerGroup 创建 Add/RemoveLayerGroupRequest | bool=false 不是 request 已执行的证明;该方法没有清除 drawables、renderTilesOwner、placementData |
| RenderSymbolLayer 停用 | 同时为普通 group 与 collisionTileLayerGroup 排队停用 | 需覆盖两类 group;不能仅数普通 RenderItems |
| layerRemoved | RenderLayer 调用虚方法 removeAllDrawables;Symbol 的实现清除两类 group 的 drawable,然后停用 | 删除 Layer 与缺源导致不 renderable 的生命周期不同,不能互相替代 |
| processChanges | 移动 pendingChanges 后逐个 execute,RemoveLayerGroupRequest 删除对应 registry entry | 删除 registry 不等于销毁 Layer 所持 group/bucket/atlas;指针仍可能持有旧数据 |
| Renderer Full | tree.prepare → source/layer upload → updateLayers → processChanges → cache评估 → tweakers/group upload → Build/live draw → present/endFrame | Cache 评估位于处理 changes 之后,但此前没有生成覆盖旧数据与成功提交的零贡献证书 |
| Cache Build | R 使用 orchestrator 的 layer group range 双 pass;没有只遍历新 LayerRenderItem | 缺源层无 RenderItem不等于range里无旧group;需要独立registry与层身份审计 |
本轮实际读取 `render_layer.hpp/.cpp`、`render_symbol_layer.hpp/.cpp`、`change_request.cpp`、`render_orchestrator.hpp/.cpp`、`render_tree.hpp`、`renderer.cpp`、`renderer_impl.cpp`、`retained_underlay_cache.hpp/.cpp`。当前 src/include 中未找到名为 ResourceManager 的类;实际资源所有者是 gfx Context/backend、RetainedUnderlayCache 的 offscreen/composite、LayerGroup/Drawable 与 RenderTile bucket/atlas,以及 DynamicTextureAtlas/ImageManager/GlyphManager。GL 的 `Texture2DPool`(`src/mln/gl/resource_pool.hpp`)管理纹理复用,不提供“某个缺源层本帧零绘制”的语义证书。
**限定的正向事实**:新加载且从未获得 source 的 tide-spots,没有匹配 RenderItem;新 RenderLayer 默认 isRenderable=false。**仍缺的事实**:原设备 JSON 没有 per-layer registry identity、两类 group 的已执行停用、旧 drawables/tiles/placementData 清空和成功 Full 提交代次。不能从其他 sources 已 Loaded、cache textureCreate=0 或 pending-changes Gate=PASS 推出这些条件全部成立。尤其 task 要求“无旧 Renderable 数据”,单纯停用并不满足它。
### 3.3 失效链已存在,但不存在缺源的受控依赖状态
`retainedSegmentSourceState` 在读 revision 前要求每个 sourceId 存在;缺失即返回 ready=false/stable=false/revision=0/reason=missing-source。renderSegmentDependencies 从完整层表收集 source 和 Symbol 全局前提,不因隐藏/空桶删依赖。RevisionBook 没有为从未出现的 tide 登记;删除墓碑可保留,但不代表当前 source 存在或 loaded。
`sourceDiff.added/removed/changed` 增加 content revision 与对应 source revision,先删/建 RenderSource,再 update。同名同类型但 immutable Impl 改变属于 changed。Fast 的 non-camera signature 比较 immutable sources 身份,下一帧在 createRenderTree 之前拒绝旧输入;已有 PreparedTree 在 Full 创建前释放。异步 onTileChanged 增加全局 content 和局部 source revision并 invalidate。Image/Glyph/Style/Context 的全局 epoch、层身份 fingerprint、camera/viewport key也各自保留。
因此“同名 source 出现后可拒绝旧 Fast”的机制已有源码依据;**不能由此反推不存在的 source 可合法登记成 loaded/stable/零 revision**。新的缺源状态需要绑定 source absent identity、完整层集合、应用后 registry/旧对象状态和成功绘制代次;source 出现、替换、异步 layout、context改变必须撤销它。当前没有这些存储和核验,不实施空依赖准入。
## 4. Symbol 首次 Build 的安全根因
### 4.1 两个 Full 分支和第二重 scene 锁
`PlacementController::placementIsRecent` 按 commitTime+updatePeriod 判断;transitions关闭则总返回false。Continuous `createRenderTree`:
1. 不 recent:placementChanged=true;Placement::create → placeLayers → commit;setPlacement 清 stale,CrossTileSymbolIndex prune 与 source fading 更新。
2. recent:placementChanged=false;setPlacementStale;controller.hasTransitions 在 enabled 时优先因 stale 返回true。
`renderSegmentPlacementGatePassed` 对结构性 Symbol 要求 `!changed && fade==0 && !controllerTransition`。每个分支至少违反一项。关闭 transitions 时 Full 为 true/1/false;duration=0但enable=true时fade=1,recent分支仍stale。等待更久、改300/30ms阈值或关闭fade无法提供证书。
`renderer_impl.cpp` 还有独立保护:retainedPlacementStable、retainedStaticStable、retainedSupported,以及 `particlePreparedSceneSupported=retainedSupported && fastContinuousSupported`。只放宽 per-R Gate不会使 #23 安全通过;只放宽 #13也不能自举 Build。Fast 成功后 prepareParticleFastFrame 才重置树的 changed/fade;调用它来准备第一次 Build会循环假定尚不存在的安全证据。
### 4.2 CPU 结果不等于最终 GPU 输入
读 `placement.cpp::commit/updateLayerBuckets/updateBucketOpacities/updateBucketDynamicAttributeData`、CrossTileSymbolIndex、SymbolBucket::updateVertices、SymbolLayerTweaker 与 DrawableAtlasesTweaker,以及 GL/Metal symbol shader:
- commit 以每个 crossTileID 的 text/icon placed 状态更新 opacity;显隐未变可继承旧 fadeStartTime。controller transition=false不等于所有桶在GPU中已上传或与旧帧等价。
- RenderTreeImpl::prepare 更新 bucket opacity/dynamic data;SymbolBucket 将 uploaded、placementChangesUploaded、dynamicUploaded 标记为false。SymbolBucket::upload 的标记不是成功GPU Drawn R的证书。
- opacity处理 duplicate、默认allow-overlap、justReloaded、variable anchors、orientation、text/icon配对;沿线重投影与camera相关。碰撞为全局 Placement,并可能跨 source/段产生依赖。
- shader实际使用 `clamp(baseOpacity ± symbolFadeChange,0,1)`,再乘paint opacity。fade=0既可能为全透明新符号,也可能为已显示符号;fade非零也可能因clamp不再改变像素。用==0或>=1替代逐输入等价均不足。
- LayerTweaker 还使用tile/label/coord矩阵、size binders、paint插值、纹理尺寸与shader变体;DrawableAtlasesTweaker绑定glyph/icon atlas和sampler。
- retainedRangeContentFingerprint 只散列 layer.baseImpl 指针与index;它不包含 placement结果、opacity buffer、bucket身份、atlas纹理、uniform最终值。Source revision也不替代这些内容。
- Build成功计数来自双pass编码无捕获异常、finishBuild与markBuildSucceeded;随后主buffer present提交。现有实现没有完整的 per-R GPU输入证书或设备completion fence记录。将编码成功说成GPU执行完毕属于证据越界。
### 4.3 可继续的设计边界与反例
| 候选条件 | 反例 / 缺少的证明 |
|---|---|
| source loaded、无手势、静止90秒 | changed/stale循环继续;异步glyph/tile/image可改变结果 |
| Placement placed布尔相同 | opacity方向/基值、duplicate、anchor/orientation或dynamic vertices可不同 |
| fade=0且controller=false | 实测15帧changed=true,Build仍拒绝;刚出现符号可能base opacity=0 |
| 当前 RenderItems 为空 | 旧group可能仍在registry或旧数据留在RenderLayer;pending请求可能未执行 |
| layer fingerprint/source revision相同 | 不能区分同桶的dynamic opacity/atlas/uniform变化 |
| 两张末态截图看起来相同 | 未捕获变更瞬间、碰撞受害段及GPU提交代次,不能建立持续复用不变量 |
待证明证书至少应覆盖:R层身份/层序和drawable集合;bucket/tiles/crossTileID及跨段碰撞结果;text/icon显隐、opacity方向/基值、dynamic vertices、沿线/variable placement;paint/layout revision;glyph/image/atlas/shader/uniform版本;projection/camera/viewport/pixelRatio;context generation;已应用ChangeRequests及异步content epoch。只能在正常Full完成prepare/upload/tweakers、真实Build成功提交且资源一致时绑定,任何不一致先Full+invalidate/rebuild。不能只加入一个“结果稳定”bool。
这涉及 Placement→Bucket→Drawable/Atlas→Renderer提交生命周期的跨层实现,当前没有足够设备/图像证据保证安全。本轮停止该准入实现,不删或强制置假 placementChanged、symbolFadeChange、controllerTransition,不改24Gate。
## 5. 七项测试源码及真实覆盖限制
两个既有 test/CMakeLists.txt 已登记的测试文件内新增 `Step117Safety.*`,无需改构建表,全部 NOT RUN:
| 用例 | 实际测试目标 | 不覆盖的事实 |
|---|---|---|
| RealPlacementControllerHasNoFirstSymbolBuildWindow | 用实际Controller/Placement::create/placeLayers/commit执行120个逻辑时刻;两个分支均fallback,Build/Reuse为0 | 空桶生命周期最小复现,无原PBF碰撞/GPU |
| RealPlacementDisabledOrZeroDurationIsNotAResultCertificate | 实际enable=false与duration=0/enable=true,fade=1、stale行为继续保护 | 没有禁用生产fade |
| MissingAndSameNameRestorationCannotReuseAnOldCache | 缺源→创建→状态机构建/复用→删除墓碑→同名恢复必须新Build | 模拟status与markBuildSucceeded仅为cache状态机,无GPU成功 |
| CameraStyleAnnotationAndContextRequireRebuildBeforeReuse | camera/viewport/content/topology/config变化经过实际revision tracker和cache,使旧cache无效且#3/#4失败 | epoch输入是模拟,未触发真实Annotation/camera设备事件 |
| RealMissingSourceTreeHasNoRenderItemButStillFailsClosed | 真实createRenderTree:缺源Symbol零RenderItem/零group仍missing-source;实际sourceDiff创建、同名替换、移除;layer fingerprint保持不变而revision变化 | 使用step117-source无数据GeoJSON测试对象,未改生产tide;无旧GPU对象场景 |
| QueuedGroupRemovalIsNotAppliedUntilProcessChanges | 两个测试group实际进入registry,真实Remove请求入队前后遍历差异与pending变化 | 测试group代表两类生命周期,不是实际Symbol collision group或GPU Draw |
| RealAsyncCallbacksAndContextStyleCleanupAdvanceContentEpoch | 实际Tile/Glyph/Image observer分派、markContextLost/clearData推进content epoch;旧content输入单独命中#5 | 事件在测试线程注入,无实际网络并发/完整Renderer/设备 |
未新增Jest,未编译或运行这些测试。生产 src/include、所有Gate/计数、Android场景和63层零修改。既有Annotations.Step116图像用例保留;新测试不代替glyph/tile并发、跨段collision、old GPU drawable清理或Full/Fast像素验证。
## 6. 具体阻塞与协调需求
| 编号 | 无法继续的状态 | 应由谁处理 / 需要的产出 |
|---|---|---|
| B1 缺源零贡献 | 当前只有absent Source与新RenderItem缺席;无应用后双group/旧drawable数据/提交世代证书;revision=0不能登记为有效 | Shared Core制定absent依赖内部状态及销毁/禁用证书;Android/Metal分别验证初始缺源、曾有旧数据、合法Style替换、同名恢复 |
| B2 Symbol首次Build | 重算changed、recent stale;所有可达Full状态都不满足现有准入;无最终GPU结果等价/时间不变证书 | Shared Core提供跨层结果证书设计与影子比较实现任务;先影子不放行,证明后才讨论受控准入;粒子负责人组织双平台审查 |
| B3 正式tide语义 | 生产Style宣告tide-spots但sources不含tide;本仓库不能自行补业务数据、改服务器或删层 | Integration协调NavSea/Style所有者明确是否属于合法宿主动态注入、何时提供真实source及身份;改变公共缺源语义才走RFC/ADR,纯内部优化不强制新Contract |
| B4 独立验证 | 本轮没有Native编译、测试或设备新证据;STEP116历史Mac配置缺libuv | RDC复核当前环境、执行定向测试,首错保留;用户明确授权后由RDC/普通Terminal集中Android验收 |
B1/B2应形成具体设计/实现任务,不通过业务开关、假tide、删Symbol、改24Gate或STEP114烟测绕过。Integration是协调入口,本轮不向他人发消息、不写跨仓文件。当前代码阶段止于测试和证据交付;详见STEP117验收计划。原目标 Build>0、Reuse>0、FastDelta>0仍未完成。

View File

@@ -0,0 +1,77 @@
# STEP-119 Symbol结果证书与R缓存Build实现依据 v1
日期:2026-10-11(JST)。状态:IMPLEMENTED_WAITING_ANDROID;Shared Core已有实质实现,尚无本轮编译、GPU、Android或视觉验收结论。
## 1. 基线与原证据
仓库 `/Users/tei/dev/MapLibreParticleLab`,Mac.lan / Darwin24.6.0 / x86_64,particle/main@869930046b11dbeb3b147e3be952a2e847316292。Native版本13.6.1-particle.23。Integration本地只读HEAD=80667d6c343411d03a760425438e3d292f452118;已读取AGENTS、instructions、README、WORKFLOW、TEAM_MODEL和contracts/rfc/adr/status/compatibility,未写跨仓文件。纯Renderer内部优化,不新增公共接口、所有权或错误协议;C-001/C-002和Core-owned timeline/playback保持。
STEP117揭示:Continuous重新计算令placementChanged=true;recent复用设置stale,hasTransitions=true。旧Symbol gate要求changed=false、fade=0、transition=false,两个分支都不能自举。global retainedPlacementStable、retainedStaticStable、retainedSupported、preparedSceneSupported还有独立阻断。
STEP118保留原63基础层manifest,仅测试副本排除tide-spots:62基础层+2天气层,Native64,R[0,62)、C[62,64),missingSourceIds=[],static-placement、Build0/Reuse0。旧设备90.00806697399821秒FastDelta0/FullDelta942、decisionMismatch0,不是本轮结果。
旧APK源码3051196054c7b69815c298040dae61d26499d9f2,SHA=44afcfabe0cf4f1ec93f35899241bd73093aa6db4b5cb82ca2120dd531b8f3e9。旧result.json SHA=d3d20fc35bbd17e9f0ea0dd014ca4af8e0c5c5284d9f0c009bc20981d876ff10;screen.png SHA=c8b57a8432626c9209ca9a614a747728d4256bd09273b28c26a793a75a6d44d4。本轮只读解析/核对,文件未修改。
## 2. 实际渲染生命周期
1. 正常Full先createRenderTree:Source/style差分、跨瓦片ID匹配、全场Placement碰撞与commit继续使用原实现,没有按R段拆碰撞或冻结新结果。
2. RenderTreeImpl::prepare调用Placement::updateLayerBuckets,继续处理text/icon、重复符号、variable anchors、方向、opacity、动态顶点、沿线投影和排序。
3. Renderer依次上传source/layer/atlas,updateLayers创建或更新Drawable,processChanges实际应用注册/移除队列。
4. 所有正常Full LayerGroup先runTweakers并upload。SymbolLayerTweaker记录**同一份传给GPU的**evaluated props、drawable/tile UBO字段;记录frameCount,并确认evaluated/drawable/tile UBO存在和consolidated容量,未执行、出错、缺buffer或错误帧不能补证。
5. 此后采集每R的原生Symbol绘制输入,与上一成功提交Full的证据精确比较。符合就准入首次Build,不依赖已有缓存。
6. Build保持原双pass和原始全局ordinal;finishBuild后再检查输入未改变。资源/绘制错误走Fallback;实际主缓冲按原R/C层序composite或live绘制。
7. present和endFrame成功返回之后再次检查输入、epoch及错误代次,才记录完成Full/Build证书。它证明成功编码和提交,不宣称GPU fence完成或像素等价已经测量。
8. 后续一次正常Full Reuse成功提交后,才允许Fast。Fast使用相同证书版本、content/topology/config、相机、backend viewport、pixelRatio和crossSourceCollisions,并继续原24项Gate。
普通Full替换旧prepared tree时只销毁引用式RenderItem;证据拥有复制的字节和必要资源引用,跨Full保留。style/context/显式prepared-tree reset仍清证书。symbolCompletedSignature独立保存上一Full的非相机输入,防止glyphURL/fontFaces/placement设置等在旧树销毁后丢失保护。
## 3. 结果证据包含什么
| 实际输入 | 采集与保护 |
|---|---|
| Symbol uniforms | 从SymbolLayerTweaker实际提交的CPU参数复制;包含paint颜色/透明度/halo、tile/label/coord矩阵、尺寸/插值、gamma、text/icon/halo标志;忽略SymbolDrawableUBO末尾12字节非shader padding |
| 顶点与实例属性 | 对实际Drawable绑定的VertexAttributeArray/InstanceAttributeArray记录属性索引、类型、stride、offset、vertexOffset和共享原始字节;包含静态布局、动态位置/角度、opacity、data-driven paint、排序实例;共享属性必须已有GPU背板,索引必须已上传且非dirty |
| 已释放的不可变buffer | 只在producer已release且存在GPU backing时用保持存活的对象/背板身份;不能读取的可变输入拒绝。不是把任意空数据当相等 |
| text/icon/索引与排序 | 原生SymbolBucket对应text/icon/sdfIcon buffer、segment范围、instance/vertex/index长度、sortKey;两条路径均记录实际三角索引字节,release后必须保持不可变索引对象与GPU背板身份;instancing固定quad和segment仍核对 |
| fade | 检查每个实际opacity顶点的packed值及当帧fade;必须在放置目标方向达到shader clamp终点,并且真实Placement fade已结束;非有限值、负值、非整数或越界packed值拒绝 |
| atlas/texture/sampler | 保持atlas、glyph/icon及实际绑定纹理/原生atlas tweaker身份,核对尺寸/format、上传状态和实际绑定一致。原生tweaker的sampler策略由固定bucket/layout和相同camera/无手势状态决定;glyph/icon像素原地更新由既有全局epoch保护 |
| shader | 独立provenance记录只在实际GL/Metal编译源码与原生Symbol源码完全相符、额外宏属于原生uniform白名单时登记;保留shader对象身份。名称相同的未知/替换shader不借用fade契约 |
| LayerGroup/层序 | processChanges之后验证Symbol组确实注册,隐藏层无旧注册组,collision额外组或陌生组拒绝;记录全局group总数、原始ordinal、组身份/层索引/数量/enable,以及Drawable实际混合方程/因子/颜色mask、剔除/深度/3D状态。全局depth range与跨段排序因此参与证据 |
| 版本与生命周期 | 精确结果revision、成功Build revision、成功Reuse状态;绑定content/topology/config、camera矩阵、viewport、pixelRatio、crossSourceCollisions;原update signature/SourceRevisionBook/Annotation Gate继续使用 |
每个结果字节缓冲上限16MiB,超限拒绝证书;没有提高公共容量或假装完整证明。资源引用避免地址ABA;比较对象是字节序列而不是散列。既有segment cache revision组合仍保留,但Fast还直接核对证书状态,不能靠组合散列碰撞取得证书;Full Reuse也要求candidateHasBuild精确版本,缺匹配Build时显式invalidate旧Cache,再走Build。
## 4. Placement与fade语义
没有修改placementIsRecent、setPlacementStale、原hasTransitions、commit/fadeStartTime、updatePeriod或placement计算频率。原hasTransitions仍用于正常renderer请求刷新。新增内部hasActiveSymbolFade仅表示实际fade;retainedRangeIsStable、retainedSegmentIsStable、retainedPlacementTransitionActive和Fast动态输入改用它。
GL三个原生Symbol vertex shader和Metal symbol.hpp三类shader均使用unpack后的base opacity加/减fadeChange并clamp到[0,1]。>=1在合法packed值上足以达到fade终点,但不能证明顶点、uniform、atlas或层序相同。因此没有单独用>=1放行,而是逐opacity检查+完整结果比较+提交代次。
重算仍令placementChanged=true,诊断继续如实记录;它不再被当成最终GPU输入改变。recent/stale仍请求刷新,不能单独代表正在fade。Fast rebase保留被证书确认的Full fade值,不把它清为0来取得准入。
## 5. 完整准入与失效
首次Build路径:已完成Full证据 → 下一Full同绑定同输入且已settled → Build编码成功 → present/endFrame及复核 → Build证书。Reuse/Fast路径:同版本缓存 → 正常Full Reuse成功提交 → 原24Gate全部通过 → Fast。preparedSceneSupported与global/perSegment稳定性均使用同一结果证明,不存在只改perSegment条件的旁路。
以下继续fail-closed:missing Source、CustomMVTVector未知revision、tile fading、未loaded style/sprite/image manager、paint/light transition、非粒子repaint、3D/debug/非continuous、不支持C、Annotation pending、相机/viewport/pixelRatio/非相机参数、异步content或pending changes、资源/编译/上传/绘制错误、未知placement-capable类型或shader(Symbol原生typeInfo指针必须匹配,名称相同不足以强制转换)、证据超限/不可读取。
Glyph requested/error/loaded、Tile error/changed、Symbol error新增或保持epoch推进;Image missing/remove/update、Source差分、Context reset、Style clear沿用原保护。捕获到的renderer/registry错误推进错误代次与全局content epoch;逃出上传/编码/present的异常由Renderer外围捕获,清prepared/证书、失效所有旧Cache后原样抛出;证书不能使用失败更新的旧Drawables。Build前和成功提交后都有epoch复核,证据观察期间的变化拒绝完成证书。
现有回调/registry操作运行在renderer调度线程;content revision是原有atomic。此优化不建立后台并发访问Drawable的通道,不改变callback线程合同。真正跨线程竞态仍需RDC平台实测。
## 6. 反例与保守限制
- 同placed布尔但variable anchor/角度/opacity不同:实际属性字节变化,revision递增,重新Full取证。
- controller不fade但尚未上传/陌生shader/atlas未绑定:complete=false,拒绝证书。
- glyph/icon纹理指针不变但内容更新:content epoch变化,旧Build/ReUse证书失配。
- Source同名替换、原63层缺tide:SourceRevisionBook/loaded/缺源保护仍拒绝,不能借空RenderItem作零贡献证明。
- 下方段删除group导致上方R ordinal/depth改变:全局group数量/ordinal证据变化,上方R不得复用旧结果。
- Build存在但没有成功Full Reuse:canFast=false;失败/未完成的Full不能刷新成功提交证据。
- Vulkan/WebGPU目前没有本轮原生shader provenance登记,Symbol结果证书保守拒绝;它们的普通Full渲染不变。本轮不宣称跨后端运行通过,也不以GL代替Metal验证。
- 16MiB上限、非常大的PBF、持续Glyph/Tile更新、不同native实例buffer生命周期均可能继续阻止Build;须保留真实结果修复,不能提高测试阈值制造PASS。
## 7. 测试与待证事实
新增13项Step119SymbolResults,位于已登记的两个test/renderer文件。覆盖真实Controller lifecycle+SymbolBucket opacity/dynamic buffer、packed clamp反例、variable位置/角度、camera/viewport/pixelRatio、CollisionGroups、真实uniform/wrapped tile编码、未知shader、失败提交、Annotation与24Gate、真实Tile/Glyph/Image/Context/Style回调、Source同名替换/缺失和跨段registry移除。
所有新增测试NOT COMPILED/NOT RUN。部分Build标记和字节payload是状态机fixture,绝不称真实GPU等价;现有CrossTileSymbolLayerIndex和Annotations图像测试必须由RDC一并验证。原PBF跨段collision、实际Glyph/Atlas延迟、Full/Fast图像、真实Build/Reuse/Fast和性能仍全部待独立验收。源码去除逻辑自举死锁,不承诺本机实机目标已实现。

View File

@@ -0,0 +1,152 @@
# iOS 地图点击重叠 Source 触发 MLNStyle 空 Peer 崩溃调查 v1
- 日期:2026-10-09(Asia/Tokyo)
- 状态:**问题已复现 / 原生崩溃堆栈已确认 / 修复与回归未执行**
- 分类:Particle iOS Native / MapLibre Darwin Style API 与 maplibre-react-native 点击兼容
- 严重程度:高;普通用户点击海图物体可使 NavSea 直接退出
- 本文件性质:跨项目缺陷移交与定位证据,**不是已实施修复、测试 PASS 或新版本发行结论**。
## 1. 用户复现现象
在 iOS NavSea 正常启动并显示地图后:
1. 直接点击地图上的「唐津ヨット ハーバ」位置点,App 异常退出。
2. 点击其他普通地图物体,未观察到同样问题。
3. 打开「+键盘」(十字光标)后,将光标移动到该港湾位置,查询/选取正常,不退出。
因此该问题优先定位 **地图直接 Tap 的原生可点击 Source 命中/图层排序链路**,而不是仅根据地点名称推断港湾数据损坏、JS 详情渲染异常或粒子 GPU 绘制故障。
## 2. 实际环境与 Git 事实
| 项目 | 本次调查事实 |
| --- | --- |
| iOS 设备 | Pleiades,iPad mini(第 5 代,iPad11,2) |
| iOS 崩溃日志声明的系统 | iPhone OS 26.5.2 (23F84) |
| App | NavSea,bundleID `com.navsea.app` |
| App 调查工作树 | `/Users/tei/dev/NavSea-release-v22` |
| App 调查 HEAD | `4bfe5257`(`release/particle-v22-android-debug-20261009`) |
| Particle 本次核对仓库 | `/Users/tei/dev/MapLibreParticleLab`,`particle/main` |
| Particle 调查起点 HEAD | `6df21dac38f072c5ad35b8dfc7ec5217509602bc` |
| 崩溃时间 | 2026-10-09 20:22:40 +0900 |
| 崩溃日志 | 设备端 `NavSea-2026-10-09-202240.ips`(调查时只读复制到 Mac `/tmp/`) |
设备还存在多份同日晚间 NavSea 崩溃报告,本次详细分析的证据为上述 **20:22:40** 报告。请不要把其余报告自动认定为相同堆栈,除非逐份核查。
Particle 工作区在调查前已有其他未跟踪文档,均不属于本问题,不得顺手删除、覆盖或纳入本次提交。
## 3. 崩溃证据(直接来自 iPad .ips)
```text
exception: EXC_CRASH / SIGABRT
exceptionReason.name: NSInvalidArgumentException
exceptionReason.composed_message:
*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
faultingThread: 0 (com.apple.main-thread)
-[MLRNMapView didTapMap:]
-> -[MLRNMapView handleTapAtPoint:]
-> -[MLRNMapView getTouchableSourceWithHighestZIndex:]
-> -[MLNStyle layers]
-> -[__NSArrayM insertObject:atIndex:]
-> NSInvalidArgumentException / abort()
```
上述调用栈直接证明 **崩溃发生在地图原生 Tap 处理访问 Style Layer 列表时,且 Objective-C 数组插入了 `nil`**。它并不证明具体是哪一个 Style Layer 的 Peer 为 `nil`;这一点需要后续补充定位日志或调试证据。
## 4. 已核实的对应代码
### 4.1 Darwin Style 列表 getter:空对象缺少防护
文件:`platform/darwin/src/MLNStyle.mm`,调查时约第 285–293 行。
```objc
- (NSArray<__kindof MLNStyleLayer *> *)layers {
auto layers = self.rawStyle->getLayers();
NSMutableArray<__kindof MLNStyleLayer *> *styleLayers =
[NSMutableArray arrayWithCapacity:layers.size()];
for (auto layer : layers) {
MLNStyleLayer *styleLayer = [self layerFromMBGLLayer:layer];
[styleLayers addObject:styleLayer];
}
return styleLayers;
}
```
`layerFromMBGLLayer:` 位于同一文件约第 376 行。该方法在已有 Peer 缺失时调用 `LayerManagerDarwin::createPeer(rawLayer)`;若工厂无法产生 Objective-C Peer,返回值可能为 `nil`,随后无条件 `addObject` 会直接触发本次异常。
### 4.2 部分 Particle/Weather Layer 仅注册为 CoreOnly
文件:`platform/darwin/src/MLNStyleLayerManager.mm`。
```cpp
addLayerTypeCoreOnly(std::make_unique<ParticleValidationLayerFactory>());
addLayerTypeCoreOnly(std::make_unique<RasterParticleLayerFactory>());
addLayerTypeCoreOnly(std::make_unique<WeatherColorLayerFactory>());
```
同文件的 `LayerManagerDarwin::createPeer`:当 `getPeerFactory(layer->getTypeInfo())` 不存在时返回 `nullptr`。因此以上 CoreOnly Layer **是造成空 Peer 的高优先级怀疑对象**,但需要定位日志确认实际 layer id/type,不能将具体类型预写为已证实。
### 4.3 RN MapView:多 Source 命中触发列表遍历
NavSea 当前安装依赖源码:
`node_modules/@maplibre/maplibre-react-native/ios/components/map-view/MLRNMapView.m`。
- `handleTapAtPoint:` 查询所有可点击 Source 的命中;存在命中时调用 `getTouchableSourceWithHighestZIndex:`。
- `getTouchableSourceWithHighestZIndex:` 在 `touchableSources.count == 1` 时直接返回唯一 Source。
- 在 **同时命中两个或更多可点击 Source** 时,方法读取 `self.style.layers` 并逆序遍历,决定顶部 Source;因此会进入第 4.1 节的危险路径。
崩溃堆栈已进入 `getTouchableSourceWithHighestZIndex:` 的 `self.style.layers`,符合多 Source 命中逻辑。需要现场记录所命中的具体 Source / layer IDs,以确认唐津位置的重叠对象组成。
### 4.4 NavSea 点击与十字光标是不同路径
- 直接点击地图:`src/map/MapScreen.tsx` 的 `MapLibreMap onPress`,以及 `src/map/layers/MooringSource.tsx` 的 `GeoJSONSource onPress`。
- 港湾点直点 Host 回调:`src/map/cursor/crossCursorEngine.ts` 的 `handleMooringPress`。
- 「+键盘」选取:`crossCursorEngine.ts` 中的 `queryRenderedFeaturesAtPointCompat` / `queryRenderedFeaturesInRectCompat` 查询。
本次异常在原生触摸处理阶段已发生;**即使 JS 侧在 `onPress` 增加 try/catch,也无法覆盖此 Objective-C SIGABRT**。
## 5. 修复方向(待 Particle iOS 实施者决定)
1. **首先精确定位**:在不改变业务和地图 Style 行为的前提下,记录 `layerFromMBGLLayer:` 转换失败的 raw layer id/type、Peer Factory 类型及命中 Source / Layer 列表,确认 CoreOnly Layer 是否为实际来源。
2. **修复 Darwin 公共访问契约**:`MLNStyle.layers` 不得把 `nil` 加入 `NSMutableArray`;优先设计与现有 CoreOnly Layer 共存的稳定方案。可评估安全过滤不可桥接 Layer,或补齐对应的 Peer/排序能力,但不可仅凭猜测引入无意义的 ObjC wrapper。
3. **保持集合访问一致性**:若采取过滤不可桥接 Layer 的方式,须同时检查 `countOfLayers`、`objectInLayersAtIndex:`、`getLayers:range:`、KVC/KVO 以及插入/移除行为,避免 getter 屏蔽后出现数量/索引语义不一致或再次写入 `nil`。
4. **保持点击 z-order 正确性**:RN 的多 Source 命中不能因为跳过 CoreOnly Layer 而改变真实可点击 Layer 的前后顺序;必要时直接在 Native 查询层顺序而不是依赖不完整 ObjC Peer 列表。
5. **限定修改范围**:优先在 Particle iOS Native / MapLibre 层完成,不通过 NavSea JS 屏蔽唐津地点、删除天气层或建立私有桥接来回避崩溃。若确实需要改 RN 公共接口或跨项目契约,应返回 Integration 项目协调。
**切勿直接删除 Particle、WeatherColor 或 Validation 的 Core Layer 来“消除异常”**;它们是正式渲染能力。
## 6. 修复后必须验证的场景
| 场景 | 期望 |
| --- | --- |
| 同位置只命中一个可点击 Source | 正常选取,无崩溃 |
| 两个及以上可点击 Source 重叠(重点复现) | 正确选中 z-order 最高且有效的 Source,无崩溃 |
| 唐津ヨット ハーバ 直接点击 | 不退出,港湾选取/摘要功能正常 |
| 「+键盘」移动到同一港湾 | 行为与修复前保持一致 |
| 其他海图对象直接点击 | 继续正常,无点击回归 |
| Style 含 RasterParticleLayer / WeatherColorLayer / ParticleValidationLayer 组合 | Style introspection 稳定,不向数组插入 nil |
| 天气/粒子图层启用与关闭后重复点击 | 无崩溃、无源层级混乱、无隐藏的点击遮挡 |
| `layers` getter、KVC 索引 getter、层增删 | 不出现 nil/越界,计数与索引契约保持一致 |
最少应使用 **Mac 构建出的实际 iOS Native 二进制 + iPad 真机** 做点击复现/回归。仅能编译或仅有单元测试通过,不等于用户场景已修复。
## 7. 责任、验收与完成条件
- **Particle iOS**:调查具体 nil layer、修复 Darwin Style API/多 Source 点击兼容、实施 iOS 构建与真机验证。
- **Particle Shared Core**:仅在确认是通用 Layer/Peer 协议缺陷时参与;不得未经证据改变 Android 行为。
- **NavSea**:在收到经过验证的新 iOS 发行后进行最小消费端验收;业务层不承担原生崩溃掩盖责任。
- **Integration 项目**:若涉及公共 API、跨平台协议、消费版本协调,则在现有 COORD/RFC/ADR/Contract/Status 框架内正式记录。
修复完成报告应写明:引发空 Peer 的真实 layer id/type、实际改动文件、验证命令及结果、真机版本和复现结果、Git commit/分支,以及正式发行时遵照 GSRL 的 Source Lock/获取方式/校验材料。**本调查报告本身不构成新发行,也不授权覆盖现有版本。**
## 8. 当前未完成事项
- [x] 找到 iPad NavSea 原生崩溃日志,确认主线程异常及调用栈
- [x] 核对 Particle `MLNStyle.mm`、`MLNStyleLayerManager.mm` 与 RN `MLRNMapView.m` 源码
- [x] 说明直接点击与十字光标查询的不同调用路径
- [ ] 获取本次返回 `nil` 的具体 Style Layer id/type
- [ ] 编写并执行 Native 修复及目标回归
- [ ] 重新构建 iOS、在实际 iPad 复现原场景并确认不再退出
- [ ] 按 GSRL 验证新版本消费与正式发行(如进入发行)
**结论:高可信度机制为 Native Style CoreOnly/缺失 Peer 与 RN 多 Source hit-test 排序不兼容;异常位置已由 .ips 确认,具体 nil Layer 身份与最终修复仍待 Particle iOS 验证。**

View File

@@ -0,0 +1,95 @@
# NavSea PBF 自动识别与轻量混淆跨平台方案设计 v1
日期:2026-09-27
状态:**规划 / DESIGN ONLY;未实现、未测试、未发布。**
适用范围:NavSea 自有矢量海图 PBF,MapLibre Native Android / iOS 与 MapLibre GL JS Web。
归属:MapLibre 资源加载适配层;**不是粒子、Weather Field 或 QBIN 格式变更**。
## 1. 背景与目标
NavSea 计划对自己分发的 PBF 进行低成本字节混淆,避免直接下载后被普通 PBF/MBTiles 工具读取;客户端收到瓦片后恢复标准 PBF,继续使用原有 MapLibre 解析和渲染链。Web 版本也要消费同一份服务器资源。
本方案明确只是增加直接复用数据的门槛,不承诺数字版权保护、防逆向或防止明文提取。客户端可被调试,解码后的 PBF 可在内存或缓存环节被截获;公开 Web 尤其如此。
**核心规则:检测到 NavSea 专用 Magic Header → 按版本解码;未检测到 → 维持 MapLibre 原有标准资源流程。** 不依据文件后缀决定是否解码,不修改标准 PBF 解析器,不建立第二套地图引擎。
## 2. 范围与不变项
- 只处理自有矢量瓦片资源;普通 PBF、第三方瓦片、Style JSON、Sprite、Glyph、图片以及 Weather/QBIN 不受影响。
- 原有 URL 路径、Source/Layer、Style Z-order、粒子绘制、GPU、地图交互与正常 HTTP 重试行为不变。
- 服务器可同时提供普通 PBF 与封装 PBF;三端遵守同一二进制契约,各自实现资源加载适配。
- 只在有效响应体上进行格式识别;404、500、304、取消、空响应等继续走既有错误/缓存语义,不把错误页当 PBF 解码。
- 本文是未来工作的设计草案;不指定当前源码已有某个具体 Hook,进入实施前须按当前 fork 做只读调查。
## 3. 封装结构草案
建议文件名仍为 `.pbf`,但编码后内容已不是标准 PBF,必须先解封装。以下 **16 字节头仅为待验证候选格式**,实施前冻结字节序、长度和兼容矩阵。
| 偏移 | 长度 | 字段 | 候选语义 |
| --- | ---: | --- | --- |
| 0 | 8 | Magic | ASCII `NSVTILE\0`,固定 8 字节 |
| 8 | 1 | Version | `1` |
| 9 | 1 | Codec | `1 = XOR-v1`;其他值拒绝 |
| 10 | 1 | Flags | bit0 表示内部 GZIP,其余保留位须校验 |
| 11 | 1 | HeaderLength | v1 固定 16,给后续扩展留入口 |
| 12 | 4 | CRC32 | 解封装且内部解压后,标准 PBF 明文的 CRC32;约定字节序 |
| 16… | 可变 | Payload | 经 XOR 混淆的原始 PBF 或内部 GZIP 字节 |
CRC32 只检测意外损坏或密钥不匹配的常见现象,**不是抗恶意篡改认证**。Magic 是类型标识,不是秘密,也不是加密强度来源。头部存在但格式、版本、长度、Flags、Codec 或校验不合法时,必须拒绝,不能退回普通 PBF。
## 4. 编码、识别、解码
服务器预处理一次:标准 PBF → 计算明文 CRC32 → 可选内部 GZIP → XOR → 添加 Header → 存储/CDN。对于已经是 GZIP 存储的原始瓦片,应先核对实际字节语义,再决定保留内部压缩还是重新封装,避免重复压缩。
客户端:收到已按 HTTP 传输层规则处理的响应体 → 检查 Magic → 读取并验证头部 → XOR 还原 Payload → 按 Flags 内部解压 → 校验长度/CRC32 → 把标准 PBF 交给现有解析器。普通资源无 Magic 时完全返回既有处理链,不新增一次盲目 GZIP 解压。
**HTTP `Content-Encoding` 与封装内部 GZIP 必须分开。** XOR 后的压缩 Payload 不得冒充 HTTP gzip,否则网络库可能在 XOR 前尝试解压。封装可用 `Content-Type: application/octet-stream`,自定义 `X-NavSea-Tile-Encoding` 仅作辅助诊断;格式识别以响应体 Header 为准。HTTP 层若另行压缩整个封装,须确认客户端收到的是解开传输编码后的封装字节。
已声明为“必须受保护”的自有 Source 可另设严格策略:缺少 NavSea Header 也报错(fail-closed),防止服务端误发明文;普通/混合 Source 则执行“无头=原路径”。Source 保护策略与字节自动识别是不同层面,不得混为一谈。
## 5. 三个平台的接入边界
| 平台 | 未来接入方向 | 重要约束 |
| --- | --- | --- |
| Android | 在当前 fork 的 MapLibre HTTP 资源响应交给解析器前解封装 | 核对 OkHttp/Native 缓存与 Response 回调实际位置,避免双重解压或额外整份复制 |
| iOS | 在当前 MapLibre Native 的对应资源响应路径解封装 | 以实际 iOS 网络资源实现为准,不假设 Android Java 修改会覆盖 iOS |
| Web | MapLibre GL JS `addProtocol()` 负责 fetch、校验、解封装并返回标准 PBF `ArrayBuffer` | 自有瓦片 URL 经自定义 scheme 或 `transformRequest` 导向注册的协议;后者本身不能修改响应体 |
Web 必须核对 GL JS 版本及协议回调契约、Style/TileJSON 的 URL 重写、Worker/主线程负载、CORS 和取消信号;服务端需暴露必要响应头。第三方请求不接入解码器。三端共享格式测试向量,不共享平台相关 HTTP 实现。
## 6. 缓存、错误与日志
- CDN/HTTP 缓存以**封装字节**为对象;ETag、Content-Length、缓存键、版本及更新失效策略要与实际封装内容一致。
- HTTP 304 没有新响应体;由现有缓存/重验证机制恢复对应实体,不能把空体交给解码器。按当前 Native/Web 路径调查 MapLibre 自有缓存究竟保存封装还是明文,不作默认安全保证。
- 若业务要求离线缓存也不可直接导出标准 PBF,需单独设计落盘边界;仅 HTTP 层 XOR 不保证 MapLibre 离线库/内存缓存保存密文。
- 解码前限制输入大小,解压后限制最大明文大小与压缩比,避免异常瓦片造成内存放大。数据损坏、版本不支持、密钥不匹配归类为独立 Codec 错误。
- 将前述 HTTP URL 诊断与 Codec 诊断分开:`INVALID_URL`、`HTTP_ERROR`、`NETWORK_ERROR`、`UNSUPPORTED_TILE_CODEC`、`TILE_DECODE_FAILED`;只记录必要的 URL(敏感查询参数脱敏)、状态码、版本与长度,不输出密钥或明文 PBF;正常 200 不逐张刷日志,可限频。
- 404/500、取消、超时、304、第三方瓦片和正常 200 行为均不因本功能改变。
## 7. 性能与安全预期
XOR 是 O(n) 的逐字节操作,优先服务器离线预编码、客户端原地处理,避免 React/JS Bridge 往返和多次完整 Buffer 拷贝。封装额外增加 Header 字节;“XOR 不改变 Payload 长度”不等于整体零额外字节。压缩应在 XOR 前进行,否则压缩收益可能明显恶化。
Web 先评估主线程处理时长与地图交互,再决定是否使用 Worker;Android/iOS 关注实际缓存策略、内存峰值与线程阻塞。性能影响只能经真实 PBF 样本、冷/热缓存及拖动缩放实测后定论。
固定或循环 XOR 密钥可以被分析,已知格式和客户端代码会进一步降低逆向门槛;把算法放到 C++/WASM 或换成 AES 也无法阻止客户端截获已解码的明文。本方案定位为**轻量混淆**。若未来目标提高到防批量下载,应另行规划 HTTPS、授权、短期访问凭据、配额/限速、异常下载监测与必要的数据分发范围;不要把 XOR 描述成加密认证。
## 8. 未来实施阶段(本次均不执行)
| 阶段 | 工作 | 产出/闸门 |
| --- | --- | --- |
| A 只读调查 | 当前 fork Android/iOS HTTP、资源与磁盘缓存;GL JS 版本;服务器 PBF/GZIP、CDN、TileJSON/Style 路径 | 确认真实接入点、压缩语义和数据所有权 |
| B 协议冻结 | 冻结 Header 字节序/CRC 覆盖、Codec、Flags、版本策略、普通/受保护 Source、Golden vectors | 三端可交叉解码同一 fixture |
| C 服务端 | 离线编码工具、标准/封装样本、响应头及缓存策略 | 普通与封装资源同时可分发 |
| D Android/iOS | 各自最小资源适配,保持解析器与业务接口不变 | 各平台原生样本验证;构建/实机由用户手工执行 |
| E Web | 注册自定义协议、URL 路由、解码与取消/CORS | 自有/第三方混合地图正常 |
| F 综合验收 | 性能、离线缓存、故障、日志、安全边界与回退 | 三端统一验收通过后再决定发布 |
验收矩阵至少覆盖:原始 PBF、标准 HTTP gzip、封装 RAW、封装内部 gzip、无头普通资源、受保护 Source 缺头、短头/错误 Magic/版本/Flags、错误 CRC/密钥、空瓦片、404/500/304、网络失败/取消、第三方瓦片、冷/热/离线缓存、跨平台 Golden Fixture、Web CORS 与长时间拖动缩放;不得以单张瓦片可显示作为最终通过。
## 9. 待实施前决策
1. 是否采用 XOR-v1 作为正式首版,还是只做试验;客户端密钥静态分发的风险是否接受。
2. Magic/Header 的精确二进制布局、CRC 字节序、最大明文长度、Codec ID 和压缩 Flags 是否按本草案冻结。
3. 哪些 NavSea Source 必须受保护;普通/混合资源的无头旁路范围。
4. 离线缓存是否必须保存封装字节,以及 MapLibre 内建缓存如何满足该要求。
5. Web URL 路由和跨域部署方案;第三方瓦片是否保持完全独立。
**本次结论:仅保留规划文档。没有新增 Codec、改动服务器资源、修改 MapLibre HTTP、引入密钥、触发构建/测试或宣布任何平台支持完成。**

View File

@@ -0,0 +1,141 @@
# STEP-094 通用 Render Segment 模型与 Style 分类 Diagnostics 设计 v1
## 1. 目标与边界
Renderer 只接收 Style 顺序和一组连续 Segment 定义。每个 Segment 的执行策略只有 `RETAINED` 或 `CONTINUOUS`;不引入 LOW/MEDIUM/HIGH,不识别 AIS、Route、Weather 等业务语义,不改变 Style Z-order,也不增加 Anchor/Marker Layer。
V12 的 Bottom Retained / Continuous / Top Retained 是本模型的兼容特例。新实现只有一套 resolved topology;旧 `setContinuousLayerRange()` 先转换为通用 Segment Plan,再进入同一 Renderer 路径。
## 2. 公共数据模型
Core 公共类型位于 `include/mbgl/renderer/render_segment.hpp`:
```text
RenderSegmentPlan
schemaVersion = 1
segments[1..N]
strategy = RETAINED | CONTINUOUS
startLayerId = 第 1 段省略;后续段必填
```
RN 公共类型位于 `packages/maplibre-particle-rn/src/types.ts`,`RendererRuntime.setRenderSegmentPlan(reactTag, plan)` 接收 typed object。TurboModule、Android/iOS package bridge 与 platform API 内部使用 schema-versioned JSON,JS 调用方不接触 raw JSON。
## 3. Config / Resolve 生命周期
1. map-scoped runtime state 保存 active config、`configSource` 和单调递增 `configRevision`。
2. `setRenderSegmentPlan()` 将 active source 切到 `explicit-render-segment-plan`。
3. `setContinuousLayerRange()` 将 active source 切到 `continuous-range-compat`,不会再并存第二套 Renderer topology。
4. 没有显式配置时,既有 `startsRetainedDynamicRange()` 仅通过 `unconfigured/legacy` adapter 生成兼容 plan。
5. RenderOrchestrator 只在 config、Style ready、Layer add/remove/change 时重新解析 boundary ID 和 diagnostics metadata;Particle 高频 Fast Path 不扫描/复制 Style 字符串,也不 stringify JSON。
6. `topologyRevision` 只在 resolved strategy/begin/end 签名变化时递增。Visibility 变化更新 `visible`,不改变 topology revision。
显式 plan 合法条件:definitions 非空;第 0 段无 boundary;第 1..N-1 段 boundary 存在、非空且 index 严格递增;每段非空;完整覆盖 `[0, layerCount)`。失败时 segments 为空、`valid=false`、释放旧 cache 并走 full renderer;ordered `styleLayers` 仍可读取。
## 4. V12 Compatibility Mapping
旧闭区间 `[startLayerId,endLayerId]` 解析为:
```text
startIndex > 0 -> RETAINED [0,startIndex)
CONTINUOUS [startIndex,endIndex+1)
endIndex+1 < styleLayerCount -> RETAINED [endIndex+1,layerCount)
```
因此自然退化为 R-C-R、R-C、C-R 或 C。`configSource=continuous-range-compat`。旧 `getRetainedUnderlayDiagnostics()` 继续保留摘要;多段真实事实以新 API 为准。
## 5. Renderer 执行与 Cache Ownership
Resolved Segment 保留 Style 全局 `layerIndex` 和 LayerGroup 原 ordinal。Retained 构建继续使用 opaque reverse / translucent forward;Continuous 直接绘制同一全局 index 范围。`parameters.currentLayer` 和 `depthRangeSize` 不按 Segment 重新编号。
Renderer 按 resolved topology 动态创建 `retainedSegmentCaches[segmentIndex]`:
- RETAINED:独立 `RetainedUnderlayCache`、key、revision、texture、compositor 和 counters;
- CONTINUOUS:对应 slot 为空,不创建 retained texture/FBO/compositor;
- topology 改变:旧 cache 在 Renderer 线程释放,不能迁移或 composite stale resource;
- GPU resource 仍由 `prepareResources()` lazy 创建,稳定 viewport 复用。
主 buffer 按 Segment 顺序执行:Retained composite 或 Continuous direct render。相邻同策略不会合并。
## 6. Invalidation
Layer immutable Impl identity 与全局 Style index 组成 retained Segment content fingerprint。Layer property/visibility 在某 retained Segment 内变化时,只推进该 Segment 的 content revision;其他 retained Segment 可继续 reuse。
以下仍保守 invalidate 全部 retained Segment:camera/viewport/pixel ratio、source/tile、image/sprite/glyph、placement/transition、context reset、debug/3D、style reload、resource/compositor failure。原因是当前 Core 没有足够稳定的通用 source-to-render-segment dependency graph;本 Step 不扩建该图。
## 7. Particle Fast Path
恰好一个 Continuous Segment、其内容满足既有 Particle-only 支持条件、所有实际 Retained cache 有效时,沿用 V12 Fast Path。缺少某个不存在的 retained cache 不构成 miss。
多个 Continuous Segment 暂不做高级 Fast Path:gate 以 `unsupported-continuous-layer` fail closed,进入正常 full CPU frame;Segment topology、cache 与 Z-order 不变。
## 8. Android / iOS / RN API
```text
RN RendererRuntime
setRenderSegmentPlan(reactTag, typedPlan)
getRenderSegmentDiagnostics(reactTag)
↓ schemaVersion=1 JSON transport
WeatherRuntimeBridge(既有唯一 bridge)
↓
Android MapLibreMap / iOS MLNMapView
↓
mbgl::Map typed RenderSegmentPlan / RenderSegmentDiagnostics
↓
RetainedUnderlayRuntimeState + RenderOrchestrator + RendererImpl
```
Android platform 内部 API 为 `setRenderSegmentPlan(String)` / `getRenderSegmentDiagnostics()`;iOS 为 `setRenderSegmentPlanJSON:` / `renderSegmentDiagnosticsJSON`。Core API 保持 typed。JSON/schema/strategy 非法时返回 REJECTED,同时把 active topology 置 invalid,避免继续复用旧 cache。
## 9. Diagnostics Schema
Snapshot 为 pull-based。顶层包含 enabled/configured/valid/configSource/configRevision/topologyRevision/counts/coverage/fallback/reason/Fast Path counters;`styleLayers[]` 始终按 Style 顺序;`segments[]` 包含 begin/end、实际 Layer 成员。Retained 的 `cache` 为 object;Continuous 的 `cache=null`,不会伪装为 disabled retained cache。
完整示例:
```json
{
"schemaVersion": 1,
"enabled": true,
"configured": true,
"valid": true,
"configSource": "explicit-render-segment-plan",
"configRevision": 7,
"topologyRevision": 4,
"styleLayerCount": 6,
"segmentCount": 4,
"retainedSegmentCount": 3,
"continuousSegmentCount": 1,
"coverageComplete": true,
"fallback": false,
"lastReason": "none",
"fastPathEligible": true,
"fastPathActive": true,
"fastPathFallbackReason": "none",
"fastPathHit": 120,
"fastPathMiss": 3,
"fullCpuFrame": 4,
"styleLayers": [
{"layerIndex":0,"layerId":"background","layerType":"background","visible":true,"sourceId":null,"sourceLayer":null},
{"layerIndex":1,"layerId":"water","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"water"},
{"layerIndex":2,"layerId":"rn-weather-color","layerType":"weather-color","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},
{"layerIndex":3,"layerId":"rn-weather-particles","layerType":"raster-particle","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},
{"layerIndex":4,"layerId":"land-area","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"land"},
{"layerIndex":5,"layerId":"ais-layer","layerType":"symbol","visible":true,"sourceId":"ais","sourceLayer":null}
],
"segments": [
{"segmentIndex":0,"strategy":"RETAINED","configuredStartLayerId":null,"beginIndex":0,"endExclusive":2,"resolvedStartLayerId":"background","resolvedEndLayerId":"water","layerCount":2,"layers":[{"layerIndex":0,"layerId":"background","layerType":"background","visible":true,"sourceId":null,"sourceLayer":null},{"layerIndex":1,"layerId":"water","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"water"}],"cache":{"cacheState":"reuse","contentRevision":19,"build":1,"reuse":120,"invalidation":0,"lastInvalidationReason":"none","textureCreate":1,"compositeCreate":1,"resourceRelease":0}},
{"segmentIndex":1,"strategy":"CONTINUOUS","configuredStartLayerId":"rn-weather-color","beginIndex":2,"endExclusive":4,"resolvedStartLayerId":"rn-weather-color","resolvedEndLayerId":"rn-weather-particles","layerCount":2,"layers":[{"layerIndex":2,"layerId":"rn-weather-color","layerType":"weather-color","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null},{"layerIndex":3,"layerId":"rn-weather-particles","layerType":"raster-particle","visible":true,"sourceId":"rn-weather-fields","sourceLayer":null}],"cache":null},
{"segmentIndex":2,"strategy":"RETAINED","configuredStartLayerId":"land-area","beginIndex":4,"endExclusive":5,"resolvedStartLayerId":"land-area","resolvedEndLayerId":"land-area","layerCount":1,"layers":[{"layerIndex":4,"layerId":"land-area","layerType":"fill","visible":true,"sourceId":"chart","sourceLayer":"land"}],"cache":{"cacheState":"ready","contentRevision":22,"build":2,"reuse":80,"invalidation":1,"lastInvalidationReason":"layer-mutation","textureCreate":2,"compositeCreate":2,"resourceRelease":1}},
{"segmentIndex":3,"strategy":"RETAINED","configuredStartLayerId":"ais-layer","beginIndex":5,"endExclusive":6,"resolvedStartLayerId":"ais-layer","resolvedEndLayerId":"ais-layer","layerCount":1,"layers":[{"layerIndex":5,"layerId":"ais-layer","layerType":"symbol","visible":true,"sourceId":"ais","sourceLayer":null}],"cache":{"cacheState":"reuse","contentRevision":11,"build":1,"reuse":40,"invalidation":0,"lastInvalidationReason":"none","textureCreate":1,"compositeCreate":1,"resourceRelease":0}}
]
}
```
有效 snapshot 保证 Segment layers 拼接等于 styleLayers,Layer 不重号、不遗漏、不重复。RN Test Host 的按钮只在用户操作时配置/拉取并显示一次完整 JSON,不轮询、不逐帧打印。
## 10. 当前限制
- 多 Continuous 只保证正确 full renderer,不提供高级 Particle Fast Path。
- Source/Tile mutation 尚无通用 dependency graph,仍全 retained invalidation。
- Legacy diagnostics 只是兼容摘要,不能表达任意 N Segment。
- OpenGL/Metal 编译、Native tests、Android codegen/Gradle 与设备运行由 RDC 独立验证后才能形成 PASS 事实。

View File

@@ -0,0 +1,76 @@
# STEP-097 V13 三轮问题去重与修复矩阵 v1
基线:Mac-mini-tei.lan;`particle/main`;起始 `720d309364628f85049cf9969d71c71e8dfacce9`,与查询时 `origin/particle/main` 一致;工作区干净。冻结 `.13` tag 指向 `ee0b89e71`。该 HEAD 相对任务书的 `b5c1c4879` 只新增 STEP-097 任务文档。Integration 本地 C-001/RFC-001 已只读;其远端是否最新尚未由 RDC 证实。所有构建、测试和运行验证:**NOT RUN**。
## 公共不变量
`fieldId/generation/validTime`、东向 U/北向 V、T0/T1/MIX、旧 T1→新 T0 复用、Style 全局 Z 顺序、每个 R 独立缓存、WeatherColor 与 Particle 分离、Host 拥有天气获取。无权改变 C-001 的公开生命周期、错误或数据所有权语义。
以下“入口”均为待执行回归,不能当作运行结果。源码状态随阶段更新;`WAITING_RDC_BUILD_AND_RUNTIME` 表示仅完成定位,尚未实现。
| # / 原编号 | 当前源码定位与最短触发 | 反例/安全约束 | 拟修文件与回归入口 | 状态 / commit / 平台待测 |
| --- | --- | --- | --- | --- |
| 01 N01/B01 | `paint_parameters.cpp:185` tile 集合相同跨 R-R target 复用 CPU mask | 同一未清 stencil target 可复用 | PaintParameters、Renderer、RenderTarget 已在新目标重置;同 tiles R-R/R-C-R 像素入口尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / GL、Metal |
| 02 N02/B02 | `mtl/render_pass.cpp:21` 仅 color clear;`offscreen_texture.cpp:67` 附件创建 | 无 depth/stencil 的 target 不可盲设附件 | Metal descriptor/combined 附件已修改;专用附件故障测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / Metal |
| 03 N03/B12 | `render_orchestrator.hpp:156` catch;`renderer_impl.cpp:824` 仍 mark success | 失败后 full fallback、下帧重试 | visitor 错误标志与 fallback 已接入;异常注入测试源码尚缺 | WAITING_RDC_BUILD_AND_RUNTIME / `e67e4961b` / GL、Metal |
| 04 N04/B06 | `Map::Impl::onUpdate()` 在 Continuous update 驱动每个 RasterArraySource;Source 使用 monotonic tick,并用 `forecastTickInProgress` 阻止同步 SourceObserver 重入二次推进 | 显式 setTime/PLAY/PAUSE/rate 由一个 Core clock owner 推进;平台 timer 只唤醒 | `map_impl.cpp`、RasterArraySource、Android/iOS WeatherRuntime;`RasterArrayV4ProductionClockTicksOnceForBothBindingsAndResetsOnCommands`、`RasterArrayV4ClockSourceNotificationReentryDoesNotAdvanceTwice` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / OpenGL 与 Metal host A2 定向 3/3 PASS;FZG1 V4 Golden PLAY/PAUSE/速率/clear/reload PASS;无 iOS runtime(用户豁免) |
| 05 B13 | `sameFieldGpuContent()` 保持 revision/version 无关的 payload 相等;renderer GPU 槽位更新使用严格 `sameFieldGpuEndpoint()`;Source mix-only cache 使用 projection identity、binding、immutable frame 和 plane/validity storage pointer identity | mix-only 复用同一 T0/T1 snapshot;frame/revision、grid、validity、U/V、encoding 或 binding 改变则重物化并失效;invalid/clear fail-closed | `field_data_adapter.hpp`、projection materialization、RasterArraySource 与 renderer;Source cache 测试 + `ParticleGpuField.*` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,A2 定向各 3/3 PASS;FZG1 stable mix 时 upload/reset 计数稳定;clear/reload 后合法增加 |
| 06 B07 | 新增内部 signed RGBA8 编码,byte 128 明确表示零;GL/Metal shader 以 high precision 还原 byte | 不改公开 QBIN/FieldProfile encoding;量化误差与 sub-half-quantum 微速边界明确测试 | `particle_gpu_math.hpp`、GL/Metal shader 源及生成头、`particle_gpu_math.test.cpp` | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,含 signed zero、取消、NaN/invalid 与微速边界;两次 shader generator 幂等;无 iOS runtime |
| 07 B08 | 统一 helper 与 GL/Metal 粒子位移路径都为 Mercator 东向 U、北向 V 应用相同 cos(latitude) metric,并夹紧极区分母 | 保持 U/V canonical m/s、V 北向为正、既有视觉时间倍率 | `particle_gpu_math.hpp`、renderer 与 GL/Metal shader 源及生成头;0°/45°/60° 四方向和极区测试 | CODE_IMPLEMENTED_STATIC_CHECKED / A2 源码提交 `f49ffa68a` / broad OpenGL 与 Metal host 各 121/121 PASS,含数学定向测试;Android FZG1 场景闭环通过;无 iOS runtime |
| 08 N05 | 长度感知 JSON 解析,拒绝 NUL/重复键/非法 schema,invalid fail-closed | 清旧计划、合法恢复 | `render_segment_json.hpp`、双平台 bridge 与 GTest | CODE_IMPLEMENTED / B Core 定向 6/6、扩展 139/139 PASS;待 B Git,iOS runtime 未测 |
| 09 N06 | FieldProfileValidator 在 Store commit 前检验有效数值及 scale/offset | validity=0/noData 合法;坏高 revision 不覆盖好帧 | FieldProfileValidator/Source GTest,修正旧 16-bit fixture | CODE_IMPLEMENTED / B Core 定向 6/6、扩展 139/139 PASS;待 B Git |
| 10 N07 | View owner、generation guard、map destroy observer 源码已写 | 旧 map 回调不得影响新世代,监听须释放 | Kotlin/Java JUnit,真实 MapLibreMap.onDestroy | Android Native JUnit 2/2、Java 编译与 owner 隔离 JVM JUnit 4/4 PASS;BLOCKED_NATIVE_AAR_COMPATIBILITY:正式 `.13` 缺新 Map 方法,默认 RN consumer 不可配对编译,B 不判 PASS |
| 11 F01/C01 | `renderer_impl.cpp:505` full diagnostics 快照 | 外部 pull schema 不变 | runtime/renderer;快照计数 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core |
| 12 F02/C02/B03 | Metal offscreen `swap()` 同步等待 | 不删不明依赖 wait | Metal;GPU capture | DEFERRED_DESIGN / 无 / Metal |
| 13 F03/C03/B04 | cache revision 变化释放 texture | target clear 必须先正确 | cache;同尺寸重绘/resize | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / GL、Metal |
| 14 F04/C04 | R 数 × viewport 独立附件内存 | 保留独立 R 与失败 fallback | cache;资源阶梯、失败注入 | DEFERRED_DESIGN / 无 / GL、Metal |
| 15 C04/B11 | encoded Store 无总 key/byte 预算 | 不静默驱逐绑定窗口 | Store;容量/旧读者 | DEFERRED_DESIGN / 无 / Core |
| 16 F07/C05 | `render_orchestrator.hpp:156` 每段前缀 distance | 全局 ordinal 与 Z 顺序不变 | orchestrator;重排/正反遍历 | WAITING_RDC_BUILD_AND_RUNTIME / 待提交 / Core |
| 17 C06/B05/B10 | A2 已处理相同 immutable projection 的 mix-only 重物化;其余 diagnostics pull、ordinal 与 UI owner 世代发布仍属后续范围 | A2 mix-only 不再复制/比较整张浮点快照;不据此宣称 C 阶段整体完成 | A2 部分:RasterArraySource cache 定向测试及 FZG1;C 部分仍按原任务另行实施 | A2 mix-only 子项已验证 / 当前工作树 `5bb1a0e` / OpenGL、Metal host 定向 3/3 PASS;C 其余项目未开始 |
| 18 C07 | drawable/tweaker tick 重建、端点复制 | 不保留 stale GPU 引用 | Particle renderer;tick/resize | DEFERRED_DESIGN / 无 / GL、Metal |
| 19 F09/O01 | `runtime_state.hpp:230` 新 topology;旧 cache 析构按 index 计数 | 总量与当前 topology 分离 | runtime/cache 按 revision 过滤;新增 `OldCacheReleaseCannotChargeReplacementAtSameIndex` GTest | CODE_IMPLEMENTED_STATIC_CHECKED / `e67e4961b` / 2026-09-26 Core GTest 1/1 PASS,诊断组 5/5 PASS;GPU 资源验收仍待测 |
| 20 F05/E01 | Test Host package/lock 已对齐正式 `.13`,SHA-256/SRI 核对一致 | RN/Native 必须精确同版,不改 `.13` 产物 | package/lock、release manifest、默认 RN 编译 | STATIC_VERSION_LOCK_PASS;B10 新 Native method 不在正式 `.13`,默认消费者仍阻塞 |
| 21 F06 | `renderer_impl.cpp` multi-C 走 full CPU | 保留安全 fallback | 后续 A/B 设计 | DEFERRED_DESIGN / 无 / GL、Metal |
| 22 F08 | source/tile 等全局失效 | 无依赖图前不缩窄 | 后续依赖设计 | DEFERRED_DESIGN / 无 / Core |
| 23 F10/E02 | Integration status/compatibility 仍写 `.5` | 发布≠NavSea 支持 | 负责人协调证据请求 | DEFERRED_DESIGN / 无 / 跨项目 |
| 24 STEP-095 §7/B09 | synthetic mask 非共享地理 mask | 不新建公开 mask API | C-001/RFC/ADR 协调 | DEFERRED_DESIGN / 无 / 跨项目 |
## 操作次数模型(静态,非性能实测)
- A1:跨 target 清除 CPU mask 为每次目标切换一次 O(1) 状态清理;当前同 tiles 可能零次重绘但产生错误像素。Metal clear 每个 pass 最多各一次附件 load 设置。异常后额外一次 full fallback,避免错误缓存跨帧复用。
- A2:PLAY 由唯一 Map/Source clock 每次 update 推进一次;V4 mix-only 的投影重物化计数为 0(50 次 Source tick 定向测试),decoded endpoint 变化时 color/particle projection 各物化一次。FZG1 日志在单次 stable mix-only epoch 内 upload/reset 计数不变;clear/reload 会合法增加计数。以上是源码测试和该次设备记录,不构成普遍 GPU 性能结论。
- B:JSON 严格校验 O(输入字节长度),有效数值验证 O(有效 cell × plane 数),View/map 每世代各至多一活跃监听;源码已实施,但正式 .13 Native ABI 不含新方法。无 GPU 性能实测。
- C:预期 full diagnostics 深复制从每 render 次降到每外部 pull 次;ordinal 前缀从每段 O(G) 降到 topology 更新 O(G);同时间物化从 O(cell) 降为 O(1)。尚未实施。
## 2026-09-26 第 19 项实测更新
- RDC 使用 `0d13552be` 重编译并执行 GTest,第 19 项 1/1 PASS、RenderSegmentDiagnostics 全组 5/5 PASS;详见 `docs/报告/STEP-097_A1_第19项GTest运行验证报告_v1.md`。其他问题状态不因此变化,A1 仍为 PARTIAL。
## 2026-09-26 第 19 项 FZG1 设备辅助证据
- 基于相同生产源码的 APK 在 FZG1-2 通过覆盖安装和设备 SHA-256 校验。两轮拓扑循环、重排、缓存 OFF/ON、缺失边界回退与恢复的 20 张截图及 UI 诊断已保存;有效场景的 B/T 粗粒度 textureCreate-resourceRelease 均为 0 或 1。此证据只支持 Android 运行态冒烟,不替代此前旧 revision 归属的 GTest 1/1 PASS,也不能证明 GPU 无泄漏。详见 `docs/报告/STEP-097_A1_第19项FZG1运行验证报告_v1.md`。A1 仍因 01/02/03 未闭合保持 PARTIAL。
## 2026-09-26 A1 专用入口补充(待 RDC 执行)
- 01:Android Validation Activity 的 STEP-093 菜单新增 STEP-097 same-source stencil 子菜单;`Step097StencilFixture` 用一个本地 GeoJSON source 与四个 fill layer 生成 R-R、R-C-R、C-R-R,菜单可重载与读 segment 诊断,既有 CACHE ON/OFF 可切换。RDC 仍需首帧/重建/reuse 的实际像素截图与 cache 计数;此处仅是可操作 fixture,未实测。
- 02:`mtl_retained_attachments.test.cpp` 检查实际离屏资源的 color/depth/stencil 绑定、simulator/x86 combined attachment、无 depth/stencil 时跳过 clear;生产 `RenderPass` 共用的 clear 辅助函数可检查 load/clear/store。设备资源创建失败与 encoder 失败的运行注入仍待 RDC 验证。
- 03:`render_orchestrator_error.test.cpp` 注入正/反向范围 visitor 异常,核对 observer/failed 标志,并用 cache 状态机核对失败后继续 Build、成功重试后 Reuse。真实 GPU 绘制故障与完整 renderer fallback 仍待 RDC 注入验证。
- 01/02/03 均保持 `WAITING_RDC_BUILD_AND_RUNTIME`;源码入口准备不等于编译或运行 PASS。19 的既有实测状态不变。
## 2026-09-26 A1 Android/Core 实测收口
- 01:same-source tiled fixture RR/RCR/CRR 两轮、rebuild/cache OFF/ON 共 18 点,屏幕中心与区域外采样两组像素均 18/18 一致;状态 valid=true/fallback=false,Android FZG1 实测通过。源码/测试提交 `d0ced7235`,编译修正 `8c006ac83`。
- 02:Metal host runner 已编译,GTest 两个测试均因没有 Metal device SKIP;用户决定不执行 iOS 运行测试,记 `USER_ACCEPTED_WITHOUT_IOS_RUNTIME_TEST`,不得称 Metal 实测 PASS。
- 03:`RenderOrchestratorError.*` 原生测试 2/2 PASS;真实 Android GPU fault injection 不在本次证据中。
- 19:前述 Core 1/1 PASS、诊断组 5/5 PASS、FZG1 辅助测试均保留。A1 在 Android/Core 验收及 iOS 用户豁免口径下进入 A2;详细事实见 `docs/报告/STEP-097_A1_Android专项验收报告_v1.md`。
## 2026-09-26 A2 RDC 最终验证
- 04–07 已在同一 dirty 源码工作树完成实现与 RDC 独立验证。旧 `sameFieldGpuContent()` 测试断言修复后原 58 项 58/58 PASS;更新后的 broad filter OpenGL 与 Metal host 各 121/121 PASS,三项 A2 Source 定向测试两平台各 3/3 PASS。
- 标准 Android Validation APK 为 OpenGL Debug/x86_64,61,753,803 bytes,SHA-256 `27bf4a6cc191bd86c4d1c10398478d81c2999edd61fe7242e40e5188516b0ca5`;安装到 FZG1-2 后设备 APK hash 相同。V4 Golden Wind test-only T0/T1 场景 17 张 PNG、24 个步骤、runner exit 0、ANR=0;PLAY 30 秒 UI 可响应,PAUSE 稳定,clear/reload 后再次 PLAY/PAUSE。
- 日志按资源窗口分别记录:clear/reload 前 mix-only epoch `fieldUploadCount=6/resetCount=2` 稳定;explicit clear/reload 后计数合法增加,达到 `fieldUploadCount=10/resetCount=4`,此后 replay 稳定。不可把 clear/reload 期间的上传与 reset 写成零。
- build source diff SHA-256 `18927a5f30a88f5c1c3a00e0017473dc33b04151c227e4baf52250dc4ad2e6a2` 与忽略目录中的 APK source snapshot 完全一致。未运行 iOS device runtime,按用户豁免记 `USER_ACCEPTED_WITHOUT_IOS_RUNTIME_TEST`;不得称 Metal host GTest 为 iOS PASS。A2 Android/Core 完成,B/C 未开始,整个 STEP-097 仍 PARTIAL。
## 2026-09-26 B 独立验证及阻塞
- RDC Core 定向 4/4、修正旧 16-bit fixture 后 broad 134/134 PASS;正式 .13 Android SDK 缺 B10 新 map destroy observer,默认 RN consumer 无法与该新源码配对。B PARTIAL,C 不启动;详见 B 独立报告。

View File

@@ -0,0 +1,76 @@
# STEP-098 QBIN V4 单帧与八帧滚动缓存设计
日期:2026-09-26
状态:**已按接受的设计契约实现源码;Native 构建、定向测试与平台运行验收待 RDC 执行**。
## 1. 权威契约
- RFC-004:`/Users/tei/dev/navsea-particle-integration/rfc/RFC-004_QBIN_V4单帧首显与八帧滚动缓存.md`
- ADR-001:`/Users/tei/dev/navsea-particle-integration/adr/ADR-001_QBIN_V4单帧与滚动缓存语义.md`
- Contract C-002:`/Users/tei/dev/navsea-particle-integration/contracts/C-002_QBIN_V4单帧与滚动缓存.md`
- COORD-005:`/Users/tei/dev/navsea-particle-integration/coordination/active/COORD-005/决议.md`
- 读取时 Integration `main` / `origin/main` 均为 `1dd576d3304e267e53ed64d31f9711d2350ce10a`。四份文件为 ACCEPTED / DESIGN ONLY 或 IMPLEMENTATION_PENDING,不代表实现、设备或 V15 发布通过。
- 原任务 v1 的旧 RFC-003 闸门和 `docs/报告/STEP-098_公共契约阻塞报告_v1.md` 属于此前状态。用户已确认旧 RFC-003/COORD-004 B 作废删除;本次不恢复、不改写任务书或旧报告。
八帧时间示例按 C-002 为 **13:00–20:00**。提交 21:00 后删除最早端 13:00。等距离时固定删除原最早端。
## 2. Core 存储与身份
`EncodedFieldStore` 按 `fieldKey` 保存独立 `FieldSeries`,默认每个序列容量 8。每个序列按 `validTimeMs` 严格升序存储;插入新帧使用 lower-bound 定位,不假定时间间隔。重复时间继续走 Duplicate、revision、stale、identity conflict 现有判定,不触发容量淘汰。
为同时支持新预报批次单帧首显和防止跨批次混插,分开处理两类兼容性:
- **同 key 共存兼容性**:grid、validity plane schema、所有 data plane 的数量与 schema 相同。不同 `fieldID`、QBIN `referenceTimeMs` 或解析 profile 的批次可以在序列中共存;否则有效的新日/新批次单帧可能无法进入序列。
- **时间插值兼容性**:除共存 schema 相同外,`fieldID`、`referenceTimeMs`、`profileID` 和 `profileVersion` 必须全部相同。resolve 只在相邻时间端点满足这些条件时返回双帧;不同批次之间的时间返回 unavailable。Ingress 会把已验证的 profile 身份保留到解码帧,避免校验后丢失身份。
满额插入先复制现有有序序列,在候选序列上执行淘汰和新增端点 schema 校验。候选有效后才 swap 到正式序列;grid/plane schema 不兼容时返回既有 `RejectedIncompatible` 路径,不淘汰现有帧。对容量 8 的正常有效插入返回既有 `AcceptedInserted`;`RejectedCapacity` 枚举与 RN 字符串继续保留兼容,不用于正常满额滚动。
对提交时间 `n` 和原序列最早/最晚端点 `e/l`,淘汰较远端:`|n-e| >= |l-n|` 时淘汰原最早端,否则淘汰原最晚端。距离计算避免无符号时间相减下溢。该规则不读取 forecast clock,所以仍符合 RN 先 submit、后 setTime 的顺序。
## 3. 单帧、双帧与投影
- 精确匹配某个 `validTimeMs` 时 resolver 返回同一个不可变 frame 作为 T0/T1,`mix=0`。颜色和有合法 U/V 或方向分量的粒子可从该帧物化;标量帧仍不能通过向量绑定校验。
- 非精确时间只在合法的相邻双帧 bracket 内插值,mix 按两端实际时间比例计算。落在窗口外、跨 batch/profile 边界或只有一帧但时间不匹配时返回 unavailable;不回退为最近帧。
- 后续帧到达只更新实际变化的投影。新帧位于当前 bracket 外时保留 Source 的不可变物化 projection 与样本时刻;当前端点被淘汰后重新 resolve,原投影不可用则清除。
- 单帧 materialization 只创建一个 `ParticleFieldSnapshot` 并让 T0/T1 共用它。Particle 和 WeatherColor 的首次资源安装也共享相同 endpoint 的 GPU texture/image/scale;GL 与 Metal 共用这段 renderer 代码及语义。设备资源实际行为仍须各平台独立验证。
- 淘汰仅释放 Store 对已移除 frame 的引用。已发布 projection 或 GPU 更新仍持有共享 snapshot/texture 引用时,按 shared ownership 生命周期释放。
## 4. 内存观测范围
新增 `DecodedFieldFrame::decodedByteSize()`、`FieldSeries::decodedByteSize()` 与 `EncodedFieldStore::decodedByteSize()`,返回 validity bitset 加所有非 validity 原始 plane storage 的字节总和;共享 validity plane storage 不重复计数。该计数**不包含** profile/容器元数据、物化 Float32 snapshot、图像、GL/Metal texture、粒子 state 或 trail。
公式为单帧 `ceil(cellCount / 8) + Σ(plane payload bytes)`;序列与 Store 分别对帧及 key 求和。Native 定向测试代码覆盖滚动时每 key 保持 8 帧及此原始字节计数回落/清空,但本轮没有运行测试,也没有 CPU/GPU 峰值、32 帧总量或长期泄漏实测。8 帧不构成全局内存保证;不新增固定 MiB 上限或公开容量错误码。
## 5. API、职责与平台
RN TypeScript、Android Kotlin/JNI、iOS Objective-C++/Facade 保持 `submitEncodedField(...)` 与 `setEncodedForecastTime(...)` 两步调用及原有方法名、参数和结果名称;Codex 静态核对现有传递路径,本轮没有改 bridge。submit 不修改目标时钟,setTime 不下载数据。两调用仍非原子,不承诺无闪烁。
`DecodedFieldFrame` 增加内部解析 profile 身份字段及 decoded-byte 计数方法,改变该 C++ 数据结构的二进制布局;不改变 RN/Android/iOS bridge 的公开方法签名或结果枚举。Core 与两个平台必须使用同一源码重编;不得把既有 `.13` AAR 或旧发布物当作新 ABI 消费证据。
NavSea、Server、下载/cache/manifest、日期 UI、QBIN 格式、Rice 解码、V3、Style Z-order、provider、公开 RN API 均未改。
## 6. 定向测试映射
以下是本轮新增或扩展的测试代码覆盖,不代表已执行:
| 项目 | 测试入口 | 本轮代码覆盖 |
| --- | --- | --- |
| T01 | `ParticleFieldBinding.SingleFrameProjectsAtItsExactTimeAndUpgradesToInterpolation`;`Source.RasterArrayV4SingleExactFrameMaterializesAndSecondFrameEnablesInterpolation` | 单帧精确颜色/向量 projection、T0=T1、mix=0、第二帧中点升级;标量-only 无向量绑定 |
| T02 | 同上 | 第二帧到达后返回双帧与 mix=0.5 |
| T03 | `ParticleFieldRuntime.UnorderedSubmissionsAreSortedAndReferenceCyclesStaySeparate` | 乱序排序;wind/wave 分 key;跨 reference/profile 只可单帧解析 |
| T04 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest`;`Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair` | 满 8 后向未来提交、淘汰最早端并保持活动 pair |
| T05 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest`;Source rolling 测试 | 满额向过去提交并淘汰最晚端 |
| T06 | `ParticleFieldRuntime.FullWindowRollsBothDirectionsAndBreaksTiesAtEarliest` | 中部等距插入固定淘汰最早端、有序且 8 帧 |
| T07 | `Source.RasterArrayV4StoresNewReferenceAndProfileAsSingleFrameWithoutCrossBatchMix` | 新 reference/profile 批次精确首显、补帧后同批插值、回到早批次单帧 |
| T08 | `ParticleFieldRuntime.RevisionIdentityAndIndependentCadence`;既有 Source stale/revision cases | Duplicate、identity conflict、revision;既有 stale 路径保留。满额下 revision 不占槽由同一 Store 分支处理 |
| T09 | `Source.RasterArrayV4HostApiRejectsKeepPublicProjectionStable`、`...RejectsNumericOverflowBeforeReplacingAcceptedRevision`、`ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent` | 解码/profile/数值和 grid schema 拒绝路径、有效帧/投影/clock 零副作用。数据帧入 Store 前仍由现有 ingress 完成解码与 profile 校验 |
| T10 | `ParticleFieldBinding.MixChangesSampleStateButNotContentIdentityOrStorage`、单帧升级 Source 测试 | 合法 bracket 的中间比例;精确端点以单帧语义表示 |
| T11 | 既有 binding `WindowUnavailable` assertions;新单帧测试 | 非覆盖时间 unavailable,不用最近帧顶替 |
| T12 | `Source.RasterArrayV4RollingWindowPreservesAnUnchangedActivePair` | 外围补帧/双向滚动不改变当前物化 projection 与 requested time |
| T13 | `Source.RasterArrayV4EvictedActiveFrameClearsItsMaterializedProjection` | 当前单帧端点淘汰后 fail-closed 清除物化 projection |
| T14 | `ParticleFieldRuntime.RejectedFullWindowInsertDoesNotEvictAndKeysRemainIndependent` | 两个 key 各自 8 帧,独立计数与字节 |
| T15 | 单帧 binding/materialization 测试;GL/Metal renderer 资源安装源码 | T0/T1 共用物化 snapshot;renderer 首装共用 endpoint texture/image。GPU/context-loss 运行验证未覆盖 |
| T16 | `ParticleFieldRuntime.RepeatedBidirectionalRollingAndClearStayBounded` | 120 次双向提交、每 key 8 帧边界、clear/clearAll 和 decoded bytes 归零 |
| T17 | 现有 `FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Android/iOS bridge 源码静态核对 | 旧 API、类型、方法名和两步语义未变;RN 自动化/双平台 consumer build 未运行 |
所有测试代码的执行状态均为 **NOT RUN**。完整执行状态与 RDC 命令见 STEP-098 完成报告。

View File

@@ -0,0 +1,113 @@
# STEP-082 XCFramework Bazel Codegen 阻塞收尾 Codex 任务 v1
## 1. 当前基线
- 项目:`/Users/tei/dev/MapLibreParticleLab`
- 分支:`particle/main`
- 起始 HEAD:`dd9687f5ed6bb9be4b58b73f2efd39f54faef7d6`
- `origin/particle/main` 应与起始 HEAD 一致。
- STEP-082 当前状态:`IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION`。
- 本任务仍属于 STEP-082 收尾,不创建 STEP-083,不进入 RN/JS/NavSea。
开始前必须读取:
- `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v1.md`
- `docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md`
- `bazel/core.bzl`
- `BUILD.bazel`
- `scripts/generate-style-code.mjs`
- `CMakeLists.txt`
## 2. 已知阻塞与调查结论
当前 `bazel build //platform/ios:MapLibre.dynamic` 在 `//:generated_style_code` 阶段失败:
`src/mbgl/style/layers/particle_validation_layer.cpp` 被声明为 generator 输出,但 `scripts/generate-style-code.mjs` 不生成它。
当前只读核对显示:
- `particle_validation_layer.cpp` 是仓库中已存在的手写源文件;
- CMake 在 `CMakeLists.txt` 中把它作为普通源文件直接编译;
- `bazel/core.bzl` 目前把它放在 `MLN_GENERATED_STYLE_SOURCE`;
- style generator 只遍历 style spec 中的正式 layer type 并生成对应标准 style layer 文件。
这说明高概率是 Bazel 文件分类错误,而不是 generator 缺功能。Codex 必须先自行核对真实代码和 Git 历史后再修改。## 3. 本次唯一目标
用最小改动修复 Bazel 对 `particle_validation_layer.cpp` 的源文件分类,使:
1. 它不再作为 `generated_style_code` 的声明输出;
2. 它仍然作为 Core 普通源文件参与 Bazel 构建;
3. 不改变现有 CMake 编译行为;
4. 不改变 Particle Validation Layer、RasterParticle、WeatherColor、QBIN、Metal 或 Runtime 语义。
优先方案是:若调查确认该文件确属手写源文件,则从 `MLN_GENERATED_STYLE_SOURCE` 移出并纳入正确的普通 Core source 集合。不得为了通过构建而让 generator 复制、伪造或空生成该文件。
## 4. 必做清单
- [ ] 核对起始 branch / HEAD / remote / dirty。
- [ ] 用 Git 历史确认 `particle_validation_layer.cpp` 的来源和预期性质。
- [ ] 最小修复 Bazel source classification。
- [ ] `bazel build //:generated_style_code` 通过。
- [ ] `bazel build //platform/ios:MapLibre.dynamic` 通过。
- [ ] 验证生成的 `MapLibre.dynamic.xcframework` 中包含 public `MLNParticleFieldRuntime.h`。
- [ ] 验证 umbrella/public header 链可见 `MLNParticleFieldRuntime`。
- [ ] 验证 binary 中存在该 facade 的 Objective-C public symbol/class。
- [ ] 验证 `MLNMapView_Private.h` 没有被导出为 public header。
- [ ] 若正式 XCFramework 可压缩,则生成临时 zip,记录真实文件大小与 SHA-256;大二进制默认不提交 Git。
- [ ] 更新 `release/maplibre-particle/ios/release-manifest.json` 中可由本次真实构建确认的字段;URL 尚未发布时保持明确占位,不伪造远端地址。
- [ ] `git diff --check` PASS。
- [ ] 更新 STEP-082 Step 记录和 `粒子开发记录.md`。
- [ ] 生成 `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v2.md`。
- [ ] scoped commit、push 到 `origin/particle/main`,记录 Final HEAD。
## 5. 严格禁止
不得:
- 修改 `scripts/generate-style-code.mjs` 去伪造 particle validation 输出,除非调查证明它本应由 style spec 生成;若出现这种相反证据,先在报告中说明,不要扩大实现。
- 修改 Particle renderer、Metal shader、QBIN decoder、FieldProfile、BindingProjection、粒子 physics。
- 修改 `packages/maplibre-particle-rn/`、`integration/react-native/` 或 NavSea。
- 重做 STEP-081 真机 smoke。
- 开始 STEP-083。
- 为通过构建删除或停用 Particle Validation Layer。
- 使用 `git add -A`、`git add .`、`git reset --hard`、`git clean`。## 6. 用户已有 dirty,必须原样保护
以下路径不得修改、暂存、提交:
- `docs/doxygen/doxygen-awesome-css`
- `platform/ios/app/MBXAppDelegate.m`
- `platform/windows/vendor/vcpkg`
另外,现有未跟踪任务书:
- `docs/40_任务/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex任务_v1.md`
不得为了清理工作树而删除或改写。
本任务书自身也不要求提交;提交时只显式 stage 本次实现与正式记录文件。
## 7. 构建与验证要求
macOS 上使用仓库当前真实工具链。编译输出遵循全局 `$build-output-throttle`。
至少执行:
- `bazel build //:generated_style_code`
- `bazel build //platform/ios:MapLibre.dynamic`
若第二条成功,必须直接检查实际 XCFramework,不允许只根据 Bazel exit code 判定:
- public Headers;
- umbrella import;
- Objective-C class/symbol;
- private header 排除;
- artifact 路径、大小;
- zip SHA-256(若生成 zip)。
如果修复第一个 blocker 后暴露新的、与本次 Bazel/iOS packaging 直接相关的小型问题,可以继续最小修复并验证;如果新问题涉及 renderer/Core 语义或较大架构改动,停止并报告,不得扩大范围。
## 8. 关闭判定
只有以下全部成立,STEP-082 才可改为 `CLOSED / PASS`:
- Bazel generated style target PASS;
- `MapLibre.dynamic` XCFramework build PASS;
- `MLNParticleFieldRuntime.h` 确认存在于 public Headers;
- public ObjC facade symbol/class 确认存在;
- `MLNMapView_Private.h` 未 public export;
- artifact size 与真实 SHA-256 已记录;
- `git diff --check` PASS;
- v2 完成报告、Step 记录、粒子开发记录更新;
- scoped commit 已 push,local HEAD == `origin/particle/main`;
- 用户三处 dirty 和旧任务书保持未触碰。
若任一门槛未满足,保持 `IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION` 或明确 BLOCKED,不得写 PASS。

View File

@@ -0,0 +1,801 @@
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行基线 — Codex 任务 v1
状态:READY FOR IMPLEMENTATION
项目:MapLibreParticleLab / 粒子系统
目标分支:`particle/main`
任务基线 HEAD:`3e03949d3411c830cfff4bf320cd350f5b6a7527`
---
## 0. 开始前必须确认
先完整阅读本任务文件,再开始修改。
开始时只读执行并记录:
```bash
git branch --show-current
git rev-parse HEAD
git rev-parse origin/particle/main
git status --short
```
预期:
```text
branch = particle/main
HEAD = origin/particle/main = 3e03949d3411c830cfff4bf320cd350f5b6a7527
```
已知且必须保持不动的用户无关修改:
```text
docs/doxygen/doxygen-awesome-css
platform/ios/app/MBXAppDelegate.m
platform/windows/vendor/vcpkg
```
若还有新的未知 dirty:
- 不自动清理;
- 不 reset;
- 不 checkout 覆盖;
- 不纳入本任务 commit;
- 在完成报告中说明。
### 执行清单
开始实施前建立简短勾选清单,至少包含:
- [ ] iOS public Native FieldRuntime facade
- [ ] MapView-owned 生命周期
- [ ] V4 submit / binding / forecast / clear
- [ ] fail-closed / style reload 安全
- [ ] public header 无 Core 类型泄漏
- [ ] XCFramework public header / symbol 验证
- [ ] distribution-ready 元数据
- [ ] 中文设计/Step/完成报告
- [ ] git diff --check
- [ ] scoped commit / push
- [ ] 最终逐项对照
不得静默跳过;无法完成必须明确写明。
---
# 1. 本 Step 的定位
STEP-081 已正式 `CLOSED / PASS`。
本任务禁止重新做:
- Uniform East
- Uniform North
- Vortex
- QBIN T0 / MID / T1
- Clear / Clear 后重建
- STEP081 readback
- 固定绿线
- Point fallback
- STEP-081 Metal pipeline cache 根因复验
STEP-082 是新的产品化前置层:
```text
RN / Consumer(后续 STEP-083)
↓
MapLibre iOS public ObjC API
↓
MLNParticleFieldRuntime
↓
MLNMapView private implementation seam
↓
mbgl::Style / RasterArraySource / QBIN V4
↓
WeatherColor / RasterParticle / Metal
```
本 Step 只做到 **公开 Native facade + 正式 XCFramework 导出基线**。
不做 RN TurboModule。
---
# 2. 已确认的当前真实事实
实施前只需快速复核,不得重新做大范围架构调查。
## 2.1 当前 RN package 是 Android-only Native
当前:
```text
packages/maplibre-particle-rn/package.json
version = 13.4.1-particle.5
```
`files` 没有 `ios`。
`react-native.config.js` 仅有 Android。
这是 STEP-083 的工作,不在本 Step 修改。
## 2.2 STEP-081 private seam 不能作为正式 SDK API
当前:
```text
platform/ios/src/MLNMapView_Private.h
```
暴露:
```objc
- (mbgl::Map &)mbglMap;
```
但该 header 属于:
```text
MLN_IOS_PRIVATE_HEADERS
```
不在 `MapLibre.dynamic.xcframework` 正式 public headers 中。
因此本任务必须新增一个正式 public facade,而不是把 `MLNMapView_Private.h` 暴露出去。
## 2.3 Particle/Weather 类型继续保持 Core-only
当前 Darwin LayerManager 已注册:
```text
RasterParticleLayerFactory
WeatherColorLayerFactory
```
为 Core-only layer。
不要新建:
```text
MLNRasterParticleLayer
MLNWeatherColorLayer
MLNRasterArraySource
```
RN / Host 仍通过 Style JSON 创建:
```text
raster-array
weather-color
raster-particle
```
Native facade 只负责找到这些对象并提交/控制数据。
---
# 3. 目标架构
## 3.1 新增公开 iOS Runtime facade
推荐类型:
```text
MLNParticleFieldRuntime
MLNParticleFieldRuntimeConfiguration
MLNMapView+ParticleFieldRuntime
```
如果现有 ObjC 命名规范要求轻微调整,可以调整文件名,但必须在设计文档说明。
禁止使用:
```text
STEP081*
Smoke*
Fixture*
NavSea*
```
作为公开 Native 类型名。
## 3.2 Public API 绝不能暴露 Core 类型
公开 header 只能依赖:
- Foundation
- MapLibre public Objective-C 类型
禁止出现在 public header:
```text
mbgl::Map
mbgl::Style
mbgl::style::RasterArraySource
std::vector
std::unique_ptr
void * core handle
raw Core pointer
MLNMapView_Private.h
```
QBIN payload 使用:
```objc
NSData *
```
而不是文件 URL。
“本地文件读取”属于后续 RN bridge 边界,不进入 Native SDK facade。
## 3.3 MapView-owned 生命周期
目标语义与 Android map-owned `WeatherRuntime` 一致:
```text
MLNMapView
owns
MLNParticleFieldRuntime
```
必须提供等价的:
- replace/create
- current runtime
- dispose
行为。
建议:
```objc
- replaceParticleFieldRuntimeWithConfiguration:
- particleFieldRuntime
- disposeParticleFieldRuntime
```
可使用 category + associated object,或符合当前 iOS SDK 结构的等价方式。
要求:
1. `MLNParticleFieldRuntime` 不得强引用 MapView 形成循环;
2. MapView 释放后 runtime 不得访问悬空 Core;
3. replace 必须先 dispose 旧 runtime;
4. dispose 幂等;
5. runtime 不保存 `RasterArraySource *` 等跨 style 生命周期 Core pointer;
6. 每次命令通过当前 MapView/current style 重新查找目标 source/layer。
---
# 4. Configuration
Configuration 至少包含:
```text
sourceId
weatherColorLayerId
particleLayerId
```
要求:
- 初始化时字符串非空;
- Runtime 不硬编码 RN package ID;
- RN package 后续仍可使用当前固定值:
- `rn-weather-fields`
- `rn-weather-color`
- `rn-weather-particles`
- Native SDK 保持 Host-neutral。
---
# 5. attach / replace 时的对象验证
创建 runtime 时必须检查当前 style 中:
```text
sourceId -> RasterArraySource
weatherColorLayerId -> WeatherColor layer
particleLayerId -> RasterParticle layer
```
验证必须基于 Core 真实类型,而不是只检查字符串 ID。
失败时:
- 不创建“半可用” runtime;
- 返回明确错误;
- 不创建第二套 source/layer;
- 不偷偷替换 style;
- 不吞掉错误后返回成功。
Style 尚未 ready 时也必须明确失败。
后续 Host 会在 style ready 后重试/attach。
---
# 6. STEP-082 必须实现的 QBIN V4 API
本 Step 只冻结新的 V4 产品主链。
至少实现:
```text
submitEncodedField
setEncodedColorBinding
setEncodedParticleBinding
setEncodedForecastTime
setEncodedForecastPlaybackState
setEncodedForecastPlaybackSpeed
clearEncodedField
clearAllEncodedFields
```
## 6.1 submitEncodedField
输入语义与当前 Android/Core 保持一致:
```text
fieldKey
fieldID
generation
dataVersion
contractJson
NSData * qbin
```
必须继续复用现有:
```text
parseResolvedFieldProfileContractJson(...)
RasterArraySource::submitEncodedField(...)
```
禁止在 ObjC facade 中重新实现 QBIN decoder。
必须继续遵守:
- `generation > 0`
- `dataVersion >= 0`
- ID UTF-8 边界与当前 V4 contract 一致
- contract JSON 有明确大小上限
- QBIN transport 有明确上限
- 空 payload 拒绝
- invalid contract fail-closed
建议继续保持当前 64 MiB payload 上限与 256 KiB contract 上限;若 Core 当前权威 guard 已定义同一值,应优先复用,不复制新的魔法数字。
## 6.2 结果映射
必须提供稳定 ObjC enum / typed result,能够无损表达当前 Core:
```text
ACCEPTED_INSERTED
ACCEPTED_REPLACED_REVISION
DUPLICATE
REJECTED_DECODE
REJECTED_CONTRACT
REJECTED_INVALID_FIELD_KEY
REJECTED_IDENTITY_CONFLICT
REJECTED_STALE_REVISION
REJECTED_INCOMPATIBLE
REJECTED_CAPACITY
```
“source missing / runtime disposed / style not ready”属于 facade/lifecycle 错误,不得伪装成某个 Core submit result。
## 6.3 Binding
Color Binding 必须保持:
```text
fieldKey
semanticID
groupID
expectedValueClass
expectedUnitID
```
支持:
```text
SCALAR
VELOCITY_COMPONENT
DIRECTION_ANGLE
```
Particle Binding 必须保持:
```text
VECTOR_COMPONENTS
DIRECTION_ANGLE
```
inactive selector 仍必须严格为:
```text
semanticID = 0
groupID = 0
```
不得按“是否为空”猜模式。
## 6.4 Forecast
保留 Core-owned:
```text
requested forecast time
play / pause
playback speed
T0 / T1 / mix
boundary reuse
```
ObjC runtime 只转发命令。
禁止复制 Forecast Clock。
## 6.5 Clear
实现:
```text
clearEncodedField(fieldKey)
clearAllEncodedFields()
```
Clear 后必须触发必要 rerender / resource release 路径,但不得增加 STEP081 临时 readback。
---
# 7. Renderer / Style 边界
本任务不得改变:
- QBIN V2 格式
- FieldProfile contract
- BindingProjection
- V4 materialization
- T0/T1 interpolation
- Metal particle physics
- 1 px current-endpoint Metal coverage
- WeatherColor/RasterParticle shader 语义
如果 facade 接入暴露出 Core 缺陷:
- 先停止扩大修改;
- 在完成报告中列出 blocker;
- 不以“顺便重构 renderer”解决。
---
# 8. Public Header / XCFramework 接线
必须把新 public API 正式加入 iOS framework 导出链。
至少检查并按真实结构更新:
```text
platform/ios/bazel/files.bzl
platform/ios/src/Mapbox.h
platform/ios/BUILD.bazel
```
若实现文件属于现有 source list 自动包含机制,按真实情况处理,不得重复注册。
最终产出的:
```text
MapLibre.dynamic.xcframework
```
必须包含新 public header。
同时确认:
- `MLNMapView_Private.h` 仍然不是 public header;
- 没有把任何 Core header 整包暴露给 Consumer;
- 新 public header 可以单独被 Objective-C Consumer import;
- public symbol 存在于 framework binary。
---
# 9. Distribution-ready 基线
当前 MLRN 11.3.6 iOS 通过 SwiftPM distribution 消费 MapLibre Native。
本 Step **不创建远端 Git 仓库,不擅自打 release tag,不发布公网 Swift Package**。
但必须在仓库中建立一个最小、可审计的 iOS Particle binary distribution 说明/模板,建议放在:
```text
release/maplibre-particle/ios/
```
至少包含:
```text
README.md
Package.swift.template
release-manifest.json
```
或与现有 release 目录规范等价的结构。
`Package.swift.template` 的目标是后续独立 distribution repo 可以直接使用:
```text
binaryTarget(name: "MapLibre", url: "...MapLibre.dynamic.xcframework.zip", checksum: "...")
```
注意:
- product/module 名必须保持 `MapLibre`,以满足 MLRN import;
- 不允许再生成第二个不同 module 名并要求 MLRN 同时链接两份 MapLibre;
- URL 可以先用明确占位符;
- checksum 必须在真实 zip 生成后记录真实值;
- 若当前 Step 尚不能生成正式 zip,则 manifest 明确标记 `NOT_BUILT`,禁止伪造 hash。
建议 external distribution version 独立采用语义版本,例如:
```text
6.28.0-particle.1
```
但不要直接修改 `platform/ios/VERSION=6.28.0`,除非通过构建系统审查确认不会破坏 upstream-compatible framework version。
本 Step 的核心是建立“MapLibre module 不变 + 自定义 binary distribution version 独立管理”的边界。
---
# 10. MLRN 11.3.6 后续兼容性记录
STEP-082 不写 RN module,但设计文档必须记录下一 Step 已确认的 iOS 视图链:
```text
reactTag
-> React View Registry
-> Fabric ComponentView
-> contentView
-> MLNMapView
-> public MLNParticleFieldRuntime
```
同时记录:
- MLRN 11.3.6 是当前 peer dependency;
- 后续 STEP-083 不建立第二套 Map registry;
- 不依赖 MLRN private ObjC header 作为长期 API;
- 后续通过 `$MLRN_SPM_SPEC` 让 MLRN 与 Particle Bridge 使用同一份自定义 `MapLibre` binary。
不要在本 Step 修改:
```text
packages/maplibre-particle-rn/
integration/react-native/
NavSea
```
除非仅为文档引用且任务确实需要;默认都不改。
---
# 11. 测试与验证
## 11.1 必做静态检查
必须执行:
```text
git diff --check
```
并检查:
1. public header 没有 `mbgl::`;
2. public header 没有 STL 类型;
3. `MLNMapView_Private.h` 未进入 public header list;
4. Core-only layer/source wrapper 没有被新增;
5. Android/RN 文件没有变化;
6. STEP-081 临时诊断代码没有恢复。
## 11.2 Objective-C / Objective-C++ 编译
如果执行环境是 macOS:
- 使用当前仓库真实 iOS build 工具链;
- 编译输出遵循全局 `$build-output-throttle`;
- 构建包含新 facade 的 iPhoneOS arm64 或正式 XCFramework target;
- 不需要重新做 STEP-081 真机视觉 smoke。
如果执行环境不是 macOS:
- 不得伪造 iOS build PASS;
- 完成源码/构建图静态接线;
- 完成报告标记 `WAITING_MAC_XCFRAMEWORK_VALIDATION`。
## 11.3 XCFramework 验证
如果成功生成 XCFramework,必须验证:
```text
Headers 中存在新 public header
Mapbox.h 可导入该 API
framework binary 中存在对应 public ObjC symbol
MLNMapView_Private.h 不在导出 Headers
```
并记录 zip:
```text
文件名
size
SHA-256
```
可将临时大产物保留在 build/release 临时目录,但是否提交大二进制必须遵守仓库现有规则;默认不要把 XCFramework zip 直接提交进 Git。
## 11.4 本 Step 不做
禁止:
- Android Gradle
- APK
- ADB
- Android 设备
- STEP-081 iPad 视觉复测
- RN iOS build
- NavSea build
- provider/network 测试
---
# 12. 文档输出
所有文档使用中文。
必须新增/更新:
```text
docs/30_设计/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行设计_v1.md
docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md
粒子开发记录.md
```
Codex 完成报告必须固定生成到:
```text
docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v1.md
```
若同名报告已存在,递增 v2/v3,不覆盖旧报告。
完成报告至少写明:
- 起始 HEAD
- 结束 HEAD
- 修改文件
- 新 public API
- 生命周期模型
- V4 API 覆盖
- public/private header 检查
- XCFramework build 结果
- artifact/hash(若有)
- 未完成/阻塞
- Git commit hash
- commit message
- push result
- remote branch
- Final HEAD
- 无关 dirty 未触碰确认
---
# 13. Git 边界
只提交本 STEP 相关修改。
禁止:
```text
git add -A
git add .
git reset --hard
git checkout -- <用户无关文件>
git clean
```
必须显式按文件/目录暂存。
提交前再次检查:
```bash
git status --short
git diff --check
git diff --cached --stat
git diff --cached
```
建议实现提交 message:
```text
feat: add ios particle field runtime facade
```
若文档收尾需要第二个 scoped commit,可使用:
```text
docs: record STEP-082 ios runtime distribution baseline
```
提交后:
```bash
git push origin particle/main
```
若 push 失败,不得宣称成功。
---
# 14. STEP-082 关闭门槛
只有同时满足以下条件,才可判定本 Step `CLOSED / PASS`:
- [ ] public iOS FieldRuntime facade 已实现;
- [ ] MapView-owned lifecycle 已实现;
- [ ] V4 submit/binding/forecast/clear 已接通现有 Core;
- [ ] public API 无 Core/STL 类型泄漏;
- [ ] style reload / source missing fail-closed;
- [ ] `MLNMapView_Private.h` 仍为 private;
- [ ] XCFramework public header 接线完成;
- [ ] macOS 环境下 XCFramework/arm64 compile 已通过;
- [ ] 若产出 zip,SHA-256 已记录;
- [ ] distribution-ready 模板/manifest 已建立;
- [ ] 没有修改 RN/Android/NavSea;
- [ ] 没有重新验证 STEP-081;
- [ ] 3 处用户无关 dirty 未触碰;
- [ ] `git diff --check` PASS;
- [ ] 中文 Step 记录与完成报告已更新;
- [ ] scoped commit 已完成;
- [ ] push `origin/particle/main` 成功;
- [ ] Final HEAD 已记录。
如果 macOS/XCFramework 构建条件不可用:
```text
IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION
```
不得写 `CLOSED / PASS`。
---
# 15. 下一 Step 边界
STEP-082 通过后,下一任务才是:
```text
STEP-083
iOS RN TurboModule + Fabric reactTag/MLNMapView Bridge + Custom MapLibre SwiftPM Consumer
```
STEP-083 预计负责:
```text
packages/maplibre-particle-rn/ios
Podspec / codegen
React View Registry
contentView -> MLNMapView
本地 QBIN file:// -> NSData
调用 STEP-082 public facade
$MLRN_SPM_SPEC 指向自定义 MapLibre binary distribution
RN Test Host iOS
V4 T0 / MID / T1 / Clear
```
本 STEP 不提前进入该范围。

View File

@@ -0,0 +1,609 @@
# STEP-083 iOS RN 统一 FieldRuntime / TurboModule / Fabric 与 XCFramework 消费 Codex 任务 v1
## 0. 任务定位
本任务是 STEP-082 之后的一个完整实现批次,不再拆成多个很小的 Step。
目标是让当前已经在 Android 使用的同一套 React Native / JS FieldRuntime 公共 API,在 iOS 上获得真实 native 实现,并通过 React Native Fabric 的 reactTag 找到当前 MLRN MapView,最终调用 STEP-082 已冻结并验证的 iOS Native FieldRuntime / MapLibre Core。
完成后应形成:
JS/TS FieldRuntime
→ 同一 NativeWeatherRuntimeBridge TurboModule ABI
→ iOS ObjC++ TurboModule
→ 当前 Fabric / MLRN MapView
→ public MLNMapView / MLNParticleFieldRuntime
→ Shared Core / Renderer / Metal
不得建立 iOS 第二套 JS API、第二套 Map registry、第二套 QBIN/Field contract。
---
## 1. 正式跨项目依据
开始前必须以以下正式约束为准,不以聊天记忆替代:
- Integration repo HEAD:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`
- Contract:`C-001`
- `contracts/PARTICLE_NAVSEA_BOUNDARY.md`
- `rfc/RFC-001_RN公共API与接入基线.md`
- `coordination/TEAM_MODEL.md`
- `WORKFLOW.md`
RFC-001 已接受的关键事实:
1. 当前公共 RN 包为 `@navsea/maplibre-particle-rn@13.4.1-particle.5`。
2. Map / Camera 继续由 `@maplibre/maplibre-react-native@11.3.6` 提供,Particle 包不得转导出或复制 MapView。
3. Particle 是纯 Field Renderer 控制面,不负责 provider、HTTP、缓存、Route/ETA 或 NavSea 业务。
4. Android 当前已有 RN bridge + Native + QBIN V4;iOS RN 在本任务前未实现。
5. Android / iOS 必须共享同一 Shared Core 与同一公共 RN 语义;不得平台分叉。
Integration 的 `PARTICLE_IOS_STATUS.md` / compatibility 在本任务创建时仍保留 STEP-082 验证前的旧状态,但 Particle 仓库实际 STEP-082 已于当前 HEAD 正式 CLOSED / PASS。不得因为 Integration 状态文件滞后而重新打开 STEP-082。
---
## 2. 起始 Git 基线
仓库:
`/Users/tei/dev/MapLibreParticleLab`
分支:
`particle/main`
起始 HEAD / origin:
`44af5e68b8569d90c26e37c2a5ae5493a5b485eb`
STEP-082 关键提交:
- implementation blocker close:
`136ed726cf749c24b3673c046581a38ef48be390`
- STEP-082 final closeout:
`44af5e68b8569d90c26e37c2a5ae5493a5b485eb`
STEP-082 已验证发行产物:
- `bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`
- distributionVersion:`6.28.0-particle.1`
- module:`MapLibre`
- size:`24639200` bytes
- SHA-256:
`64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`
- device:arm64
- simulator:arm64 + x86_64
- public `MLNParticleFieldRuntime.h`:两个 slice 均存在
- `Mapbox.h`:两个 slice 均导入该 header
- `MLNMapView_Private.h`:public export count = 0
- ObjC class symbols:device/simulator 均存在
必须先阅读:
- `docs/50_Step记录/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_v1.md`
- `docs/报告/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex完成报告_v2.md`
- `release/maplibre-particle/ios/release-manifest.json`
- `platform/ios/src/MLNParticleFieldRuntime.h`
- `platform/ios/src/MLNParticleFieldRuntime.mm`
---
## 3. 当前 RN 公共 API 是冻结基线
以下文件是本任务必须兼容的公共面:
- `packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts`
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`
- `packages/maplibre-particle-rn/src/types.ts`
- `packages/maplibre-particle-rn/src/EncodedFieldGuards.ts`
当前 Android reference implementation:
- `packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
- `WeatherRuntimeBridgePackage.kt`
- 当前 JNI / WeatherRuntime Android 实现与 guard/error 语义
### 3.1 硬性一致性要求
本任务原则上不得改变:
- TurboModule 名称:`WeatherRuntimeBridge`
- `NativeWeatherRuntimeBridge.ts` 方法名
- 参数顺序与 primitive ABI
- TypeScript public `FieldRuntime` 方法名
- `types.ts` public union / enum 字符串
- V4 submit result 字符串
- V3 submit result 字符串
- `CommandResult = ACCEPTED | REJECTED`
- JS 层 encoded guard 语义
- V4 contract JSON field names
- inactive selector `(0,0)` ABI
- `reactTag` 作为 MapView 入口的调用方式
不得让 NavSea / Test Host 写:
`Platform.OS === "ios" ? iOS专用API : AndroidAPI`
也不得新增第二套 iOS JS module。
### 3.2 JS 文案允许修正
`FieldRuntime.ts` 当前 `nativeBridge()` 的错误文本仍写死 “requires the Android WeatherRuntimeBridge native module”。
iOS 模块完成后必须改成平台中性的错误文本,但不得改变 API 类型或成功路径。
---
## 4. 第一阶段:真实代码调查,禁止凭猜测实现
在写 iOS bridge 前必须完成一次短而完整的只读调查,并在 v1 完成报告中记录结果。
### 4.1 调查当前 MLRN 11.3.6
必须取得并检查 `@maplibre/maplibre-react-native@11.3.6` 的真实 iOS source / podspec / codegen 配置。
可通过当前 Test Host 安装依赖、npm pack 或其他不会改变产品版本锁的方式取得源码。
必须确认:
1. Fabric 下 MapView ComponentView 的实际 ObjC/ObjC++ 类名。
2. `reactTag` 在 RN 0.85.2 / Fabric 中如何从当前 surface/UIManager 安全 resolve ComponentView。
3. MLRN ComponentView 内实际持有/暴露的 content view 类型。
4. 如何从该 content view 获得真正的 public `MLNMapView`。
5. 哪些接口是 MLRN public/稳定 seam,哪些是私有实现。
6. MLRN 11.3.6 的 MapLibre iOS binary dependency 方式。
7. 当前版本是否支持 `$MLRN_SPM_SPEC` 或等价官方/仓库已有 override;必须以真实 podspec/source 为准,不得凭记忆发明变量。
### 4.2 调查 RN 0.85.2 TurboModule iOS codegen
必须检查当前 RN 0.85.2 codegen 生成的 iOS Spec / base class pattern,确认:
- ObjC++ module class声明/继承方式;
- Promise resolve/reject ABI;
- module provider / codegen registration;
- 新架构 Fabric / TurboModule 的实际编译入口;
- package podspec 需要包含哪些 generated files / dependency。
### 4.3 做一张 parity matrix
在完成报告中列出当前 `NativeWeatherRuntimeBridge.ts` 每一个方法:
- Android 当前实现
- iOS 可映射到哪个 public Native/Core seam
- 是否需要扩展 `MLNParticleFieldRuntime`
- 最终 parity 状态
不得只实现 Wind V4 后声称“接口一致”。
---
## 5. 第二阶段:iOS TurboModule 与 Fabric reactTag → MLNMapView
### 5.1 新增 iOS package 实现
在 `packages/maplibre-particle-rn/` 中增加正式 iOS native implementation,并补齐 RN package iOS autolinking 所需文件。
预期包括但不限于:
- iOS ObjC++ TurboModule implementation
- 必要的 package podspec / codegen 接线
- `package.json files` 纳入 iOS / podspec
- `react-native.config.js` 正确支持 iOS(若当前 RN autolinking 实际需要)
- README / CHANGELOG 更新
实际文件名必须遵循当前 RN 0.85.2 codegen 生成基类和模块注册要求,不得照旧版 RN 模板硬套。
### 5.2 reactTag 规则
每一个需要 Map 的 native command 都必须:
1. 验证 reactTag 为正整数;
2. 在 Main/UI thread 执行 view lookup;
3. 从当前 Fabric surface/UIManager 重新 resolve reactTag;
4. 确认它确实是当前 MLRN MapView ComponentView/content view;
5. 获得当前真实 `MLNMapView`;
6. 再调用 MapView-owned Particle runtime。
禁止:
- 缓存 reactTag → map 指针;
- 建立全局/静态 Map registry;
- 缓存跨 style / surface 生命周期的 `MLNMapView *`;
- 通过 private Core 指针绕过 public MLN facade;
- 用 Test App private seam 作为 RN 正式 API。
### 5.3 Map 生命周期
`attach` 必须对应 Android 现有语义:
- 当前 Map 存在;
- style ready;
- 配置要求的 `raster-array` source / `weather-color` / `raster-particle` 已存在并类型正确;
- attach/re-attach 的 replace/dispose 语义稳定;
- runtime 由 MapView 持有;
- bridge 不持有第二份 runtime owner。
`detach`:
- 对当前 MapView 的 runtime 执行 dispose;
- 不销毁 MapView;
- 不缓存 stale runtime。
后续每条 command 都必须针对当前 MapView/current runtime执行,style reload 后按 Native facade fail-closed。
---
## 6. 第三阶段:Android ↔ iOS 公共行为完整对齐
### 6.1 V4 必须完整实现
至少包括:
- `submitEncodedField`
- `setEncodedColorBinding`
- `setEncodedParticleBinding`
- `setEncodedForecastTime`
- `setEncodedForecastPlaybackState`
- `setEncodedForecastPlaybackSpeed`
- `clearEncodedField`
- `clearAllEncodedFields`
QBIN 必须从本地绝对 path / `file://` URI读取为 bytes,再交给 `MLNParticleFieldRuntime`;JS 不解码 QBIN。
文件 guard 必须与 Android 当前 contract 等价:
- local file only;
- readable regular file;
- 1..64 MiB;
- read 时防文件长度变化;
- contract JSON 上限与 encoded ID UTF-8 byte guard保持一致;
- 不使用 Base64 / JS number[]。
### 6.2 V3 与当前公共控制面也必须审计并实现 parity
当前 RN public Spec 不只有 V4。
必须逐项处理:
- `submitWindField`
- `submitField`
- `clearWindFields`
- `clearFields`
- `getState`
- `setFieldValueSource`
- `setParticleAdvectionScale`
- `setWeatherEnabled`
- `setWeatherColorVisible`
- `setParticleVisible`
- `setTargetFps`
- `setForecastTime`
- `setPlaybackState`
- `setPlaybackSpeed`
- `setWeatherColorStops`
- `setParticleColor`
- `setTrailColor`
- `setParticleCount`
- `setParticleLineWidth`
- `setParticleLength`
- `setParticleOpacity`
- `setTrailOpacity`
- `setTrailFade`
- `setWeatherColorOpacity`
原因:RFC-001 当前 NavSea 事实仍包含 Wind V4 + Wave/Rain/Tide V3;不能靠 iOS-specific JS branch规避。
### 6.3 如果 082 facade 覆盖不足
当前 `MLNParticleFieldRuntime` 主要冻结 V4 submit/binding/forecast/clear。
如 parity audit 证明 V3 / state / visual control 在 iOS 已有 Shared Core 能力但没有 public ObjC facade:
- 允许在本 STEP-083 内最小扩展 `MLNParticleFieldRuntime.h/.mm`;
- 仅做现有 Core 能力的薄 public facade;
- Android API/Shared Core contract不改变;
- 不把 provider、NavSea、RN 类型写进 Core;
- 仍保持每次 command current-style lookup 与 fail-closed;
- public header 不泄漏 C++/STL/private Core 类型。
如某个现有 RN public method 在 iOS 根本没有对应 Shared Core 语义,且实现它必须新增/改变跨平台公共协议:
**停止扩大该部分并在报告中明确 BLOCKED。不得:**
- 返回假的 ACCEPTED;
- 写 no-op stub;
- iOS 永久返回 REJECTED 却仍宣称 parity;
- 在 JS 用 Platform.OS 绕过;
- 私自创建 iOS-only public protocol。
需要新的跨平台语义时必须回到 Integration RFC/Contract。
---
## 7. Error / Promise 语义对齐
以 Android `WeatherRuntimeBridgeModule.kt` 为 reference。
iOS 必须尽量保持相同 reject code 和触发条件:
- `INVALID_ARGUMENT`
- `MAP_VIEW_NOT_FOUND`
- `MAP_NOT_READY`
- `STYLE_NOT_READY`
- `RUNTIME_DISPOSED`
- `SOURCE_MISSING`
- `NATIVE_REJECTED`
- 以及当前 attach / weather-object readiness 已有的稳定错误码
Core V4 submit rejection 仍作为 **resolved `EncodedFieldSubmitResult`** 返回,不得错误地全部转成 Promise reject。
Facade/lifecycle/file/ABI 错误使用 Promise reject。
同类输入在 Android 与 iOS 的成功/reject类别不得无理由分叉。
---
## 8. XCFramework / MapLibre 单实例消费
这是本任务的关键发行门槛之一。
### 8.1 只能有一个 MapLibre.framework
MLRN 与 Particle iOS bridge 必须消费 **同一个** STEP-082 custom MapLibre module/product。
禁止:
- MLRN 拉官方 MapLibre,而 Particle bridge再静态/动态链接另一份 custom MapLibre;
- App 中出现两个 `MapLibre.framework`;
- 两份 ObjC class runtime;
- 私自 rename module 绕开 duplicate symbol。
### 8.2 使用真实 MLRN 11.3.6 支持的 override
先调查 MLRN 11.3.6 podspec / SPM consumer。
如果真实支持 `MLRN_SPM_SPEC`,按其实际格式接 STEP-082 custom distribution。
如果实际版本使用其他机制,按真实机制实现,并在报告记录。
不能凭聊天记忆写一个未被 MLRN 读取的环境变量。
### 8.3 本地验证允许使用 STEP-082 artifact
当前 artifact 尚未远端发布,manifest URL 为 `LOCAL_BUILD`。
本任务测试阶段允许用本地 `MapLibre.dynamic.xcframework.zip` / 解包后的 XCFramework 做可重复的 local consumer 验证。
不得伪造远端 URL。
远端正式发布可以后续单独处理;但本 STEP 必须证明 iOS RN/Test Host 能消费同一 custom MapLibre binary。
---
## 9. RN package 版本规则
本任务首先完成实现与验证,不为了“有 iOS 文件”立即发布新 npm。
当前 `13.4.1-particle.5` 与 Android Native `.5` 是 RFC-001 正式发布基线。
除非任务过程中发现已有正式 release 流程明确要求同步 bump,否则:
- 不擅自发布 `.6`;
- 不让 RN package version 与 Android Native strict lock失配;
- 可以在工作树/Test Host 用 source package验证 iOS。
最终若需要新跨平台发行版本,在 STEP-083 验证通过后单独记录 release decision。
---
## 10. Test Host:同一 JS 代码验证 Android/iOS
优先复用:
`integration/react-native/MapLibreParticleRNTestHost/`
当前它没有生成 iOS 工程。
本任务应让它具备 iOS validation 能力,但不要复制第二份业务 App。
### 10.1 JS 层不得另写 iOS 页面
同一个 `App.tsx` / `FieldRuntime` 调用路径用于 Android 与 iOS。
允许平台专属的 build/config/native files,不允许平台专属业务调用。
### 10.2 iOS 最小验收内容
至少准备并尽可能实际执行:
1. dependency install;
2. RN codegen;
3. TypeScript typecheck;
4. iOS project/prebuild/pods 或实际当前工具链对应生成流程;
5. simulator 或 device Debug build;
6. module link/launch smoke;
7. Map reactTag attach;
8. V4 Wind Golden T0/T1 submit;
9. color/particle binding;
10. forecast time midpoint;
11. PLAY/PAUSE/speed;
12. clear selected / clear all;
13. detach/re-attach;
14. style/source not ready fail-closed;
15. 验证没有 duplicate MapLibre framework/class。
如真实运行需要用户手工设备操作,Codex必须把代码、测试入口和明确验收步骤准备完整;不得把未运行项目写 PASS。
---
## 11. 允许修改范围
主要允许:
- `packages/maplibre-particle-rn/src/`(仅为平台中性错误文本/必要 codegen 修正;不得分叉 public API)
- `packages/maplibre-particle-rn/ios/` 新增
- `packages/maplibre-particle-rn/*.podspec` / package/autolinking配置
- `packages/maplibre-particle-rn/package.json`(仅 package files/codegen/build必要项;默认不 bump version)
- `packages/maplibre-particle-rn/react-native.config.js`
- `packages/maplibre-particle-rn/README.md`
- `packages/maplibre-particle-rn/CHANGELOG.md`
- `integration/react-native/MapLibreParticleRNTestHost/` 的 iOS build/config/test入口
- 如 parity 必需:`platform/ios/src/MLNParticleFieldRuntime.h/.mm` 的薄 facade 扩展
- 必要 iOS Bazel/public header build graph,前提是不改变 Shared Core contract
- STEP-083 设计/记录/报告文件
不得修改:
- NavSea 仓库
- Android public RN API签名
- Android Kotlin/JNI行为,除非只是无语义的测试/编译兼容且报告说明;默认不改
- QBIN/FieldProfile/BindingProjection contract
- Renderer/Metal particle physics
- provider/network/cache/Route/ETA
- STEP-081/082 已关闭事实
- unrelated submodule / user dirty
---
## 12. 必须保护的现有 dirty / untracked
以下必须原样保护,不得暂存或提交:
- `docs/doxygen/doxygen-awesome-css`
- `platform/ios/app/MBXAppDelegate.m`
- `platform/windows/vendor/vcpkg`
现有未跟踪任务书也不得删除、改写、提交:
- `docs/40_任务/STEP-082_XCFramework_Bazel_Codegen阻塞收尾_Codex任务_v1.md`
- `docs/40_任务/STEP-082_iOS_Native_FieldRuntime_Facade与XCFramework发行基线_Codex任务_v1.md`
本 STEP-083 任务书自身默认也不要求提交。
禁止:
- `git add -A`
- `git add .`
- `git reset --hard`
- `git clean`
只允许 scoped stage。
---
## 13. 验证要求
### 13.1 静态/代码
至少:
- package TypeScript typecheck
- RN Codegen 成功生成 iOS/Android Spec,且 Android ABI未意外变化
- iOS ObjC++ compile / Xcode build能证明 module与 generated spec匹配
- podspec lint/解析或等价 CocoaPods 集成检查
- `git diff --check`
- package tarball 内容检查:必须包含 iOS native files / podspec,不丢现有 Android/src
编译输出遵循全局 `$build-output-throttle`。
### 13.2 公共 API parity
完成报告必须逐项证明:
- public TS API无 iOS新增/删减
- Native Spec无平台分叉
- Android既有 API仍可 codegen
- iOS module实现同一 Spec
- 关键 error code/submit result映射一致
- Test Host JS 不需要 Platform.OS分支
### 13.3 MapLibre binary identity
必须检查最终 iOS build graph:
- 只解析一个 MapLibre module/product;
- 实际链接的是 STEP-082 custom binary;
- `MLNParticleFieldRuntime` symbol可见;
- 无 duplicate framework/class warning;
- MLRN 与 Particle bridge看到同一 ObjC runtime class。
---
## 14. 执行策略
这是复杂任务,可以在一个 STEP 内分“调查 → 实现 → 构建/验证 → 收尾”四个阶段,但不要人为拆成 STEP-083A/B/C/D。
遇到小型编译/Pod/codegen/headers问题,Codex继续最小修复并验证。
只有遇到以下情况才停止:
1. 需要改变当前 RN public API;
2. 需要改变 C-001 Shared Core contract;
3. 需要新增跨平台 semantic/error/lifecycle协议;
4. MLRN 11.3.6 无法在不双链接 MapLibre 的情况下消费 custom binary,需要新的架构决定。
停止时写明确 blocker,不得用 stub/Platform.OS/第二 registry绕过。
---
## 15. Git 与文档输出
实质进展后更新:
- `粒子开发记录.md`
- 新建:
`docs/50_Step记录/STEP-083_iOS_RN统一FieldRuntime_TurboModule_Fabric与XCFramework消费_v1.md`
Codex完成报告固定生成:
`docs/报告/STEP-083_iOS_RN统一FieldRuntime_TurboModule_Fabric与XCFramework消费_Codex完成报告_v1.md`
报告至少写:
- 调查结论
- parity matrix
- MLRN 11.3.6 Fabric lookup真实路径
- RN 0.85.2 TurboModule registration真实路径
- package/podspec改动
- Native facade扩展(如有)
- custom MapLibre single-binary消费方式
- typecheck/codegen/build/test结果
- 未实际运行项
- user dirty保护情况
- commit hash / message
- push result
- remote branch
- Final HEAD
在合适检查点做 scoped commit;完成范围才提交,不要带入 unrelated dirty。
最终 push:
`origin/particle/main`
---
## 16. CLOSED / PASS 门槛
只有全部成立才能标记 STEP-083 `CLOSED / PASS`:
- iOS native module 实现现有同一 `NativeWeatherRuntimeBridge` Spec;
- JS/TS public API 不分叉;
- reactTag → 当前 Fabric/MLRN view → public MLNMapView 链真实成立;
- bridge 不缓存 Map/Core pointer,不建立第二 registry;
- V4 入口完整;
- V3 + 当前公共控制面 parity audit 完成,所有方法真实实现或已由正式跨平台决定处理;
- iOS package autolinking/codegen可用;
- MLRN 与 bridge只消费一个 STEP-082 custom MapLibre;
- iOS compile/link成功;
- Test Host 至少完成真实 module/link/attach smoke;QBIN/Field功能未实际运行的项目不得伪报 PASS;
- `git diff --check` PASS;
- 完成报告与 Step记录更新;
- scoped commit/push成功;
- local HEAD == `origin/particle/main`;
- 三处用户 dirty和历史任务书未触碰。
若代码完整但真实 iOS runtime/device验收尚未完成,应写:
`IMPLEMENTED / WAITING_IOS_VALIDATION`
不得提前写 CLOSED/PASS。

View File

@@ -0,0 +1,357 @@
# STEP-088 静态底图缓存 PoC 与 Renderer 可观测性 Codex 任务 v1
## 1. 任务目标
基于最新 origin/particle/main 实际代码,实现第一阶段 Retained Static Underlay PoC。
核心目标:
- Camera 固定、Style/Tile 稳定、存在 WeatherColor/Particle 时,把动态层之前连续普通 LayerGroup 前缀 render 到 offscreen texture。
- 后续 Particle Tick 继续走当前 onUpdate/createRenderTree,但不再重复 draw 静态前缀,而复用 cached underlay texture。
- Particle simulation/render 继续按 cadence 正常。
- 增加 Renderer 可观测性。
- 本任务只执行 STEP-088,不得执行 STEP-089/090。
## 2. 执行基线
直接在主仓库:
/Users/tei/dev/MapLibreParticleLab
目标主线:
origin/particle/main
执行前:
1. git fetch origin particle/main。
2. 确认本地 HEAD、origin/particle/main、git status。
3. 不 reset/clean,不覆盖用户既有未提交修改。
4. 如存在无关本地修改,只修改、stage、commit STEP-088 相关文件。
5. 以当前实际代码为准,不得凭旧版本猜接口。
必须阅读:
- docs/40_任务/粒子连续渲染静态底图缓存与动画快路径优化规划_v1.md
- 本任务书
- docs/20_调查/STEP-086_Particle连续渲染唤醒MapLibre整图成本调查_v1.md
- docs/00_项目总则/
- docs/00_项目规范/
- 根目录 AGENTS.md(如存在)
## 3. 开工前只读确认
必须确认并在完成报告记录:
- RenderRasterParticleLayer::needsRepaint()
- RenderWeatherColorLayer::needsRepaint()
- Renderer::render()
- RenderOrchestrator::createRenderTree()
- RenderOrchestrator::hasTransitions()
- renderer_impl.cpp 的 updateLayers/processChanges
- LayerGroup tweaker/upload
- visitRenderTargets
- main clear
- opaque/translucent pass
- present
- RenderTarget / OffscreenTexture
- Heatmap/Hillshade offscreen + texture composite 样板
涉及文件至少:
- src/mbgl/renderer/layers/render_particle_validation_layer.cpp
- src/mbgl/renderer/layers/render_weather_color_layer.cpp
- src/mbgl/renderer/renderer.cpp
- src/mbgl/renderer/render_orchestrator.cpp/.hpp
- src/mbgl/renderer/renderer_impl.cpp/.hpp
- include/mbgl/renderer/render_target.hpp
- src/mbgl/renderer/render_target.cpp
- src/mbgl/renderer/layers/render_heatmap_layer.cpp
- src/mbgl/renderer/layers/render_hillshade_layer.cpp
## 4. 修改一:retained dynamic boundary
重点修改:
- src/mbgl/renderer/render_layer.hpp
- src/mbgl/renderer/layers/render_weather_color_layer.hpp
- src/mbgl/renderer/layers/render_raster_particle_layer.hpp
增加仅 Renderer 内部使用的虚函数,语义类似:
virtual bool startsRetainedDynamicRange() const { return false; }
WeatherColor 与 RasterParticle override=true。
规则:
- 两者同时存在取最小实际 layerIndex。
- only Particle 使用 Particle index。
- 无 candidate 时完全走原 Renderer。
- 禁止根据 React layer ID/name 字符串硬编码。
- 禁止让 NavSea/Host 传 boundary。
- 不新增 public API。
## 5. 修改二:RenderOrchestrator LayerGroup 分段访问
重点修改:
- src/mbgl/renderer/render_orchestrator.hpp
- src/mbgl/renderer/render_orchestrator.cpp
必要时小改:
- include/mbgl/renderer/layer_group.hpp
- src/mbgl/renderer/layer_group.cpp
需要:
- 查询 retained dynamic boundary。
- 遍历 boundary 之前 groups。
- 遍历 boundary 及之后 groups。
- forward/reverse 都支持。
- callback 能获得完整原始 LayerGroup 序列 ordinal。
可采用 before/from visitor 或更通用 indexed/range visitor。
关键:
parameters.currentLayer、depthRangeSize、opaque/translucent ordering 必须与完整遍历等价。
不得让 dynamic subset 从 0 重新编号导致 depth/transparency 变化。
## 6. 修改三:Renderer-owned RetainedUnderlayCache
优先新增:
- src/mbgl/renderer/retained_underlay_cache.hpp
- src/mbgl/renderer/retained_underlay_cache.cpp
并在 CMakeLists.txt 真实 source list 注册。
至少维护:
- offscreen renderable / texture
- viewport size
- boundary
- valid
- buildCount
- reuseCount
- fallbackCount
- last invalidation reason
不要修改 Heatmap/Hillshade RenderTarget 原有语义。
STEP-088 至少保证:
- resource 创建失败 -> full-render fallback
- viewport resize -> invalid
- boundary change -> invalid
- has3D -> fallback
- Debug Overdraw 等明显不兼容模式 -> fallback
- context 不可用 -> 不复用
- Camera fingerprint 连续相同、style loaded、2D、boundary stable 才允许 reuse
任何不能证明安全的状态都 full render。
## 7. 修改四:Renderer::Impl 分离 Static Underlay / Dynamic Range
重点:
- src/mbgl/renderer/renderer_impl.hpp
- src/mbgl/renderer/renderer_impl.cpp
STEP-088 必须保留:
- Renderer::render -> createRenderTree
- source update/prepare
- layer prepare
- updateLayers
- LayerGroup tweaker/upload
只修改真正 draw 阶段。
目标流程:
1. existing independent RenderTargets。
2. cache invalid 时,boundary 前 LayerGroups render 到 offscreen 并 mark valid。
3. main clear。
4. composite cached underlay texture。
5. boundary 及之后 opaque/translucent 正常 render。
6. legacy items 按安全规则。
7. debug。
8. present。
Underlay offscreen 优先透明 clear。
main framebuffer 保持原 backgroundColor/depth/stencil 语义。
必须确认 background layer optimized-as-color 路径。
## 8. Legacy RenderItems
renderer_impl.cpp translucent pass 仍有 legacy layerRenderItems。
必须确认是否有 boundary 以下 legacy item。
- 无法安全按 layerIndex 切分时不纳入 cache。
- 可能破坏 ordering 时 fallback full render。
- 不得静默改变顺序。
## 9. 修改五:cached texture compositor
不要正式复用 LocationIndicatorTexturedShader。
建议新增:
RetainedUnderlayCompositeShader
职责:
- viewport/fullscreen quad
- sample cached RGBA
- 正确 premultiplied alpha
- 不做 color ramp
- 不做 map transform
- 不做 Particle 业务
按当前真实 shader 注册规则检查/修改至少:
- include/mbgl/shaders/shader_source.hpp
- src/mbgl/shaders/shader_source.cpp
- include/mbgl/shaders/shader_defines.hpp
- include/mbgl/shaders/shader_manifest.hpp
- include/mbgl/shaders/gl/shader_info.hpp
- src/mbgl/shaders/gl/shader_info.cpp
- src/mbgl/gl/renderer_backend.cpp
- src/mbgl/mtl/renderer_backend.cpp
- cmake/opengl.cmake
- cmake/metal.cmake
- 新增 GL/Metal compositor shader 文件
如当前 shader codegen 要求生成文件,按真实 codegen 走。
Android OpenGL 与 iOS Metal 语义都要完整。
## 10. Renderer Debug Counters
修改:
- include/mbgl/particle/renderer_debug_counters.hpp
至少新增:
- FullRendererFrame
- RetainedUnderlayBuild
- RetainedUnderlayReuse
- RetainedUnderlayFallback
- RetainedUnderlayInvalidation
- RetainedUnderlayStaticGroupDraw
- RetainedDynamicGroupDraw
要求:
- NDEBUG 下 no-op
- 不增加 Host API
- 不每帧刷大量日志
允许有限首次日志:cache enabled/build/fallback reason。
## 11. Native 定向测试
不新增或运行 Jest。
至少覆盖:
Boundary:
- 无 Weather/Particle -> disabled
- only Particle
- Weather + Particle 不同顺序
LayerGroup range:
- before/from
- forward/reverse
- original ordinal
Cache:
- first build
- reuse
- resize invalid
- boundary change invalid
- unsupported 3D fallback
Failure:
- compositor/resource 创建失败 -> safe fallback/no crash
测试放当前 renderer test 真实结构,不新造孤立测试框架。
## 12. Mac 编译与验证
本任务采用仓库 AGENTS.md 的固定闭环:
`Codex 写代码 → RDC 执行编译/测试 → 失败则把错误证据交回 Codex 修复 → RDC 再验证`
Codex 本轮负责:
- 完成 Renderer / OpenGL / Metal 相关源码与测试代码实现;
- 给出 RDC 需要执行的精确命令;
- 不自行启动、等待或轮询长时间编译/测试;
- RDC 返回失败日志后,继续分析并修复代码。
由 RDC / 普通 Terminal 实际执行:
- 编译受影响 Renderer C++ target;
- 编译 OpenGL compositor shader 相关 target;
- 编译 Metal compositor shader 相关 target;
- 运行直接相关 C++ 定向测试;
- git diff --check。
RDC 返回结果时:
- 成功:返回 exit code、耗时、target 摘要;
- 失败:返回首个真实 error、相关 target、必要上下文和完整日志路径;
- 不把完整大型 build log 反复送入 Codex。
明确禁止 Codex 执行:
- CMake / Ninja / Bazel 长时间编译;
- OpenGL / Metal 长时间构建等待;
- Android Gradle build;
- AAR/APK build/install;
- ADB;
- Android App 启动/清数据;
- Android 真机/虚拟机;
- Jest。
Android 最终由用户手工验证。
## 13. PoC 验收证据
理想 fixed Camera + Particle ~30 FPS:
- RetainedUnderlayBuild ≈ 1
- RetainedUnderlayReuse 持续增长
- ParticleSimulationAdvance 持续增长
- RetainedUnderlayStaticGroupDraw 建 cache 后不随 Particle FPS 增长
- RetainedDynamicGroupDraw 持续增长
如 Mac 无法跑完整 Particle runtime:
- 完成 Native 定向/静态验证
- 给出用户最少 Android 手工验证项
- 最终状态必须:IMPLEMENTED / WAITING FOR ANDROID VALIDATION
- 不得写 CLOSED / PASS
## 14. 本任务明确不做
- production 全部 tile/style/placement invalidation
- Map::Impl::onUpdate fast path
- 跳过 createRenderTree
- 跳过 source->update
- 跳过 TilePyramid::update
- 跳过普通 prepare
- 新 Host API
- NavSea React 修改
- AIS 专项优化
- 通用任意 static/dynamic RenderGraph
- STEP-089
- STEP-090
## 15. 文档输出
必须生成:
- docs/50_Step记录/STEP-088_静态底图缓存PoC与Renderer观测_v1.md
- docs/报告/STEP-088_静态底图缓存PoC与Renderer观测_Codex完成报告_v1.md
如同主题已有,递增版本,不覆盖旧报告。
Step 记录至少写:
- 当前问题
- 实现结构
- boundary
- underlay cache flow
- PoC 限制
- 用户 Android 手工验证项
完成报告必须写:
- 起始 HEAD / 最终 HEAD
- 修改文件
- boundary 实现位置
- LayerGroup 分段实现位置
- cache/compositor 实现位置
- debug counters
- 测试/编译结果
- Android 未执行项
- fallback
- 是否满足进入 STEP-089 条件
- final commit hash
- commit message
- push result
- remote branch
- Final HEAD
## 16. Git 收尾
完成后:
1. 只 stage/commit STEP-088 相关文件。
2. 不提交当前主仓库既有无关 dirty/untracked 内容。
3. commit message 建议:feat: add retained underlay render cache poc
4. push 到 origin/particle/main。
5. push 前 fetch;远端前进时安全整合本任务。
6. push 失败不得声称完成。
7. 最终确认任务提交已在 origin/particle/main。
## 17. 最终响应
直接逐项报告:
- 已完成
- 未完成
- 无法验证
不要再输出实施计划。

View File

@@ -0,0 +1,144 @@
# STEP-089 静态渲染 Revision 与 Invalidation 基础层 Codex 任务 v1
## 目标
为 STEP-090 Static CPU Fast Path 建立可靠、可验证的静态状态失效判据。
STEP-089 不负责跳过 static source/tile/render-tree/prepare,也不负责降低 CPU;只建立“静态前缀当前是否仍可安全复用”的 revision / invalidation 基础,确保后续 fast path 不会复用过期静态状态。
当前基线:
- branch: `particle/main`
- baseline HEAD: `07a17b4c701cd22c8e8b516ad84f4044f291f9fa`
- STEP-088 retained underlay 已完成:
- GPU static draw cache
- map-scoped runtime Cache ON/OFF
- Release 最小 diagnostics
- Android/iOS/RN runtime API
- Android NavSea 真实 PBF 性能场景
- 实测证明主要剩余成本是 Particle continuous repaint 下 PBF/vector/static renderer pipeline 被重复 processing。
- STEP-090 才负责 CPU fast path。
## 必做项
- [ ] 引入单调递增的 static/retained revision 机制,能表达“静态前缀相关输入已变化”。
- [ ] retained cache build 时记录所依据的 revision。
- [ ] reuse 前必须确认当前 revision 与 build revision 一致。
- [ ] 下列变化必须使 revision 前进或明确 invalidate:
- [ ] camera / transform
- [ ] viewport / size / pixel ratio
- [ ] style reload / style mutation
- [ ] layer add/remove/reorder/property mutation
- [ ] source add/remove/change
- [ ] tile / drawable 内容更新
- [ ] image / sprite / glyph 变化
- [ ] placement / symbol fade 状态变化
- [ ] render context / surface / backend resource reset
- [ ] debug render options
- [ ] terrain / 3D / custom layer 等当前不能安全证明静态的场景
- [ ] symbol fade / placement transition 活跃时不得复用 stale static cache。
- [ ] unsupported / uncertain 情况继续 fail closed 到 full renderer。
- [ ] Cache OFF/ON runtime 行为保持不变。
- [ ] Release diagnostics 保持低成本;允许新增只读 revision 字段,但禁止 per-frame Release 日志。
- [ ] Debug 日志可增加 revision / invalidation reason,便于 RDC 验证。
- [ ] 原有 LayerGroup ordinal、dynamic boundary、transparent underlay、legacy ordering 语义不得回退。
- [ ] 不实现 STEP-090 的 static CPU processing skip。
- [ ] 不新增 React 管理的 renderer cache。
- [ ] 不把 provider/network/NavSea Store 逻辑引入 MapLibre Core。
- [ ] 不使用 Jest。
## Revision 设计约束
优先采用单调 `uint64_t` revision / generation:
- revision 只增不减;
- 不依赖 wall clock;
- 不要求每种事件都维护独立缓存副本;
- 可以保留 reason/category 作为诊断,但 correctness 依赖 revision,而不是字符串 reason;
- revision wraparound 可按现实上不可达处理,但类型必须明确。
建议最少区分:
- 当前 static revision;
- retained underlay build revision;
如现有 Renderer/RenderOrchestrator 的生命周期更适合其他命名,可调整,但不要把模型复杂化。
## Invalidation 范围原则
STEP-089 首选“安全偏保守”。
如果某类状态变化是否会影响 static prefix 无法可靠判断:
- 直接 bump revision / invalidate;
- 不做复杂局部 dependency graph;
- 不为了提高 hit rate 提前实现 STEP-090 级别的细粒度 source/layer dependency tracking。
目标是让 STEP-090 有一个可信的 fast-path gate,而不是在 089 追求最高 cache reuse。
## Placement / Symbol 特别规则
已有 Android 真机证据:
- Particle 放到全部 63 层上方时,symbol-heavy prefix 会出现持续 `static-placement` / `static-transition`。
- BEFORE_SYMBOL_CLUSTER(fill/line 在下,symbol cluster 在上)是目前最稳定边界。
因此:
- placement/fade active 必须阻止旧 static state 被错误复用;
- fade/placement 稳定后允许重新 build 并记录新 revision;
- 不得简单忽略 symbol placement 来提高 TOP 场景 reuse。
## Runtime API / Diagnostics
保持现有:
- `setRetainedUnderlayEnabled`
- `getRetainedUnderlayDiagnostics`
允许在 diagnostics 增加:
- `staticRevision`
- `buildRevision`
如增加,Android/iOS/RN 三端需一致;如果为了控制 STEP-089 范围决定暂不公开,也至少在 Debug Native 日志中可观察。
不要新增第二套 runtime module / map registry。
## 测试要求
新增/扩展 Native directed tests,至少覆盖:
1. revision 单调递增;
2. build revision 与 current revision 相等时允许 reuse;
3. camera/viewport/style/source 变化后 revision 改变并禁止旧 cache reuse;
4. placement/fade active 时不 reuse stale cache;
5. Cache OFF -> invalidate;重新 ON -> 下一次重新 build;
6. unsupported / resource failure 保持安全 fallback;
7. 原有 RetainedDynamicBoundary / LayerGroupRange / RetainedUnderlayCache 测试继续通过。
不要求 Jest。
## Codex 执行规则
- 先完整阅读本任务、AGENTS.md、STEP-088 v3 Step/报告,以及 retained underlay 当前实现。
- 不需要提交实施计划。
- 直接实现。
- 不运行 Gradle / Android build / ADB / Xcode。
- 不运行大型 noisy build/test;编译和最终验证由 RDC 负责。
- 可以做必要的静态代码检查,但不要替代 RDC 编译。
- 不 commit/push,等 RDC 验证后统一收尾。
- 不触碰用户既有无关 dirty/untracked。
- 不使用 worktree。
- 无法完成的项明确说明,不得静默跳过。
## 完成时必须报告
逐项按“已完成 / 未完成 / 无法验证”报告上述必做项,并列出:
- 修改文件;
- revision 的实际所有者与生命周期;
- 每类 invalidation 在哪里触发;
- 新增测试名;
- RDC 应执行的精确 OpenGL / Metal / Android Validation 命令;
- 明确说明没有实现 STEP-090 fast path。

View File

@@ -0,0 +1,239 @@
# STEP-090 Particle-only Static CPU Fast Path Codex 任务 v1
## 目标
基于 STEP-088 retained underlay GPU cache 与 STEP-089 static revision/invalidation gate,真正减少 Particle continuous repaint 下静态 PBF/vector 的 CPU processing。
当前已实测:
- PURE PBF idle:RenderThread ~0%
- PURE PARTICLE 2048 / FPS30:RenderThread ~10.2%
- production Style + 60 个 PBF layer hidden + Particle:RenderThread ~25.5%
- production PBF + Particle:RenderThread ~96~100%
- STEP-088 仅省 static GPU draw,RenderThread 仍 ~96%
- STEP-089 已保证:
- steady state staticRevision/buildRevision 稳定;
- camera/style/tile 等真实变化会 revision bump;
- transient drawable/topology churn 不误触发。
因此 STEP-090 的目标是:
> 当当前帧仅由 Particle animation 触发,且静态状态 revision/topology/camera/viewport/placement 等均稳定时,跳过不必要的静态 PBF/vector CPU pipeline,复用上一帧已准备好的 render state / RenderTree,并只执行 Particle 所需的动态 render。
## 基线
- branch: `particle/main`
- baseline HEAD: `98e1d2d977ba9e007ba313c14d63bfa54ab9d23b`
- STEP-089: CLOSED / PASS
## 核心约束
### 1. Fast path 必须在 createRenderTree 之前判定
当前:
`Renderer::render(UpdateParameters)`
→ `RenderOrchestrator::createRenderTree()`
→ `renderTree->prepare()`
→ `Renderer::Impl::render()`
如果 fast path 只放在 `Renderer::Impl::render()`,已经无法节省 createRenderTree/source update/prepare,不满足 STEP-090。
### 2. 仅允许 Particle-only frame
必须内部区分:
- Particle-only animation frame;
- 其他任何会影响 map/render tree 的更新。
Fast path 只在能够证明“除了 Particle animation,没有其他更新需要处理”时使用。
不得新增 Host/RN 手工调用:
- 不要 `particle.renderFastFrame()`
- 不要 `cacheMapNow()`
- 不要 `invalidateBasemap()`
这是 MapLibre Native 内部自动行为。
### 3. 安全 gate
至少要求:
- retained underlay runtime enabled;
- retained cache valid;
- retained buildRevision == current staticRevision;
- camera / transform unchanged;
- viewport / pixelRatio unchanged;
- styleLoaded;
- dynamic boundary unchanged;
- topologyRevision unchanged;
- no static transition;
- no placement/fade transition affecting correctness;
- no source/tile/image/glyph/style/layer mutation;
- no context reset/debug/3D/custom unsupported;
- no pending change request that would alter render tree;
- no non-particle layer transition/repaint;
- no fading tile;
- no still image request;
- no map mode / special render path that cannot safely reuse。
任何不确定情况必须 full path。
### 4. RenderTree reuse
优先实现一个最小、明确的 cached prepared RenderTree / render state 生命周期:
- full frame 成功创建并 prepare 后,可作为下一次 particle-only fast frame 的候选;
- fast frame 不重新 createRenderTree;
- fast frame 不重新调用 full `RenderTree::prepare()`;
- 复用对象生命周期必须明确,不能悬挂引用;
- full path 创建新 tree 后替换旧缓存;
- revision/context/style reset 时失效。
若 RenderTree 当前设计无法安全长期持有,允许提取一个专用 immutable prepared frame state,但不要大规模重构。
### 5. 动态部分
Particle fast frame必须仍然:
- 正常 advance simulation;
- 正常 render Particle;
- composite retained underlay;
- 正常 present;
- 保持 particle cadence/targetFps 语义。
对于 Particle 之外的动态 layer:
- 如果存在其他 `needsRepaint()` / transition / placement / fading tile,则禁止 fast path,走 full path。
- 不要在本步骤尝试支持 AIS/Ownship 等所有未来动态 layer;先确保 Particle-only fast frame 安全。
WeatherColor:
- 若本身不需要 repaint 且输入未变,可与静态 prepared state 一起复用;
- 若其状态变化,full path。
### 6. Source/PBF processing
Fast path 命中时,目标是明确不执行:
- full `createRenderTree()`
- static PBF/vector source update
- tile pyramid normal per-frame update
- static layer prepare
- static placement orchestration
- static layer group rebuild
允许仍执行 fast frame 所必须的最小 upload/tweaker/render,但要避免重新走静态 source/layer CPU pipeline。
### 7. Runtime ON/OFF
继续使用现有 `retainedUnderlayEnabled` 作为整体优化 gate:
- ON:允许 retained cache + CPU fast path;
- OFF:完全回到原 full-render baseline,用于 A/B;
- 不新增第二个用户开关,除非内部 debug-only 诊断确实必要。
这样同一个 Release 构建仍可通过 RN RendererRuntime 做完整 A/B。
### 8. 诊断
Debug 增加最小 counters/log:
- `ParticleFastFrameHit`
- `ParticleFastFrameMiss`
- `ParticleFullFrame`
- miss reason(debug only)
建议周期日志继续并入 `ParticleRetained:` 或新增稳定前缀 `ParticleFastPath:`,字段至少:
```text
hit=<n>
miss=<n>
full=<n>
reason=<...>
staticRevision=<n>
buildRevision=<n>
```
Release 不做 per-frame 日志。
如现有 diagnostics 适合,可在 Release pull snapshot 中增加累计 hit/miss,但不是强制;不要扩大 RN 高频接口。
### 9. 性能验收目标
不预设固定百分比门槛,但 Android NavSea PBF Test App 必须证明:
- AFTER_CLIP / BEFORE_SYMBOL_CLUSTER 固定 Camera;
- Cache/FastPath ON 时 fast hit 持续增长;
- full frame 不随 Particle frame 一比一增长;
- Particle 仍连续运动;
- static draw 继续不随 particle frame 增长;
- RenderThread CPU 相比 STEP-089 / Cache OFF 有明显下降。
关键基线用于比较:
```text
PURE PARTICLE ~10.2% RenderThread
PBF hidden + Particle ~25.5%
PBF visible + Particle ~96~100%
```
目标方向是让 PBF visible + Particle 大幅向前两者靠近,而不是只减少几个百分点。
### 10. 回退验证
必须验证:
- pan / zoom / rotate → fast path 立即 miss/full;
- tile load/change → full;
- style/layer/source change → full;
- cache OFF → full;
- static state重新稳定后 → fast hit恢复;
- TOP symbol-heavy / placement active → 保守 full/fallback;
- resize/context reset → full;
- no crash / stale map / ordering regression。
## 不做
- 不新增 Host API 触发 fast frame。
- 不把 provider/network/vendor 逻辑放入 Native。
- 不改 RN Weather Service 边界。
- 不做 STEP-091 级别的多动态层通用调度。
- 不做复杂 dependency graph。
- 不做 Jest。
- 不使用 worktree。
## Native 测试
新增 directed tests,至少覆盖:
1. particle-only + stable revision → fast candidate true;
2. revision mismatch → false;
3. camera/viewport mismatch → false;
4. non-particle transition/repaint → false;
5. placement/fade/fading tile → false;
6. cache disabled → false;
7. context/style/source/tile mutation后 → false;
8. full frame重新稳定后 → fast candidate true;
9. cached RenderTree 生命周期/替换不悬挂;
10. 原 12 个 retained tests继续通过。
## Codex 执行规则
- 先完整读取 AGENTS.md、STEP-088 v3、STEP-089 task/Step/报告、当前 renderer.cpp / render_orchestrator / renderer_impl / RenderTree。
- 不提交实施计划,直接调查并实现。
- 不运行 Gradle/ADB/Xcode/大构建。
- 不 commit/push。
- 不触碰无关 dirty/untracked。
- 不使用 worktree。
- 不加 Jest。
- 如果发现当前 RenderTree 不能安全缓存,先在当前架构内采用最小安全方案,不进行无边界重构。
- 如果实现范围明显膨胀到新的架构层,停止并报告阻塞点,不强行完成。
## 完成时必须报告
逐项说明:
- fast path gate 实际位于哪里;
- 如何判断 particle-only;
- 哪些 full-frame 工作被跳过;
- cached RenderTree / prepared state 生命周期;
- 所有 miss/fallback 条件;
- 修改文件;
- 新增 tests;
- RDC 精确验证命令;
- 明确没有新增 Host 手工 fast-frame API。

View File

@@ -0,0 +1,225 @@
# STEP-091 Zoom Out 后粒子 Viewport 一次性重播种 Codex 任务 v1
## 目标
修复正式 RasterParticleLayer 在“大 Zoom → 小 Zoom(Zoom Out)”后,旧粒子短时间聚集在原视野小区域,必须等待粒子生命周期/运动逐渐扩散后才恢复正常密度的问题。
用户实机现象:
- Zoom Out 完成后,粒子集中成一小团/小矩形;
- 风场和粒子仍继续运动;
- 等旧粒子跑远或寿命到期后,整个新视野才逐渐恢复正常铺开;
- Zoom In 暂未观察到同类问题。
本任务必须以当前实际源码为准修复,不改 Field/T0/T1/Timeline 数据契约,不引入第二套粒子系统。
## 基线
- repo: MapLibreParticleLab
- branch baseline: `origin/particle/main`
- baseline HEAD: `297b9126200cea3da3bdef2faacedcd9845f6d08`
- 实施工作树: `/Users/tei/dev/MapLibreParticleLab-step091`
- 实施分支: `step091-zoom-out-reseed`
- STEP-090 已 CLOSED / PASS,retained underlay + Particle-only CPU fast path 必须保持。
## 已确认根因
重点读取:
- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`
- OpenGL / Metal particle shader
- 当前 camera invalidation、respawnBounds、State/Trail seed 逻辑。
当前正式路径已经存在:
- `TrailCameraStableDelay = 150ms`
- camera 变化时 `trailCameraInvalidated=true`
- camera stable 后重新启用 viewport density
- `viewportRespawnDomain(...)`
- `respawnBounds`
- shader 的 `outsideRespawnBounds(position)`
- `stateSeeded / trailSeeded`
- initial seed 时由 shader 使用 `respawnSeed(...)` 在 `respawnBounds` 内播种。
问题是 Zoom Out 后新的 respawnBounds 变大,旧粒子仍位于扩大后的 bounds 内,因此不会命中 outsideRespawnBounds,只能等待 age/invalid/运动触发 respawn。
## 修复原则
Camera 稳定后,如果本次手势/连续 camera 变化的最终结果是明确 Zoom Out,则执行一次 viewport density reseed:
- 下一次 State pass 把全部粒子重新播种到“新的、已经计算完成的 respawnBounds”;
- 同时清理/重新 seed Trail,避免旧小视野 trail 与新粒子叠加;
- 之后立即恢复正常连续 simulation。
这不是完整 Simulation reset。禁止为了 Zoom Out:
- 调用 `resetSequence("ZOOM_OUT")`;
- 重建 State/Trail GPU resource;
- 重新上传 Field texture;
- reset Timeline / T0 / T1 / MIX;
- style reload;
- source reload;
- Host/RN 手工 reset API。
优先复用现有 `stateSeeded=false` / `trailSeeded=false` 和 shader initial-seed 机制;若实际代码证明还需一个极小内部状态,可最小新增。
## Camera 手势状态
需要记录“一次 camera invalidation 开始前”的稳定 Zoom,不能每帧覆盖。
建议最小状态语义:
- camera gesture / invalidation 开始时保存 `startZoom`;
- camera 变化期间仍沿用现有 invalidated 行为;
- 150ms stable 时比较最终 `endZoom`;
- 仅当 `endZoom < startZoom - epsilon` 才请求 viewport reseed;
- epsilon 仅防浮点/微小抖动,建议约 0.05,实际值可按现有 double/zoom 语义选择并写明理由;
- Zoom In、纯 Pan、Rotate、Pitch 不触发 full reseed;
- 新一轮 camera invalidation 必须重新建立 startZoom,不得复用上一轮。
## 执行顺序要求
必须保证 reseed 使用新的 viewport domain:
1. camera 变化 → invalidated;
2. camera 连续变化期间不触发 reseed;
3. stable debounce 到期;
4. 计算当前 `viewportRespawnDomain(transformState, viewport, field)`;
5. 得到当前 `viewportDensityActive` 和新的 `respawnBounds`;
6. 仅在本轮最终为 Zoom Out 且 viewport respawn domain 有效时执行一次:
- `stateSeeded = false`
- `trailSeeded = false`
- 清除 pending 标记;
7. 下一次 State/Trail pass 使用现有 initial seed 流程;
8. 后续帧继续正常 simulation。
若 stable 时 respawn domain fallback/无效:
- 不允许留下会在未来无关 Pan 时突然触发的 pending reseed;
- 必须安全消费/取消本轮请求并记录原因;
- 不得伪造 viewport domain。
## Trail
Zoom Out viewport reseed 必须同时让 Trail 重新 seed/清理。
不得保留旧小视野 trail 作为新 viewport 的历史。
不得改变正常连续播放时的 trail fade、T0/T1 overlap、simulationRebuild=false 语义。
## Shader / UBO 边界
当前 GL/Metal shader 已有:
- initial seed;
- respawnSeed;
- respawnBounds;
- alpha/validity 防止 respawn jump 连线。
本任务默认不修改 shader、UBO layout 或公共 Layer API。
只有实际源码证明现有 initial-seed 无法正确完成 viewport reseed 时,才允许最小修改,并在完成报告说明原因。
绝对禁止:
- partial reseed ratio 新协议;
- per-particle CPU readback;
- JS/RN 粒子重播种;
- 新增 Host camera callback 驱动粒子;
- 将 provider/network/vendor 逻辑放入 Renderer。
## STEP-089 / STEP-090 保护
Camera 变化期间原本就应使 fast path miss/full;稳定后应恢复 fast hit。
本次修复不得破坏:
- retained underlay cache;
- Particle-only static CPU fast path;
- staticRevision/buildRevision 语义;
- camera/style/tile mutation fail-closed;
- normal continuous particle cadence;
- WeatherColor 与 RasterParticle 分层;
- Runtime Field Window/T0/T1/MIX。
## 诊断日志
新增或扩展最小 Debug 日志,至少能确认一次 Zoom Out reseed:
建议稳定前缀:
`ParticleDensity: event=VIEWPORT_RESEED reason=ZOOM_OUT ...`
字段至少包含:
- startZoom
- endZoom
- respawnBounds
- stateReseed=true
- trailReseed=true
- fieldResubmit=false
- timelineReset=false
- gpuResourceRebuild=false
fallback/无效 domain 时输出一次 skip/cancel 原因即可。
禁止 per-frame spam。
## 定向测试
不得加入 Jest。
至少验证:
1. Zoom Out 超过 epsilon → stable 后请求一次 reseed;
2. Zoom In → 不请求;
3. 纯 Pan → 不请求;
4. Rotate/Pitch 且 Zoom 未下降 → 不请求;
5. Zoom Out 后 fallback domain → 不留下延迟 pending;
6. 一次 gesture 只 reseed 一次;
7. 下一轮 gesture startZoom 正确刷新;
8. 现有 respawn/State/Trail 生命周期测试继续通过(如已有可复用测试)。
测试实现优先使用小型 C++ helper / directed native test;不要为了测试扩大公共 API。
若当前测试结构无法直接覆盖 private renderer 状态,允许测试提取出的纯判定 helper,但不得引入复杂新模块。
## Mac 验证
Codex 需要:
- 读取根目录 `AGENTS.md` 与相关 STEP-055、STEP-089、STEP-090 记录/报告;
- 执行与修改直接相关的 Mac 原生精确编译/定向 C++ 测试;
- noisy build 遵循全局 `$build-output-throttle`;
- `git diff --check` PASS;
- 不运行 Android Gradle/APK/ADB;
- 不运行 Jest。
## Android Test App
本任务默认不新增 UI 控件。
现有 Particle Core Layer Validation / 正式 RasterParticleLayer 场景应足以通过 ADB 手势验证。
如果现有 Test App 无法可靠触发正式 viewport respawn path,才允许最小增加 debug-only validation 入口,禁止改产品 Host API。
## 实机验收预期(由 RDC 在 Codex 完成后执行)
- 编译 OpenGL Debug Test APK;
- 安装到当前可用 Android 测试设备;
- 启动 `ParticleCoreLayerValidationActivity`;
- 使用正式 RasterParticleLayer 场景与较高粒子数;
- 做明显 Zoom In 后再快速 Zoom Out;
- 日志出现一次 VIEWPORT_RESEED;
- Zoom Out 后不再等待 10 秒级生命周期才铺满新视野;
- 无 crash / ANR / GL/EGL error;
- camera stable 后 ParticleFastPath fast hit 恢复增长。
## 文档与 Git
Codex 完成后必须:
- 更新 `粒子开发记录.md`;
- 生成 `docs/50_Step记录/STEP-091_ZoomOut_Viewport_Particle_Reseed_v1.md`;
- 生成 `docs/报告/STEP-091_ZoomOut_Viewport_Particle_Reseed_Codex完成报告_v1.md`;
- 完成报告逐项写“已完成 / 未完成 / 无法验证”,不要写实施计划;
- 记录实际修改文件、实际测试命令与结果;
- 记录是否改 shader/UBO/API;
- 记录 STEP-090 fast path 是否保持。
提交本任务相关变更到当前工作分支,并 push:
`git push origin HEAD:particle/main`
不得提交任何与本任务无关的改动。
push 失败不得声称任务完成。
完成报告必须记录:
- implementation commit hash
- commit message
- push result
- remote branch
- Final HEAD / origin/particle/main
## 完成标准
只有同时满足以下条件才可判定 Codex 阶段 PASS:
- 根因按当前代码被修复;
- Zoom Out stable 后只做一次 viewport State/Trail reseed;
- 不做 full simulation reset / field resubmit / timeline reset / GPU resource rebuild;
- Zoom In/Pan/Rotate/Pitch 不误触发;
- 定向 C++ 测试与 Mac 精确编译通过;
- git diff --check 通过;
- 文档完成;
- commit + push 到 `origin/particle/main` 成功。
Android APK 构建、安装与设备手势验收由后续 RDC 串行执行,不属于 Codex 自己的完成条件。

View File

@@ -0,0 +1,249 @@
# STEP-092 Continuous Range 可退化分段 Retained Cache — Codex 任务 v1
## 0. 目标
在当前 MapLibreParticleLab / Particle V11 Renderer 上实现一个 explicit Continuous Range。
正式架构定义:
App 用正常 Style 定义唯一 Z-order,并指定两个现有 Style Layer ID 作为闭区间 Continuous Range;
Renderer 将两个 ID 解析为连续 layerIndex,根据 Continuous Range 在 Style 中的位置自然形成 0~2 个 Retained Range Cache;
最终可以是 1、2 或 3 个 Render Range,绝不是固定“三层三明治”。
本任务只支持一个 Continuous Range,不做多个 Range 或任意 N Segment Planner。
## 1. 环境与起始确认
工作目录:/Users/tei/dev/MapLibreParticleLab
目标分支:particle/main
历史参考 HEAD:b29e564944197072a0effaec36ca95c48fe0649b,但不得据此强制回退。
修改前必须:
1. git fetch origin
2. 确认当前分支、HEAD、origin/particle/main
3. 记录起始 HEAD
4. 检查 git status --short
5. 不覆盖、不删除、不 reset/clean、不提交无关 dirty/untracked
6. 若远端更新,以当前真实 origin/particle/main 为基线
## 2. 唯一 Z-order 事实来源
Style Layer 顺序是唯一 Z-order 事实来源:
App/RN 负责业务语义和 Style Layer 顺序;
MapLibre Style 保存正式 Z-order;
C++ Renderer 只消费最终连续 layerIndex。
Renderer 禁止硬编码 NavSea 业务 Layer ID(AIS/Alarm/Wind/Wave 等)。
禁止建立 BOTTOM/CONTINUOUS/TOP 第二套 metadata 分类表。
禁止增加 AnchorLayer、ContinuousStartLayer、ContinuousEndLayer、RenderAnchorLayer、空白 Marker Layer。
## 3. Continuous Range API 语义
App 直接指定两个已有正常 Style Layer:
startLayerId
endLayerId
公开语义为闭区间 [startLayerId, endLayerId]。
Native 按当前 Style 解析成 startIndex/endIndex。
合法条件必须是 startIndex <= endIndex。
必须正式支持 startLayerId == endLayerId,单 Layer Continuous 完全合法。
App/RN 不传数字 index,RN 不计算 layerIndex。
内部建议转半开区间:
continuousBegin = startIndex
continuousEndExclusive = endIndex + 1
Bottom=[0,continuousBegin)
Continuous=[continuousBegin,continuousEndExclusive)
Top=[continuousEndExclusive,layerCount)
Range 存在判断统一为 begin < endExclusive。
## 4. 四种合法结构与 Cache 数量
必须支持:
1. Bottom + Continuous + Top → 2 retained caches
2. Bottom + Continuous → 1 Bottom cache
3. Continuous + Top → 1 Top cache
4. Continuous only → 0 retained caches
Continuous only 当 startIndex==0 且 endIndex==layerCount-1,是完全合法状态,不是 error/fallback。
不存在的 Range:
不创建 cache;
不分配 texture;
不分配 framebuffer/render target;
不 build;
不 composite;
不参与 cache-valid gate。
当前 RetainedUnderlayCache 应合理泛化为可复用 retained range cache 能力。
具体命名服从当前源码,但不得固定创建 bottomCache+topCache 占位资源。
Bottom/Top GPU 资源必须 lazy 创建。
## 5. Final Render 顺序
如果 Bottom 存在:build/reuse/composite Bottom
然后 render Continuous
如果 Top 存在:build/reuse/composite Top
顺序只能来自 Style,自然退化为 3/2/1 段。
## 6. global layerIndex / pass 语义
分 Range 后仍必须保持整个 Style 的:
原始 layerIndex;
原始 ordinal;
parameters.currentLayer;
depthRangeSize。
不得给 Bottom/Continuous/Top 各自从 0 重新编号。
Opaque 继续 reverse traversal;
Translucent 继续 forward traversal。
## 7. Visibility
Range topology 来自 Style/orderedLayers,不来自当前 visible/renderable Layers。
hidden start/end 仍必须参与 Range index 解析。
Layer ON/OFF 是内容变化,不是 Range topology 变化。
例如 start=10 hidden、11 visible、end=12 hidden,Range 仍是 10..12,不得缩成 11..11。
## 8. Style mutation
必须支持正常 Style mutation。
Continuous 内新增/删除普通成员正常工作。
Bottom/Top 内增删使对应实际存在 cache invalid/rebuild。
同一 Range 内 reorder 使 cache 内容 invalid/rebuild。
Layer 跨 Range 移动:重新解析 start/end index → topology revision → invalidate affected caches → invalidate fast prepared state → rebuild。
startLayerId/endLayerId 自身删除:Range 立即 invalid,清除 stale cache/index/prepared state并安全 fallback。
Layer 后续重新出现且顺序合法时可恢复 explicit Range。
## 9. Top Cache 正确性
Top retained texture 必须是透明 RGBA,并正确使用 premultiplied alpha。
目标支持 Particle 属于 Continuous、land-area 属于 Top Retained 时,Top texture 正确覆盖 Particle。
不得新增 LandMask RT、Wave/Tide mask system 或 Native land-mask 业务判断。
land-area 仍只是普通 Style Z-order。
## 10. Legacy compatibility
当前 WeatherColor/RasterParticle 可能通过 startsRetainedDynamicRange() 参与 legacy boundary。
规则:
explicit Continuous Range 已配置且有效 → STEP-092 Range 模式;
未配置 explicit Range → 保持当前 legacy retained-underlay 行为。
一旦 explicit Range 生效,startLayerId/endLayerId 是唯一 topology/boundary 来源。
禁止 explicit Range 与 startsRetainedDynamicRange() 混合生成第二套 boundary。
## 11. RN / Native API
在现有 RendererRuntime 增加稳定入口,目标语义:
setContinuousLayerRange(reactTag,startLayerId,endLayerId)
要求:ID 非空;允许相同;map-scoped;thread-safe;monotonic config revision;
Range 修改触发 renderer update/invalidation。
Android/iOS bridge 不理解业务;C++ 按当前 Style 解析 index。
保留 setRetainedUnderlayEnabled() 与 getRetainedUnderlayDiagnostics() 兼容。
## 12. Invalid / fallback
至少处理:
start 不存在;
end 不存在;
startIndex > endIndex;
Style 未 ready;
未配置 explicit range。
要求:不 crash;不猜 index;不复用 stale index/cache/prepared state;
不根据 Particle/WeatherColor 类型偷偷修正 explicit Range;
安全进入 legacy/full renderer;
diagnostics 说明 fallback 原因。
Continuous-only 不是 invalid/fallback。
## 13. Particle-only CPU Fast Path
现有 Particle-only CPU fast path 不得退化。
三段:Bottom reuse → Continuous render → Top reuse
只有 Bottom:Bottom reuse → Continuous render
只有 Top:Continuous render → Top reuse
0 cache:Continuous render
不存在的 cache 不能成为 Fast Path miss 原因。
Bottom 不存在时不得要求 bottomCache.valid=true;Top 同理。
Fast gate 只检查实际存在 retained ranges。
Continuous Range 本身必须通用,不依赖 RasterParticleLayer/Weather Layer ID。
若 Continuous 中存在当前 Fast Path 无法证明安全的未知 Layer:Fast Path fail closed,走正常 full CPU frame,Range Z-order/cache 仍正确。
不要为此重写整个 RenderGraph。
## 14. Invalidation
继续 correctness-first、偏保守模型,至少覆盖:
camera/transform/viewport/resize/pixel ratio/style reload;
layer add/remove/reorder/property;
source mutation/tile update/drawable update;
image/sprite/glyph;
placement/symbol fade/transition;
context reset/debug options/unsupported 3D/legacy ordering/resource/compositor failure。
无法判断只影响 Bottom 还是 Top 时,可同时 invalidate 所有实际存在 retained caches。
本任务不做复杂 dependency graph。
topology 变化还要 invalid Range index resolution、retained prepared state、Particle fast prepared state。
## 15. Symbol / Placement
Top 可能包含 symbol/label/Navigation/AIS/Alarm。
correctness 优先。
placement/symbol fade/transition 无法证明 reuse 安全时,invalidate/full fallback,稳定后 rebuild。
不重写 Placement 系统。
## 16. Diagnostics
至少增加/暴露:
rangeConfigured, rangeValid,
startLayerId,endLayerId,startIndex,endIndex,
bottomPresent,bottomBuild,bottomReuse,bottomInvalidation,
topPresent,topBuild,topReuse,topInvalidation,
fallback,lastReason。
bottomPresent=false 且 topPresent=false 可以只是 Continuous-only,不能自动判 error。
Release 保持 pull-based,不增加 per-frame 高频日志。
## 17. GPU 生命周期与双后端
实际 retained texture 数量必须严格等于实际存在 retained ranges:
Continuous only=0;
Bottom+Continuous=1;
Continuous+Top=1;
三段=2。
稳定 Continuous frame 不得每帧重建 retained texture/framebuffer/compositor resources。
Mac 上完成当前工程允许的精确 Native 验证。
OpenGL 重点:Bottom/Top target 创建复用、Top RGBA/premultiplied alpha、0/1/2 cache 无空资源、Continuous-only、Fast Path。
Metal 重点:同等 Range/cache topology、透明 Top composite、0/1/2 cache 生命周期、explicit Range/fallback 与 OpenGL 一致。
不得用 Linux Core/OpenGL 构建替代 Mac 基线。
若 Android 实机/Gradle/ADB 项未执行,报告必须明确列为“未执行 Android 项”,不得伪造通过。
## 18. Native directed tests(不加入 Jest)
至少覆盖:
start==end;
start<end;
start>end invalid;
start missing;
end missing;
hidden start/end;
Style reorder;
start/end 删除;
四种 Range 结构;
Bottom empty/Top empty/both empty;
不存在 Bottom 时不因 bottom cache missing 而 Fast Path miss;
不存在 Top 时不因 top cache missing 而 Fast Path miss。
还应验证:
explicit Range 有效时不混用 legacy boundary;
explicit Range invalid 不复用 stale cache;
single-layer Continuous 正常;
hidden endpoint 不改变 topology;
reorder 后重新解析 index;
endpoint 删除 fail safe;
0/1/2 retained cache resource 数量与 topology 一致;
Opaque/Translucent traversal 保持 global layerIndex 语义。
## 19. 明确不做
不做:任意 N Segment Planner、多个 Continuous Range、LOW/MEDIUM/HIGH 分类、
自动分析 Layer 更新频率、自动调整 Z-order、Anchor/Marker Layer、新 Style Layer 类型、
NavSea AIS 专项优化、Weather provider、Route ETA、Native land-mask 业务、
Wave/Tide 业务判断、第二套 Z-order、NavSea Layer ID 硬编码、
复杂 per-source dependency graph、PBF 重构/重生成、通用 RenderGraph、
Placement 重写、Particle 专属 Continuous 机制、Jest。
如发现必须突破边界才能继续,停止扩大范围并记录阻塞。
## 20. 必做文档
生成:
docs/50_Step记录/STEP-092_ContinuousRange可退化分段RetainedCache_v1.md
docs/报告/STEP-092_ContinuousRange可退化分段RetainedCache_Codex完成报告_v1.md
追加:粒子开发记录.md
同主题已存在则递增版本,不覆盖旧文件;所有文档中文。
完成报告必须包含:
起始 HEAD、最终 HEAD、修改文件清单;
C++ Renderer、RN API、Android API/bridge、iOS API/bridge;
Range config 所有者、ID→layerIndex 解析位置机制;
single-layer range、1/2/3 Render Range、0/1/2 retained cache;
invalidation、legacy compatibility、Fast Path、diagnostics、GPU lifecycle;
OpenGL、Metal、Native tests、未执行 Android 项、known limitations;
commit SHA/message、push result、remote branch、origin/particle/main Final HEAD、最终 git status --short。
## 21. Git
仅 scoped staging。
禁止 git add -A、git add .、git reset --hard、git clean。
不得触碰无关 dirty/untracked。
结束前:git diff --check;核对变更;只 stage 本任务文件;scoped commit;push origin particle/main。
push 后必须确认:
git rev-parse HEAD
git rev-parse origin/particle/main
git status --short
要求 HEAD == origin/particle/main。
push 失败不得声明完成。
## 22. PASS Gate
只有架构、四种 Range、0/1/2 cache、global layerIndex、Opaque/Translucent、Fast Path、
diagnostics、RN/Android/iOS API、Native directed tests、OpenGL/Metal 规定验证、
文档、scoped commit、push、远端 HEAD 一致全部满足,才可判 STEP-092 PASS。
关键项无法完成必须明确 PARTIAL/BLOCKED 和原因,禁止伪造 PASS。

View File

@@ -0,0 +1,652 @@
# STEP-093:Continuous Range 运行态综合验收
## 1. 任务目标
在 STEP-092 已完成并关闭的 Continuous Range / Retained Cache 架构基础上,进入真实 Renderer 运行态综合验收。
本任务不重新设计 STEP-092 架构,不引入新的 Z-order 体系,不重做 retained cache 结构。目标是通过可重复、可观察、可诊断的运行态场景,确认以下核心事实在真实渲染过程中成立:
1. Style Layer 顺序仍然是唯一 Z-order 真相。
2. `[startLayerId, endLayerId]` 能稳定解析为一个 Continuous Range。
3. Bottom / Continuous / Top 四种合法退化结构均能正确工作。
4. 实际 retained cache 数量严格为 0 / 1 / 2,不存在的 range 不创建无意义 GPU 资源。
5. Particle 连续播放时,稳定的普通 Style/PBF 内容能够命中 retained cache,而不是每帧重复完整绘制。
6. Top range 能真实覆盖 Continuous 中的 Particle,验证最终画面顺序为:`Bottom → Continuous → Top`。
7. Camera、viewport、style、layer/source 等变化发生时能够正确 invalidation、rebuild,并重新进入稳定 reuse。
8. Fast Path 失败时必须安全退回 full CPU frame,但不得破坏 Continuous Range、retained cache 和 Z-order。
9. Android/OpenGL 与 iOS/Metal 最终表现一致;若本阶段只能先完成 Android,则 iOS 可明确记录为后续待验收,不得虚报通过。
---
## 2. 当前基线
开始任务前必须先检查真实仓库,不得只依赖任务书中的 SHA。
预期起始基线:
- Repository: `MapLibreParticleLab`
- Branch: `particle/main`
- STEP-092 最终状态:`CLOSED / PASS(Mac Native 验证闭环)`
- STEP-092 最终已知 HEAD:`8a5d88008c142e2a80e0ff602d228e0e67de2d83`
- `HEAD == origin/particle/main`
- 工作区应为 clean
STEP-092 已完成的关键内容包括:
- Continuous Range 显式配置;
- Bottom / Continuous / Top 三段模型;
- 四种合法退化拓扑;
- 0 / 1 / 2 retained cache;
- Bottom/Top retained cache;
- Top 透明 RGBA + premultiplied alpha 合成;
- Style mutation 后重新解析;
- invalid explicit range 的安全 fallback;
- Particle Fast Path 与 retained cache 的协同;
- Android / iOS Bridge 接口;
- OpenGL Native build;
- iOS Metal-only native build;
- 定向 Native tests 29/29 PASS。
本 STEP 不重新证明上述静态设计,而是证明它们在真实运行态画面与资源生命周期中成立。
---
## 3. 开始前必读
Codex 开始前必须读取:
1. 仓库根目录 `AGENTS.md`
2. 当前粒子系统总规则 / 架构约束文档
3. `docs/50_Step记录/STEP-092_ContinuousRange可退化分段RetainedCache_v2.md`
4. `docs/报告/STEP-092_ContinuousRange可退化分段RetainedCache_Codex完成报告_v2.md`
5. STEP-092 相关实现文件与 diagnostics 实现
6. 当前 Android/iOS Particle Test App / validation 页面 / Host runtime 实现
7. 当前可用的测试按钮、场景、日志和 diagnostics 展示入口
如实际仓库结构与本任务书描述不一致,以当前真实代码为准,并在报告中写明差异。
---
## 4. 本任务职责划分
### 4.1 Codex 负责
Codex 负责:
- 调查当前可用的运行态测试入口;
- 为 STEP-093 补齐必要的 Test/Validation UI、场景切换、diagnostics 展示和最小测试辅助代码;
- 只在确有必要时补充可观察性;
- 修复在准备阶段发现的明确、局部、与 STEP-093 验收直接相关的问题;
- 为 RDC / 用户准备清晰、可逐项执行的运行态验收入口;
- 更新中文 STEP 记录、完成报告和开发记录;
- 提交并 push 自己完成的代码与文档。
### 4.2 RDC / 用户负责
实际 Android / iOS 编译、安装、启动、真机或模拟器运行、视觉操作与最终画面确认由 RDC / 用户执行。
Codex 不得仅依据静态代码、日志或自己推测就把运行态测试标记为 PASS。
如果没有实际执行某个运行态场景,必须写为 `NOT RUN`、`WAITING_RUNTIME_VALIDATION` 或其他明确的未完成状态,不得写成 PASS。
---
## 5. 严格保护的 STEP-092 架构
本 STEP 禁止借“测试方便”修改以下既定架构:
1. Style 顺序是唯一 Z-order 真相。
2. App/RN 只提供 `startLayerId`、`endLayerId`。
3. Native 自行解析 index。
4. Continuous Range 为闭区间 `[startLayerId, endLayerId]`。
5. 内部:`continuousBegin = startIndex`,`continuousEndExclusive = endIndex + 1`。
6. 三段:Bottom `[0, begin)`、Continuous `[begin, endExclusive)`、Top `[endExclusive, layerCount)`。
7. `start == end` 合法。
8. Continuous-only 合法,不是 fallback。
9. 不允许加入 anchor / marker layer。
10. 不允许增加第二套 BOTTOM / CONTINUOUS / TOP metadata 真相源。
11. 不允许 Renderer 硬编码 NavSea/AIS/Alarm/Wind/Wave 等业务 Layer ID。
12. Visibility 不改变 topology。
13. explicit range 有效时不得再混用旧 `startsRetainedDynamicRange()` 作为 topology boundary。
14. 不允许把普通 style layer 改造成新的特殊 layer type 来解决测试问题。
15. 不做任意 N-range planner。
16. 不做多个 Continuous Range。
17. 不做频率分类系统。
18. 不做 PBF 通用重构。
19. 不做 RenderGraph 重构。
20. 不加入 Jest。
---
# 6. STEP-093 必测场景
## 6.1 场景 A:Bottom + Continuous + Top
构造至少:Bottom 为普通 Style/PBF Layer,Continuous 为 Particle / Weather 动态 Layer,Top 为普通 Style/PBF Layer。
必须验证:
- explicit range valid;
- Bottom present = true;
- Top present = true;
- retained cache 实际数量 = 2;
- Bottom cache 可 build/reuse;
- Top cache 可 build/reuse;
- 最终画面顺序 `Bottom → Continuous → Top`;
- Top 内容能够真实覆盖 Particle;
- 无明显黑边、错误透明、旧 cache 或层序颠倒。
## 6.2 场景 B:Bottom + Continuous
Continuous 结束于当前 Style 最顶层。
必须验证:
- Bottom present = true;
- Top present = false;
- retained cache 实际数量 = 1;
- 不得创建 Top texture/FBO/compositor;
- 不存在的 Top 不得阻塞 Fast Path;
- 画面顺序正确。
## 6.3 场景 C:Continuous + Top
Continuous 从当前 Style 最底层开始。
必须验证:
- Bottom present = false;
- Top present = true;
- retained cache 实际数量 = 1;
- 不得创建 Bottom texture/FBO/compositor;
- 不存在的 Bottom 不得阻塞 Fast Path;
- Top 可正确覆盖 Particle。
## 6.4 场景 D:Continuous only
Continuous 覆盖全部 Style Layer。
必须验证:
- Bottom present = false;
- Top present = false;
- explicit range valid = true;
- retained cache 实际数量 = 0;
- 这是合法 Continuous-only,不得被识别为 fallback;
- 不创建 retained texture/FBO/compositor;
- Particle/continuous renderer 正常运行。
## 6.5 场景 E:Single-layer Continuous
设置 `startLayerId == endLayerId`。
必须验证:
- range valid;
- startIndex == endIndex;
- continuousEndExclusive == startIndex + 1;
- 正常参与 Bottom/Top 切分;
- 不得 fallback;
- 不得被误认为空 range。
---
# 7. Z-order 核心视觉验收
这是 STEP-093 的最高优先级运行态验收。
必须构造一个肉眼可以清楚确认覆盖关系的场景:
1. Bottom 中放一个明显底图/填充层;
2. Continuous 中运行 Particle;
3. Top 中放一个能够明显覆盖 Particle 的普通 Style Layer。
优先使用已有普通 Style Layer;如果测试环境缺少合适图层,可添加纯测试用普通 Style Layer,但不得引入新的特殊 layer type 或业务硬编码。
建议 Top 验证至少覆盖:opaque fill、半透明 fill、line;如当前测试环境可行,再加入 symbol/text。
必须确认:
- Top 真正绘制在 Particle 上方;
- Particle 不会穿透 opaque Top;
- 半透明 Top 与 Particle 合成正确;
- 无黑边;
- 无颜色异常变暗;
- 无错误双重 alpha;
- 无 cache 旧内容残留。
---
# 8. Stable Frame / Cache Reuse 验收
在 Camera、viewport、Style、普通 PBF/source 内容都不变化,仅 Particle 持续动画时,必须观察:
- Bottom/Top retained cache build 后持续 reuse;
- 不应每个 Particle frame 重建 retained cache;
- 不应每 frame 重建 retained texture;
- 不应每 frame 重建 FBO/offscreen texture;
- 不应每 frame 重建 composite group;
- Fast Path 在满足条件时能够持续命中;
- diagnostics 能够区分 build / reuse / invalidation。
如果现有 diagnostics 无法证明资源生命周期,可增加最小 debug-only counter / reason 字段。禁止为了测试引入生产环境高频日志风暴。
---
# 9. Camera / Viewport invalidation
至少验证:Pan、Zoom、Rotate、Pitch、viewport resize/orientation 或等价尺寸变化,以及测试环境可实际触发时的 pixel ratio 变化。
每项检查:
- 旧 retained cache 被正确 invalidation;
- 下一帧或正确时点 rebuild;
- rebuild 后重新进入 reuse;
- 不出现 stale cache;
- 不出现错位;
- 不出现黑屏;
- 不出现 Particle 与地图不同步;
- topology 不应无故改变。
---
# 10. Style Mutation / Topology 重新解析
必须提供可重复测试入口,至少覆盖:
### 10.1 Continuous 内部变更
添加 Layer、删除非 endpoint Layer、reorder Continuous 内部 Layer。
预期:重新解析必要状态;range 仍有效时保持正确 topology;cache 根据需要 invalidation;不产生第二套 Z-order。
### 10.2 Bottom 内变更
添加、删除、reorder,检查 Bottom cache invalidation / rebuild / reuse。
### 10.3 Top 内变更
添加、删除、reorder,检查 Top cache invalidation / rebuild / reuse。
### 10.4 Layer 跨边界移动
将某个普通 Layer 做有代表性的 Bottom → Continuous、Continuous → Top 或 Top → Bottom 跨 range 移动。
必须验证:
- ID → index 重新解析;
- topology/cache/prepared state 正确 invalidation;
- 新 Z-order 正确;
- 不使用旧 index;
- 不出现 stale cache。
---
# 11. Endpoint 删除与恢复
删除 start endpoint 时:explicit range 立即 invalid;不 crash;不猜测新 start;不继续使用 stale index/cache;进入既定安全 fallback;diagnostics 有明确 lastReason。
恢复 start endpoint 时:Native 能重新解析;explicit range 恢复 valid;正确重建需要的 cache;不需要重建整个 Map instance。
对 end endpoint 做同等验证。
---
# 12. Visibility 不改变 topology
对 start endpoint、end endpoint 或 Continuous 内普通 Layer 做 visible → hidden → visible。
必须验证:
- topology 仍以 Style 顺序解析;
- startIndex/endIndex 不因 visibility 改变;
- hidden endpoint 仍是 endpoint;
- 不自动收缩 Continuous Range;
- ON/OFF 只作为内容变化处理;
- 如需 cache invalidation,应是内容 invalidation,不得变成新的 topology 规则。
---
# 13. Invalid Explicit Range
至少验证:
1. startLayerId 不存在;
2. endLayerId 不存在;
3. startIndex > endIndex;
4. Style 尚未 ready 时配置;
5. 空 ID / 未配置(按实际 API 合法性处理)。
预期:不 crash;不猜测;不复用 stale cache;不按 Layer 类型自动修正;进入明确安全 fallback;diagnostics 显示 invalid 原因;条件恢复后能够重新进入 explicit range。
---
# 14. Particle Fast Path
## 14.1 可命中场景
Continuous 内只保留当前 Fast Path 已支持的动态内容。
检查:Fast Path 可持续命中;Bottom/Top 的存在与否只影响实际存在的 retained cache;0 cache 的 Continuous-only 不得因为“没有 retained cache”而错误阻止 Fast Path。
## 14.2 Fail Closed 场景
在 Continuous 中加入当前 Fast Path 不支持的普通/未知 Continuous Layer。
必须验证:
- Fast Path 退回 full CPU frame;
- 不 crash;
- 不错误跳过该 Layer;
- Continuous Range 本身保持 valid;
- Bottom / Top retained cache Z-order 仍正确;
- 不允许为了 Fast Path 强行把未知 Layer 移出 Continuous。
---
# 15. Top Alpha / Premultiplied Alpha 验收
必须重点看 Top retained cache 合成:transparent background、opaque fill、semi-transparent fill、line,以及可行时 symbol/text。
检查:透明区域正确露出 Particle;半透明区域正常叠加;opaque 区域完整覆盖;无黑色边缘;无 halo;无颜色被错误乘两次;无残影;OpenGL 与 Metal 设计保持一致。
---
# 16. 长时间运行与资源稳定性
在测试环境允许情况下进行持续运行测试,至少包括:Particle 连续播放、多次 Pan/Zoom/Rotate、多次 range 配置变化、endpoint 删除/恢复、visibility 切换、style mutation、再次稳定运行。
观察:
- 无持续 GPU resource 增长迹象;
- retained texture/FBO 不因 stable frame 每帧新建;
- 无明显内存持续增长;
- 无黑屏;
- 无闪屏;
- 无 stale cache;
- 无偶发层序颠倒;
- 无 Particle 停止后恢复不了的问题。
如果当前工程缺少可靠 GPU memory 数字来源,不要求伪造数值;允许使用资源创建/释放 counter、diagnostics 和稳定运行现象作为当前阶段证据,并明确限制。
---
# 17. Diagnostics 要求
STEP-093 的测试入口必须能清楚观察至少以下信息:
### Range
- configured
- valid
- startLayerId
- endLayerId
- startIndex
- endIndex
- continuousBegin
- continuousEndExclusive
- fallback / lastReason
### Bottom
- present
- cache state
- build count
- reuse count
- invalidation count
- last invalidation reason
### Top
- present
- cache state
- build count
- reuse count
- invalidation count
- last invalidation reason
### Fast Path
- eligible / active
- fallback reason
- full CPU frame count 或等价诊断(若现有已有)
- unknown continuous layer 导致 fail-closed 的可观察原因
如当前已有字段足够,不得重复造一套 diagnostics。
---
# 18. Test UI / Playground 要求
优先复用现有 Particle Test App / validation UI。
如果需要新增 STEP-093 面板,要求:
- 不影响生产 MapLibre API 语义;
- 不引入 NavSea 业务逻辑;
- 场景按钮命名明确;
- 能一键切换四种 topology;
- 能设置 single-layer range;
- 能触发 invalid range;
- 能隐藏/显示 endpoints;
- 能删除/恢复 endpoint;
- 能触发代表性的 reorder / cross-boundary move;
- 能显示当前 diagnostics;
- 能恢复默认测试场景。
测试 UI 本身不应成为复杂新框架。
---
# 19. Android 运行态验收
Android 作为本 STEP 首要完整运行态验收平台。
Codex 必须准备:
- 明确的 build/install/run 手工命令;
- STEP-093 测试入口位置;
- 每项场景的操作步骤;
- 需要采集的日志/diagnostics;
- PASS/FAIL 判定。
实际 Android Gradle / APK / ADB / 真机或模拟器操作由 RDC / 用户执行。
不得由 Codex 仅凭代码静态分析代替 Android 实机/模拟器结论。
---
# 20. iOS / Metal 运行态验收
Android 完整通过后,再进行 iOS / Metal 对等测试。
至少需要验证:四 topology、Single-layer Continuous、Top 覆盖 Particle、Stable cache reuse、Camera invalidation、endpoint missing/recovery、visibility、Fast Path fail-closed、Top alpha。
如果本 STEP 暂未执行 iOS runtime:
- 报告必须明确写 `WAITING_IOS_RUNTIME_VALIDATION`;
- Android PASS 不得自动推导为 iOS PASS;
- Metal native build PASS 也不能替代 Metal runtime rendering PASS。
---
# 21. 编译与测试原则
1. MapLibre Native 的 Android/iOS 开发与编译统一使用 Mac。
2. 不安排 Linux Native 编译。
3. noisy native build 遵循 Mac/Codex 全局构建输出节流规则。
4. 不加入 Jest。
5. 可运行已有 Native 精确测试;如代码变更影响 STEP-092 核心逻辑,应重跑相关现有 Native tests。
6. 不为了“让测试绿”删除或弱化 STEP-092 已有覆盖。
---
# 22. 发现问题时的处理规则
### 22.1 小型、事实明确、局部问题
例如测试 UI 接线错误、diagnostics 漏展示、明确计数器错误、局部 invalidation reason 错误、编译错误、测试工具小缺陷,允许直接修复,并重新验收受影响项目。
### 22.2 架构级问题
如果发现需要修改 Continuous Range 数学定义、Style 顺序唯一 Z-order、0/1/2 retained cache 模型、renderer 大范围遍历结构、Fast Path 核心模型、第二套 layer metadata、新特殊 layer type、新 RenderGraph 或大规模 GPU cache redesign,必须停止当前修复扩张,先生成明确的“STEP-093 架构阻塞报告”,说明实际问题、复现方式、为什么 STEP-092 架构无法满足、建议方案和影响范围。
不得在运行态测试任务中擅自重构架构。
---
# 23. 验收矩阵
最终报告必须至少给出以下矩阵:
| 类别 | 场景 | Android | iOS/Metal | 关键证据 | 结论 |
|---|---|---|---|---|---|
| Topology | Bottom+Continuous+Top | | | | |
| Topology | Bottom+Continuous | | | | |
| Topology | Continuous+Top | | | | |
| Topology | Continuous-only | | | | |
| Range | Single-layer Continuous | | | | |
| Z-order | Top covers Particle | | | | |
| Cache | Stable reuse | | | | |
| Invalidation | Pan/Zoom/Rotate/Pitch | | | | |
| Mutation | Continuous internal mutation | | | | |
| Mutation | Cross-range move | | | | |
| Endpoint | start missing/recovery | | | | |
| Endpoint | end missing/recovery | | | | |
| Visibility | hidden endpoint topology | | | | |
| Invalid | missing/reversed range | | | | |
| Fast Path | eligible | | | | |
| Fast Path | unknown layer fail-closed | | | | |
| Alpha | Top premultiplied composite | | | | |
| Stability | repeated operations / long run | | | | |
状态只允许使用清楚枚举,例如:PASS、FAIL、NOT RUN、BLOCKED、WAITING_ANDROID_RUNTIME_VALIDATION、WAITING_IOS_RUNTIME_VALIDATION。
---
# 24. STEP-093 完成条件
只有满足下面条件,才允许把 STEP-093 标记为 `CLOSED / PASS`:
1. Android 运行态核心矩阵实际执行并通过;
2. 四种 topology 实际验证;
3. Single-layer Continuous 实际验证;
4. Top 覆盖 Particle 实际验证;
5. 0/1/2 cache 与 diagnostics 一致;
6. Stable reuse 实际验证;
7. Camera invalidation → rebuild → reuse 实际验证;
8. endpoint missing/recovery 实际验证;
9. visibility 不改变 topology 实际验证;
10. invalid range fallback 实际验证;
11. Fast Path eligible + fail-closed 实际验证;
12. Top alpha 无明显错误;
13. 发现的阻塞性缺陷已解决并回归;
14. 文档与 Git 收尾完整;
15. `HEAD == origin/particle/main`;
16. 工作区 clean。
如果 Android 已全部通过但 iOS 尚未运行,可记录为:`ANDROID CLOSED / PASS + IOS WAITING_RUNTIME_VALIDATION`。不得把未运行的平台写成 PASS。
---
# 25. 必做输出
本任务所有需要 ChatGPT/RDC 后续审查的文件必须由 Codex 在任务中实际生成,不得在结束后临时补要。
## 25.1 STEP 记录
生成:
`docs/50_Step记录/STEP-093_ContinuousRange运行态综合验收_v1.md`
如果同名主题已有版本,递增为 v2/v3……,不得覆盖旧版。
内容至少包括:起始 Git 基线、调查到的实际测试入口、新增/修改内容、Android/iOS 实际执行状态、验收矩阵、问题与修复、未执行项、当前状态、下一步。
## 25.2 Codex 完成报告
生成:
`docs/报告/STEP-093_ContinuousRange运行态综合验收_Codex完成报告_v1.md`
同主题已有版本时递增版本号。
报告至少包含:任务目标、修改文件、测试入口、diagnostics、build/test 实际结果、Android runtime 实际结果、iOS runtime 实际结果、PASS/FAIL/WAITING 状态、Git commit、push、Final HEAD、origin/particle/main、git status、未执行事项、风险与已知限制。
## 25.3 开发记录
追加:
`粒子开发记录.md`
只追加,不覆盖历史内容。
---
# 26. Git 规则
严格遵守项目 Git 规则:
- 只提交本任务相关文件;
- 禁止 `git add -A`;
- 禁止 `git add .`;
- 禁止 `git reset --hard`;
- 禁止 `git clean`;
- 不触碰无关 dirty/untracked;
- 功能代码与文档可按合理 scoped commit 拆分;
- push 到 `origin/particle/main`;
- push 后确认 `HEAD == origin/particle/main`;
- 最终确认 `git status --short`;
- push 失败不得标记任务完成。
---
# 27. Codex 执行终态规则
Codex 本轮结束时必须给出真实状态,而不是默认写 PASS。
建议状态:
- `IMPLEMENTED / WAITING_RUNTIME_VALIDATION`
- `PARTIAL / WAITING_ANDROID_RUNTIME_VALIDATION`
- `ANDROID PASS / WAITING_IOS_RUNTIME_VALIDATION`
- `CLOSED / PASS`
- `BLOCKED`
- `FAILED`
如果只是完成测试工具与代码准备,而 RDC/用户尚未真正运行 Android,则只能标记等待运行态验收。
---
# 28. 明确不做
STEP-093 不做:
- 任意 N-range planner;
- 多 Continuous Range;
- frequency class 自动分类;
- 自动 Z-order;
- anchor/marker layer;
- 第二套 BOTTOM/CONTINUOUS/TOP metadata;
- NavSea AIS 专用优化;
- Alarm 专用优化;
- Weather provider;
- Route ETA;
- LandMask 业务系统;
- Wave/Tide 业务系统;
- PBF 通用重构;
- RenderGraph 大改;
- 新粒子算法;
- 粒子视觉美化;
- unrelated performance tuning;
- Jest。
---
# 29. 最终核心判定
STEP-093 最终不是检查“按钮能不能点”,而是验证 STEP-092 的核心架构在真实渲染中确实成立:
> 普通地图内容可以被合理拆成 Bottom / Top retained cache,Particle 所在 Continuous Range 保持高频实时绘制;Style 顺序仍然决定最终 Z-order;不存在的 retained range 不产生资源;缓存只在需要时失效,并在稳定状态持续复用。
只要这个结论还没有通过真实运行态证据建立,STEP-093 就不能写成最终 `CLOSED / PASS`。

View File

@@ -0,0 +1,599 @@
# STEP-094 通用 Render Segment 分段渲染与 Style 分类诊断 — Codex 任务 v1
## 0. 任务目标
从已冻结的 Particle V12 `Bottom Retained / Continuous / Top Retained` 单 Continuous Range 模型,演进到最终通用模型:
```text
Style Z-order
↓
连续 layerIndex
↓
若干连续 Render Segment
↓
每个 Segment 的执行策略仅为:
CONTINUOUS 或 RETAINED
```
Style 顺序继续是唯一 Z-order 事实来源。Renderer 不理解 LOW / MEDIUM / HIGH,也不理解 AIS、Route、地图、Weather 等业务语义。
本 STEP 同时必须建立新的 pull-based Segment Diagnostics,能够取回“当前 Style → Segment → Layer”的真实分类快照,供后续人工判断 Segment 划分是否合理。
## 1. 当前冻结基线
工作目录:`/Users/tei/dev/MapLibreParticleLab`
目标分支:`particle/main`
当前已知 V12 冻结基线:
- HEAD:`7eaff6e12360e26591dceb5e011129eeab7ba4c7`
- Commit:`release: finalize unified particle.12`
- Tag:`maplibre-particle/rn/13.4.1-particle.12`
- `HEAD == origin/particle/main`
- 工作区 clean
开始前必须重新执行 fetch/status/HEAD 检查,以实际仓库为准。禁止 reset/clean,禁止回退、移动、覆盖 V12 Tag。STEP-094 是 V12 之后的新开发,不修改 V12 已发布事实。
必须先读取:
- 根目录 `AGENTS.md`
- `ARCHITECTURE.md`
- STEP-092 / STEP-093 任务、Step 记录与完成报告
- V12 当前 Continuous Range、Retained Cache、Fast Path、Android/iOS/RN bridge 实现
- 当前 `RendererRuntime.ts`、`types.ts`、`NativeWeatherRuntimeBridge.ts`
## 2. 最终架构原则
1. Style Layer 顺序是唯一 Z-order 真相。
2. 整个 Style 必须按连续 layerIndex 被完整划分为 1..N 个 Render Segment。
3. 每个 Segment 只声明 `RETAINED` 或 `CONTINUOUS`。
4. 允许多个 RETAINED、多个 CONTINUOUS,且可按 Style 顺序任意交错。
5. 相邻同策略 Segment 不得自动合并。
6. 每个 RETAINED Segment 独立拥有 cache / revision / invalidation / rebuild / GPU resource。
7. CONTINUOUS Segment 不创建 retained texture/FBO/compositor。
8. Renderer 不自动分析 Layer 更新频率,不自动调整 Z-order。
9. App 通过 Style 顺序 + Segment boundary 表达分类。
10. 不建立 LOW/MEDIUM/HIGH 第二套业务分类。
11. 不硬编码 NavSea Layer ID。
12. 不增加空白 Anchor / Marker Layer。
V12 三明治只是本模型的一个特例:`RETAINED → CONTINUOUS → RETAINED`。
## 3. 命名约束
MapLibre 现有代码已经大量使用 shader/bucket 的 `Segment` / `SegmentVector`。本任务禁止新增裸名称 `Segment` 造成语义冲突。
新模型命名必须明确属于 Renderer execution topology,例如:
- `RenderSegmentStrategy`
- `RenderSegmentDefinition`
- `RenderSegmentPlan`
- `ResolvedRenderSegment`
- `RenderSegmentDiagnostics`
- `RetainedSegmentCache`
具体文件名可按当前代码结构调整,但必须避免与 geometry/shader Segment 混淆。
## 4. Segment Plan 公共语义
推荐公共模型:
```text
RenderSegmentDefinition
strategy: RETAINED | CONTINUOUS
startLayerId?: string
```
规则:
- definitions 至少 1 个。
- 第 0 个 Segment 从 Style index 0 开始,`startLayerId` 必须省略/为空。
- 第 1..N-1 个 Segment 必须提供非空 `startLayerId`。
- startLayerId 必须引用当前 Style 中已经存在的普通 Layer。
- Native 根据当前 Style 解析真实 layerIndex。
- App/RN 不传数字 index。
例如:
```text
[
{ strategy: RETAINED },
{ strategy: CONTINUOUS, startLayerId: "rn-weather-color" },
{ strategy: RETAINED, startLayerId: "land-area" },
{ strategy: RETAINED, startLayerId: "ais-layer" }
]
```
解析后:
- Segment 0 = `[0, index(rn-weather-color))`
- Segment 1 = `[index(rn-weather-color), index(land-area))`
- Segment 2 = `[index(land-area), index(ais-layer))`
- Segment 3 = `[index(ais-layer), layerCount)`
Segment 2/3 虽然都是 RETAINED,也必须保持两个独立 cache,不得合并。
这种定义保证整个 Style 完整覆盖;插入普通 Layer 后依据 Style 位置自然进入对应 Segment;最顶端新增 Layer自动属于最后一个 Segment。
## 5. Plan 合法性与 resolve
有效计划必须满足:
- Style ready;
- 所有 boundary Layer 存在;
- boundary index 严格递增;
- 无重复 boundary;
- 每个 resolved Segment 非空;
- 最终覆盖 `[0, layerCount)`;
- 不重叠、不留 gap。
hidden / visibility=none 的 boundary Layer 仍参与 topology。Visibility 变化不得改变 Segment membership。
以下情况 plan invalid:
- boundary missing;
- boundary 顺序反转或重复;
- Style 未 ready;
- 空 definitions;
- 非法 strategy;
- JSON/schema 非法。
invalid 时不得 crash、不得猜 boundary、不得复用 stale cache。必须释放/隔离失效 topology 的 retained resource,安全走 full renderer,并在 diagnostics 给出明确 reason。Style 后续恢复合法时允许自动重新 resolve。
必须有 monotonic `configRevision`。Style add/remove/reorder 后重新 resolve;实际 begin/end 变化时推进 `topologyRevision`。Visibility 变化不得推进 topologyRevision。
## 6. Renderer 执行模型
Resolved Segment 按 Style 顺序执行:
```text
for segment in resolvedSegments:
RETAINED -> build/reuse cache -> composite
CONTINUOUS -> direct render
```
逻辑上按 Segment 顺序组合,但实际实现必须继续满足 MapLibre 原有 RenderPass 语义。
必须保持:
- 全 Style 原始 global layerIndex;
- 原始 ordinal;
- `parameters.currentLayer`;
- `depthRangeSize`;
- Opaque reverse traversal;
- Translucent forward traversal。
禁止每个 Segment 从 0 重新编号,禁止为了 Segment 模型重新排序 Style Layer。
## 7. RETAINED Segment 独立 Cache
每个 RETAINED Segment 必须拥有独立:
- cache state;
- content revision;
- build/reuse/invalidation count;
- last invalidation reason;
- texture/compositor create count;
- resource release count。
GPU resource 必须 lazy 创建。CONTINUOUS Segment 绝不能因为占位而创建 retained texture。
Plan/topology 改变后,不能证明安全复用的旧 cache 必须释放并重建;stale segment cache 绝不能错误 composite。本 STEP 不要求复杂 cache migration,correctness-first。
## 8. Invalidation 粒度
目标是 Segment 级 invalidation。
能够明确定位影响 Segment 时,只 invalidate 受影响 RETAINED Segment;其他 RETAINED Segment 保持 valid/reuse。
LayerMutation 若明确属于某 Segment,应只使该 Segment dirty。Source/Tile mutation 若能从现有 RenderLayer/Source 关系安全确定影响的多个 Segment,应 invalidate 对应集合。
若无法可靠确定影响范围,允许保守 invalidate 全部 RETAINED Segment。本 STEP 不建立复杂通用 dependency graph。
camera/transform/viewport/resize/pixel ratio、style reload、image/sprite/glyph、placement/symbol fade/transition、context reset、debug options、unsupported 3D、resource/compositor failure 等无法证明局部安全时可 invalidate 全部 retained。
完成报告必须区分“已实现的 segment-local invalidation”和“仍采用全 retained invalidation 的原因”。
## 9. Cache ON/OFF
现有 Cache ON/OFF 总开关继续有效。
Cache OFF:
- Segment Plan 仍然 resolve;
- Diagnostics 仍能看到 Style/Segment/Layer 分类;
- RETAINED Segment 不错误复用 retained GPU cache;
- Renderer 安全走非 retained 路径。
Cache ON:
- 每个 RETAINED Segment 独立 build/reuse;
- CONTINUOUS Segment 直接绘制。
关闭所有 CONTINUOUS Layer 的 visibility 不得删除 Segment topology。
## 10. V12 兼容入口
不得破坏现有:
- `setRetainedUnderlayEnabled()`
- `setContinuousLayerRange(startLayerId,endLayerId)`
- `getRetainedUnderlayDiagnostics()`
新内部实现应尽量只有一套 Segment topology。
`setContinuousLayerRange(start,end)` 应作为 compatibility adapter,转换成等价 RenderSegmentPlan:
- start 前存在 → RETAINED
- start..end → CONTINUOUS
- end 后存在 → RETAINED
自然退化成 V12 原有 1/2/3 Range。
旧 API 与新 Segment Plan 不得同时形成两套 Renderer topology。最后一次显式 topology 配置必须只有一个 active plan,并从 diagnostics 可见 `configSource`。
旧 `getRetainedUnderlayDiagnostics()` 保留兼容摘要;新的多 Segment 事实以新 Diagnostics API 为准。
## 11. 新 RN / Native API
新增稳定公共语义:
```text
RendererRuntime.setRenderSegmentPlan(reactTag, plan)
RendererRuntime.getRenderSegmentDiagnostics(reactTag)
```
建议 TS:
```text
RenderSegmentStrategy = "RETAINED" | "CONTINUOUS"
RenderSegmentDefinition = {
strategy: RenderSegmentStrategy
startLayerId?: string
}
RenderSegmentPlan = {
schemaVersion: 1
segments: readonly RenderSegmentDefinition[]
}
```
RN public API 必须是 typed object。TurboModule ABI 可使用 JSON string 作为 plan/snapshot 内部传输,避免可扩展嵌套数组变成脆弱 parallel arrays。若采用 JSON,必须有 schemaVersion 与严格校验;public TS 不暴露 raw JSON。
必须沿当前真实链路扩展:
`RendererRuntime → NativeWeatherRuntimeBridge/TurboModule → Android/iOS package bridge → MapLibre platform API → Core Renderer state`。
不得新建第二套独立 RN bridge。
## 12. Segment Diagnostics — 强制要求
这是 STEP-094 的强制验收项,不是附加日志。
新增 pull-based `getRenderSegmentDiagnostics()`,必须能够取回当前真实 Style 的:
1. Style 全 Layer 顺序;
2. 当前 resolved Segment 顺序;
3. 每个 Segment 的 strategy;
4. 每个 Segment 的 begin/end;
5. 每个 Segment 内实际包含的 Layer;
6. 每个 RETAINED Segment 的 cache 状态与统计。
Diagnostics 只能按需读取。禁止 per-frame 打印完整 Style/Segment/Layer,禁止每帧构造大 JSON/大字符串。
## 13. Diagnostics Snapshot 最低字段
顶层至少:
```text
schemaVersion
enabled
configured
valid
configSource
configRevision
topologyRevision
styleLayerCount
segmentCount
retainedSegmentCount
continuousSegmentCount
coverageComplete
fallback
lastReason
fastPathEligible
fastPathActive
fastPathFallbackReason
fastPathHit
fastPathMiss
fullCpuFrame
```
`configSource` 至少区分:
- `explicit-render-segment-plan`
- `continuous-range-compat`
- `unconfigured/legacy`(若仍存在)
必须提供完整 ordered `styleLayers[]`,用于 plan invalid 时仍可人工分析 Style。
## 14. Style Layer Diagnostics
每个 `styleLayers[]` / Segment 内 `layers[]` 至少返回:
```text
layerIndex
layerId
layerType
visible
```
如当前 Style/Core 可低成本稳定取得,还应返回:
- `sourceId`
- `sourceLayer`
不适用时可为空/null,禁止伪造。Layer Diagnostics 只描述通用 Style 信息,禁止加入 AIS/Route/Wind 等业务判断。
## 15. 每个 Segment Diagnostics
每个 Segment 至少:
```text
segmentIndex
strategy
configuredStartLayerId
beginIndex
endExclusive
resolvedStartLayerId
resolvedEndLayerId
layerCount
layers[]
```
如果 `strategy=RETAINED`,还必须有:
```text
cacheState
contentRevision
build
reuse
invalidation
lastInvalidationReason
textureCreate
compositeCreate
resourceRelease
```
如果 `strategy=CONTINUOUS`,不得伪装成 disabled retained cache。retained-only 字段应为 null/not-applicable,或由 TS discriminated union 排除。
## 16. Diagnostics 一致性
当 plan valid:
- `styleLayers.length == styleLayerCount`
- Segment 按 segmentIndex/beginIndex 严格递增
- 所有 `segment.layers[]` 拼接后必须与 `styleLayers[]` 完全一致
- 每个 Style Layer 必须且只能属于一个 Segment
- `segment.layerCount == segment.layers.length`
- `coverageComplete=true`
当 plan invalid:
- 仍尽可能返回当前 ordered styleLayers
- segments 可为空或只返回已安全解析部分,但必须 `valid=false`
- 禁止把猜测结果标成 valid
## 17. DEBUG 使用目标
必须能取回类似:
```text
Segment 0 RETAINED [0..18)
background
water
coastline
Segment 1 CONTINUOUS [18..20)
rn-weather-color
rn-weather-particles
Segment 2 RETAINED [20..27)
land-area
navigation-marks
route
Segment 3 RETAINED [27..30)
ais
radar
alarm
```
此信息只用于人工分析 Style 分类是否合理。Renderer 不根据 Debug 结果自动重新分类。
设计文档必须给出至少一个完整 JSON/TS snapshot 示例。
## 18. Particle Fast Path
V12 已有 Particle Fast Path 不得无声退化。
至少保证:
- 对“恰好一个 CONTINUOUS Segment + 其余为 RETAINED”的 V12 等价拓扑,且 Continuous 内容满足现有支持条件时,现有 Fast Path 能继续工作。
- 不存在某个 retained cache 时不得成为 miss 原因。
对于多个 CONTINUOUS Segment,若当前 Fast Path 无法安全支持,允许 fail closed:
- `fastPathActive=false`
- 明确 fallback reason
- 正常 full CPU frame
- Segment topology / retained cache / Z-order 仍正确
禁止为了 STEP-094 一次性重写整个 RenderGraph 或实现所有 N-Continuous Fast Path 优化。
## 19. Style Mutation
必须验证:
- Segment 内新增/删除/reorder Layer
- boundary 前后插入 Layer
- boundary Layer hidden
- boundary Layer 删除/重新出现
- boundary reorder 导致 plan invalid
- plan 更新后恢复
普通 Layer 插入 Segment 内后应自然成为该 Segment 成员,不要求 App 给每个 Layer 单独注册频率 metadata。
相邻 RETAINED Segment 即使策略相同,也必须依靠显式 boundary 保持独立。
## 20. GPU 生命周期
实际 retained GPU resource 数量应与当前 resolved plan 中 RETAINED Segment 数量一致,允许 lazy 未创建状态,但不得为 CONTINUOUS Segment 分配 retained target。
Plan topology 变化后 stale resource 必须正确 release。稳定帧不得重复创建 retained texture/FBO/compositor。OpenGL 与 Metal 行为必须一致。
## 21. Native Directed Tests(不加入 Jest)
本任务明确不加入 Jest。
至少覆盖:
1. 单 RETAINED Segment 覆盖全 Style
2. 单 CONTINUOUS Segment 覆盖全 Style
3. V12 等价 R-C-R
4. C-R-R
5. R-R-C
6. R-C-R-C-R 多段交错
7. 相邻 R-R 不自动 merge
8. hidden boundary 不改变 topology
9. boundary missing / duplicate / 顺序非法
10. Style 内插入 Layer 后 membership 正确
11. boundary 删除后 fail safe、恢复后重新 resolve
12. retained resource 数量匹配 RETAINED Segment 数
13. CONTINUOUS 无 retained resource
14. Cache OFF 时 topology diagnostics 仍完整
15. V12 `setContinuousLayerRange` 兼容映射
16. global layerIndex / Opaque / Translucent 语义不变
17. 多 CONTINUOUS Fast Path 安全 fallback
Diagnostics tests 额外验证:
- styleLayers 顺序精确
- segments 顺序精确
- 每个 Segment 的 layers 成员精确
- 拼接 segment.layers == styleLayers
- layerIndex 不重编号
- layerId/layerType/visible 正确
- sourceId/sourceLayer(若实现)正确
- invalid plan 仍能取得 ordered styleLayers
- snapshot pull 不产生 per-frame 日志
## 22. 平台与 RN 验证
Core/C++:
- 执行规定的 Mac 原生精确编译与 Native tests
- OpenGL / Metal
- 编译输出遵循全局 build-output-throttle
Android:
- 完成源码、JNI/Kotlin/Java/RN bridge 修改
- Android Gradle 编译由用户手工执行,不由 Codex 运行
- 报告列明“未执行 Android 编译/实体机”直到用户验证
iOS:
- 只面向 iPhone/iPad arm64 真机架构
- 不要求 Simulator slice
- 可执行当前工程已有的 Mac/iOS arm64 精确编译验证
- 不伪造实体机运行 PASS
## 23. 最小 Debug/Validation 入口
在当前已有 Particle validation/Test Host 体系中补一个最小 pull 入口:
- Fetch Segment Diagnostics
- 展示或输出一次完整 snapshot
- 能看到 Segment 列表和每个 Segment 的 Layer 列表
不得增加持续轮询,不得把大 snapshot 每帧写日志。若现有 UI 已有 diagnostics panel,优先复用。
## 24. 性能要求
Segment topology/style diagnostic 数据只在 config/style topology 或相关 debug metadata 变化时更新,或在显式 diagnostics pull 时做必要轻量组装。
禁止因为 Particle 30/60fps:
- 每帧扫描/复制完整 Style Layer 字符串
- 每帧 JSON stringify
- 每帧创建 Segment diagnostics 大对象
- 每帧 Logger 输出 topology
高频 render path 只消费预解析的轻量 segment ranges/cache state。
## 25. 明确不做
本 STEP 不做:
- LOW/MEDIUM/HIGH 枚举或自动频率分类
- Renderer 识别 NavSea 业务 Layer
- 自动调整 Style Z-order
- Anchor/Marker Layer
- 复杂通用业务 dependency graph
- Weather provider / Route ETA / AIS 专项 renderer
- PBF 重生成
- 通用 RenderGraph 重写
- 自动合并同策略 Segment
- 多 Continuous 的高级 Fast Path 优化
- per-frame Segment debug log
- Jest
如发现必须突破这些边界才能保证 correctness,停止扩大范围并在报告明确 BLOCKED/PARTIAL。
## 26. 必做文档
新增:
- `docs/30_设计/STEP-094_通用RenderSegment模型与Style分类Diagnostics设计_v1.md`
- `docs/50_Step记录/STEP-094_通用RenderSegment分段渲染与Style分类诊断_v1.md`
- `docs/报告/STEP-094_通用RenderSegment分段渲染与Style分类诊断_Codex完成报告_v1.md`
追加:
- `粒子开发记录.md`
同主题已有文件则递增版本,不覆盖历史。所有文档中文。
设计文档必须包含最终 Segment 数据模型、config/resolve 生命周期、V12 compatibility mapping、cache ownership、invalidation、Fast Path 边界、Android/iOS/RN API、Diagnostics schema、完整 Style→Segment→Layer snapshot 示例。
## 27. 完成报告必须记录
- 起始 HEAD / 最终 HEAD
- 修改文件清单
- RenderSegment config API
- boundary Layer ID → layerIndex 解析
- N Segment 遍历
- retained cache ownership/resource lifecycle
- segment-local 与 global invalidation
- V12 compatibility / Fast Path
- Diagnostics schema
- styleLayers / segment.layers 验证
- RN/Android/iOS bridge
- OpenGL / Metal
- Native test 数量与结果
- 未执行 Android/实体机项目
- known limitations
- commit SHA/message
- push result / remote branch
- origin/particle/main Final HEAD
- 最终 `git status --short`
## 28. Git 规则
仅 scoped staging。禁止 `git add -A`、`git add .`、`git reset --hard`、`git clean`,不得删除/覆盖无关 dirty/untracked。
结束前必须:
1. `git diff --check`
2. 核对 diff
3. 只 stage 本任务文件
4. scoped commit
5. push `origin particle/main`
6. fetch/确认远端
最终要求:
```text
HEAD == origin/particle/main
git status --short 无本任务残留
```
push 失败不得声称完成。
## 29. PASS Gate
只有以下全部成立才可判 STEP-094 PASS:
- Style Z-order 仍唯一
- 1..N 连续 Segment 完整覆盖
- RETAINED/CONTINUOUS 可任意交错
- 相邻同策略 Segment 不自动合并
- 每个 RETAINED Segment 独立 cache/revision/invalidation/resource
- V12 Continuous Range 兼容
- V12 等价 Fast Path 不无声退化
- 新 Diagnostics 能真实返回 Style→Segment→Layer
- diagnostics pull-based、无 per-frame 大开销
- Android/iOS/RN API 完整
- Native directed tests 通过
- 规定编译验证完成
- 文档完成
- scoped commit/push 完成
- HEAD 与远端一致
关键项未完成必须明确 PARTIAL/BLOCKED,禁止伪造 PASS。

View File

@@ -0,0 +1,109 @@
# STEP-095 V13 独立严格代码与性能审查 — Codex 任务 v1
## 0. 工作性质与模型
- 本任务是独立、严格、全链路、以源码为最终证据的只读代码/性能调查,不是修复任务,也不是批准已有报告。
- 执行环境:Mac-mini-tei.lan,仓库 /Users/tei/dev/MapLibreParticleLab,分支 particle/main。
- 运行模型:gpt-6-astra,reasoning effort=high;由外部启动命令实际指定,报告记录实际模型。
- 最终交付物必须为中文,具体源码符号、文件路径、命令、证据可保留英文。
- 不以“编译通过”“定向单元测试通过”“已有 Codex 报告 PASS”推断 GPU 运行态、安全性、性能或跨平台一致性已经通过。
## 1. 源码冻结基线
- 被审查的 V13 代码提交:ee0b89e711bf01335b3aa71d36def785cd97fec3。
- V13 核心功能提交:016fd1fff6a33cf795c10d4f5c5413d308383677。
- V13 发布标识:RN 13.4.1-particle.13;iOS 6.28.0-particle.13;以实际 manifest/tag/API 核实,不假设已在 NavSea 接入。
- 前置独立审查归档提交:45c4dca84928a57a2cf0226737c34cabd7fbfbfb。
- 审查前读取 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 与其中标记的当前有效总则。
- 同步检查 git branch、HEAD、origin/particle/main、working tree、release manifest 和相关 tag;记录所有偏差。
- 本任务自身新增文档提交不改变上述被审查的源码 commit;核查源码时固定以该提交为事实基线。
## 2. 审查独立性:先源码,后已有报告
- 首先依据 Git V12→V13 改动、当前调用链及实际代码独立扫描,不从旧报告结论直接生成 findings。
- 然后阅读 docs/20_调查/Particle_V13_严格代码审查报告_v1.md,逐项复核 F01–F10:确认、修正、否定或标记需动态验证。
- 不能复制上一份报告来冒充独立 review。需要发现旧报告遗漏、误判、证据不足及其他模块的潜在风险。
- 交叉阅读 STEP-088~094 的任务、设计、Step 记录、Codex 报告及相关发布/验证事实。
- 当任务书与代码冲突,分别说明“设计意图”和“实际实现”;不能把历史数据冒充 V13 测量。
- 可以读取 Integration repo 的 contract/status/compatibility,但不得修改该仓库;发现跨平台契约问题只提升级建议。
## 3. 强制审查范围 A:Renderer 与 V13 Segment
- 通用 RenderSegment plan、JSON parser/resolver、配置切换、missing/reorder/hidden boundary、valid/invalid/recovery。
- Style 全局 Z-order、layerIndex、LayerGroup ordinal、opaque reverse、translucent forward、depth/stencil/blending、debug/legacy/3D 与 offscreen composite 的一致性。
- 任意 R/C 交错:全 R、全 C、R-C-R、C-R-R、R-R-C、R-C-R-C-R、相邻 R-R;缓存 OFF 和资源失败的 full fallback。
- Retained cache 独立生命周期、build/reuse/invalidate/release、topology generation、content fingerprint、diagnostics counters 的归属。
- V12 连续区间兼容映射、单 Continuous Fast Path、多个 Continuous 安全 fallback,注意 silent fallback 与正常渲染的真实成本。
- 特别核查 Segment pass 分割后的 alpha/depth/stencil 语义、source/layer mutation 与下一帧 stale GPU object 风险。
## 4. 强制审查范围 B:GPU、粒子和时间场
- OpenGL 与 Metal OffscreenTexture、Framebuffer、Texture、render pass、commit/present/wait、resource destroy/context loss;区分通用 Core 与平台实现。
- 是否存在 CPU/GPU 同步等待、重复 GPU allocation、每帧 upload/UBO/drawable 重建、纹理引用失效和资源峰值。
- Particle State A/B、Trail、Field T0/T1/MIX、spatial sampling、forecast/playback、zoom-out reseed、viewport bounds 和 resize/orientation。
- Wind/Wave/Current/Rain 等 canonical FieldProfile;V3/V4 encoded/decoded field、QBIN decoder、projection/materialization、binding/validity/missing data。
- 检查异常或损坏输入、极大网格/时间、浮点 NaN/Inf、整数溢出、文件/缓冲区生命周期、重复/乱序/过期 field、安全失败和内存占用。
- 需要从 Source→Runtime→RenderLayer→Renderer→GPU 按真实调用链追踪,不接受只搜索名称的浅层判断。
## 5. 强制审查范围 C:高频、线程和 Host API
- 每帧 CPU 分配/深复制/锁竞争/字符串或 JSON 构造、LayerGroup 遍历复杂度、静态 CPU Fast Path 命中与错过原因。
- style/source/tile/image/glyph/placement 失效范围是否正确,segment-local 与全局 revision 的边界是否一致。
- map-scoped lifetime、prepared RenderTree、Runtime/renderer/control state 的并发、线程归属、异步回调、销毁后引用和重复 attach/detach。
- Android Java/Kotlin/JNI 与 iOS ObjC++/Metal 的桥接接口、错误归一化、RN TurboModule typed API、ABI/codegen 与版本锁。
- Test Host 的依赖及 lockfile、构建结果的可复现性、release manifest/npm/AAR/XCFramework 的版本一致性。
- 不将业务 provider、HTTP、Route/ETA 带入 Shared Core;不建议建立新的隐藏私有桥接或第二套协议。
## 6. 强制审查问题清单
- 是否存在 V13 上线后能够导致画面层级错误、透明度/遮挡错误、丢帧、黑屏、闪烁、过期 cache composite、GPU/CPU 泄漏、崩溃或错误数据的实际源码路径?
- 非法配置是否真正 fail closed?同一 index 换不同 Segment 时旧 cache/diagnostics 归属是否错误?
- Retained contentDirty 是否被误当作 GPU allocation invalid?Metal 逐 cache wait 是否可证实?需要哪些 profiler 证据才能决定修改?
- 高频 Fast Path 是否复制 full diagnostics、扫描 Style 或反复线性搜索?有无更重的热点在既有报告中遗漏?
- 多段 retained 是否存在全 viewport depth/stencil 过度分配;是否有峰值预算、安全回退及 resource accounting?
- source/tile 局部更新是否引起不必要全 retained rebuild?若建议局部化,必须给出不破坏 correctness 的依赖证据。
- 单 Continuous 与多 Continuous 的 Frame 处理在相同真实 Style 下有什么可测的差异?不能外推 STEP-090 历史 A/B 数字。
- release、Test Host、RN/iOS/Android 版本与实际产物是否能被独立复现?尚未测试的必须标 NOT RUN。
## 7. 审查方法
1. 建立 V12→V13 变更与模块地图,阅读入口、类型定义、状态机、调用方和析构路径。
2. 针对每个怀疑的问题制作最短触发条件和源码调用链;尝试找反例,禁止只因为名称相似就报告漏洞。
3. 按已证实缺陷、源码确定的成本、真实运行影响待验证、显式设计限制、文档/环境偏差分类。
4. 为每条 finding 写严重度 P0/P1/P2/P3、置信度、源文件精确行范围、触发场景、影响、最小安全修复方向和验收方法。
5. 独立比较上一份 F01–F10,标为 CONFIRMED / QUALIFIED / DISPROVED / NEEDS_RUNTIME,说明依据和新增发现。
6. 对潜在收益只给复杂度/估算表达和建议基准测试,不提供无 profiler 支持的 FPS/CPU/GPU 节省百分比。
7. 所有未亲自执行的测试必须标 HISTORY / NOT RUN;已有报告的 PASS 只能列为历史证据,不替代当前独立执行。
## 8. 运行与修改边界
- 禁止修改任何生产源码、shader、公共 API、构建配置、package/lockfile、release manifest、tag、发布产物或 Integration 文件。
- 禁止运行耗时 CMake/Xcode/Gradle 编译、安装、设备操作和大型测试;由 RDC/用户另行验证。
- 允许只读 git show/log/diff、文件检索、源码静态分析和查看既有日志、测试源码、报告。
- 如判断必须借助 profiler、真机或编译才可定论,写精确验证方案而不是擅自实施。
- 仅允许新增本 STEP 中文调查/Step/完成报告,按规定追加根目录粒子开发记录;不要顺手修复发现的问题。
## 9. 完成报告格式(必须实际落盘)
- 执行摘要:哪些是确定缺陷、哪些是性能成本、哪些只能动态验证;不能只给结论口号。
- 基线与范围:审查 commit、分支、release manifest、审查的主要文件/代码路径、未覆盖范围。
- Finding 总表:编号、严重度、证据类别、问题、触发、影响、代码定位、修复方向、验证方法。
- 逐条完整分析:至少包含最短可复核调用链及确认或推翻依据;新问题与旧报告重复项区分。
- V13 与 V12 架构/性能成本比较:不确定的写待测;不要虚构性能数值。
- F01~F10 独立复核矩阵:原结论、当前意见、依据、是否需补充/修正。
- 缺失测试与运行态矩阵:GPU correctness、CPU/GPU frame time、allocation/peak memory、30/60 FPS、长时间稳定性、各平台状态。
- 优先级清单:分 correctness gate、无需设备的安全优化、GPU 资源/同步、较大架构改造。
- 具体建议的下一轮 Codex 修复批次(只建议,不创建修复任务,不改源码)。
- Git:起始/最终 HEAD、docs-only diff 清单、commit SHA/message、push 结果、remote HEAD、最终 status。
- 结论状态:调查 PASS/PARTIAL/BLOCKED 与 V13 产品运行态是否通过必须分开。
## 10. 必做输出和固定路径
- 调查主文档:docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md。
- 阶段记录:docs/50_Step记录/STEP-095_V13独立严格代码与性能审查_v1.md。
- Codex 完成报告:docs/报告/STEP-095_V13独立严格代码与性能审查_Codex完成报告_v1.md。
- 追加:根目录 粒子开发记录.md,短而具体地记录本次调查变化、现状及下一步,不覆盖历史。
- 若同主题相同文件已经存在,改用递增版本号并在报告写明,不覆盖旧报告。
- 以上文件全部中文;任何报告必须明确区分事实、推断和待验证。
## 11. Git 收尾
- 先确保用户现有 dirty/untracked 不被覆盖;只有本任务新增/追加文档允许 staged。
- 不执行 git reset --hard、git clean、force push、tag 移动或向 upstream push。
- 对所有新增文档进行路径、目录、内容、交叉引用检查;git diff --check。
- 仅 git add 上述具体文档和本次追加的开发记录;scoped commit 后 push origin particle/main。
- push 失败或 HEAD 与远端不一致时,不得写 PASS;明确失败与恢复建议。
- 记录源代码相对于原 V13 源码提交没有改变,禁止把 docs-only commit 误写成新 V13 软件发布。
- 最终由 RDC 独立检查报告证据、Git 与任务边界,Codex 不自行宣布正式产品验收。
## 12. 停止条件
- 无法确认当前 commit、源代码状态与所审查发布版本对应时,停止并报告 BLOCKED。
- 遇到跨项目协议变更/需要修改业务源码时,只形成接口阻塞或修复建议,不扩大任务。
- 证据不足时明确 NEEDS_RUNTIME,不应为了凑数量虚构问题。
- 重大 P0 或明确破坏性问题优先直接写进报告,但本任务仍只读,不自动修复。

View File

@@ -0,0 +1,59 @@
# STEP-096 V13 隔离盲审 — Codex 任务 v1
## 0. 工作性质与隔离目标
这是针对已冻结 V13 源码的全新、只读、源码优先的代码与性能审查。发现必须来自当前隔离目录内的源代码和规范,不允许从任何既有审查结论、聊天记录或外部仓库导入问题清单。不得为凑数量制造 finding。
- 执行模型:全新 Codex TUI 会话,`gpt-6-astra`,reasoning effort=`high`;RDC 负责实际启动与记录,Codex 报告仅记录能核实的启动事实。
- 唯一工作目录:`/Users/tei/dev/MapLibreParticleV13Blind096`。
- 目标源码版本:`ee0b89e711bf01335b3aa71d36def785cd97fec3`;由 RDC 从该 Git 对象导出**无 .git 元数据**的源码快照。当前隔离目录不是主仓库、不是 Git worktree;不得在此运行 git,也不得用其他路径反查仓库历史。
- 唯一任务说明:本文件。用户和外部 RDC 将在审查开始前检查隔离树不含已有调查、阶段记录、完成报告、任务历史或开发日志。
- 禁止访问隔离目录之外的所有源码、文档、Git metadata、会话日志、终端回放、网页、连接器、用户历史或其他 AI 输出;不要尝试恢复缺失文档。对任何未在快照内的证据写 NOT AVAILABLE。
- 不得读取、搜索或比较其他轮次的审查材料;**本任务只有盲审,不包含解除盲态后的交叉比较。** 完成后的文件由 RDC 先冻结散列,再独立移交正式仓库。
## 1. 只读入口与事实层级
先读取隔离目录的 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md`、其中列出的当前有效总则和本任务。隔离快照故意不提供历史 Step、调查/完成报告;读取要求在盲审范围内以当前有效规范和实际源码为准,不得向目录外补读。规范包含历史发布坐标时,以本快照的 release manifest、源码和目标 SHA 为实现事实,区别说明规范意图与实际代码。
允许读取:隔离树内的 C++/GL/Metal/Android/iOS/RN 代码、头文件、shader、构建配置、测试源码、release manifest,以及当前有效规范/架构设计。历史测试输出和运行日志不可用;阅读测试源码不等于执行测试。不得浏览 `docs/20_调查`、`docs/50_Step记录`、`docs/报告`、既有历史任务或开发日志(这些输入在隔离树内也应不存在)。不引用主仓库的当前 HEAD 作为 V13 源码 SHA。
## 2. 审查目标与探索方法
从目录和公开 API 识别实际架构,自行建立模块和调用链地图;不要按照预设缺陷名称凑检索结果。对每个候选问题至少追踪入口、状态所有权、调用方、正常路径、错误路径、销毁路径,并寻找反例。分别审查:
1. Style 顺序和分段解析/更新/绘制语义;任意连续渲染策略交错、透明/深度/裁剪及缓存一致性。
2. Renderer→gfx→OpenGL/Metal 的目标、命令、资源和生命周期;稳定/变化帧、失败恢复、尺寸变化及上下文切换。
3. GPU 粒子、颜色场、时空插值与场数据链;输入合法性、数值边界、时间状态和显示状态。
4. Source/Runtime/Field/QBIN、数据快照、线程与内存所有权;重复、乱序、异常或极大输入与 fail-closed。
5. Android/iOS/RN 的公开 API、错误传播、生命周期和版本/产物引用;区分源码可证事实与安装环境需要的验证。
6. 高频执行成本:CPU、锁、分配、遍历、复制、GPU 同步/资源、UI 线程以及资源上界。查找实际热点路径,但没有 profiler 不得断言运行时热点排序或节省百分比。
7. 兼容与工程边界:既有行为、上游薄接入、Style Z-order 唯一事实来源;业务下载/HTTP/provider 属于 Host,不进入 Native。
如同一问题已有安全 fallback、缓存稳定复用或数据校验,必须写出反例和限制,而不是仅报告表面上可疑的符号。源代码路径和行号必须在隔离快照内真实存在。
## 3. 每条 finding 的证据契约
- 按发现顺序自行编号 `B01`、`B02`……;不要采用其他报告的任何编号。
- 列:P0/P1/P2/P3、类型(确定缺陷/源码确定成本/设计限制/环境或文档偏差/需运行验证)、置信度、触发条件、最短源码调用链与精确路径/行号、反例、影响边界、安全修复方向、最小验证方案。
- 区分“代码中的风险路径已经证实”和“用户画面、GPU、CPU、OOM、崩溃、性能收益已经实测”;后者本轮一概不可假定。
- 如果推断尚缺关键调用链,降为 HYPOTHESIS/NEEDS_RUNTIME,不以 P0/P1 明确事故冒充。
- 不要求 finding 数量、也不要求与任何报告相同;实际不存在证据就明确无此类 finding。
- 不复制包含商业/个人机密的数据;路径与定位信息足够即可。
## 4. 验证边界与停止条件
本任务只允许静态读取、轻量文件搜索、源码/语法/引用自检。禁止生产代码、测试代码、shader、manifest、package/lock、工具链改动;禁止 CMake/Xcode/Gradle、Codegen、Jest、设备、安装、性能测试、发布、Git 写入。历史构建/测试结果没有暴露,不得编造。所有未运行门槛标 `NOT RUN`,运行效果标 `NEEDS_RUNTIME`。如必须依赖平台设备、profiler 或竞态注入,写可执行验证矩阵,停止在证据边界。
只能写以下三份中文文件,写完确认三份存在、正文和相对链接有效、所引用路径/行号有效、无错误引用和行尾空格后退出:
1. `docs/20_调查/STEP-096_V13隔离盲审调查_v1.md`:架构地图、独立 finding 总表与证据、反例、风险、性能模型、未覆盖范围、运行验证矩阵、分批建议。开头明确声明仅使用隔离输入。
2. `docs/50_Step记录/STEP-096_V13隔离盲审_v1.md`:状态、输入版本、检查范围、只读工作过程、结果、三份文件清单、未运行验证、阻塞与下一步。
3. `docs/报告/STEP-096_V13隔离盲审_Codex完成报告_v1.md`:执行结果 PASS/PARTIAL/BLOCKED(仅表示盲审文档交付)、finding 索引、证据质量、输入限制、构建测试 NOT RUN、源码未变、交付/移交方案。
本隔离树无 Git metadata,**不要在报告内伪造 Git commit/push/remote/clean 状态**。正式仓库移交、`粒子开发记录.md` 追加、四个文件的 scoped commit/push、最终 SHA 和远端核对均由 RDC 在盲审文件冻结之后执行。Codex 不得自行打开正式仓库进行移交,避免在写稿前接触比较材料。
## 5. 盲审冻结与移交验收(由 RDC 执行,不是 Codex 自行执行)
Codex 停止后,RDC 验证隔离树的文件清单,计算三份原始报告 SHA-256 并记录时间;冻结后不得由同一会话基于其他资料重写 finding。随后独立把三份报告完整复制到正式 `particle/main` 同路径,追加根目录 `粒子开发记录.md`,执行引用、边界、git diff --check、scoped commit、push `origin particle/main`、远端 HEAD/clean 核查。禁止修改已冻结正文,若必须校正移交元数据,应写入独立移交记录,不得改写发现表。
文档交付 PASS 不等于 V13 Android/iOS GPU 运行态或性能 PASS。若 Codex 无法访问输入、丢失上下文或额度阻断,停止、保留已成品并标 PARTIAL/BLOCKED;不得使用其他会话已有审查结果补齐。

View File

@@ -0,0 +1,153 @@
# STEP-097 V13 三轮审查问题合并修复 — Codex 任务 v1
状态:**计划 / 待执行**。本文件只定义后续代码修复任务;创建本文件不代表任何缺陷已经修复、编译通过或 V13 运行验收通过。
## 0. 目标、基线及本轮决策
目标:对 V13 三轮源码调查的问题去重、复核、按安全依赖顺序修改;保留全部原始编号和证据归属,不以“多份报告都说有问题”取代源码验证。先消除渲染与天气数据的正确性风险,再处理输入安全、Host 生命周期和可证明的热路径成本。保留通用连续 Render Segment、Style Z-order 唯一顺序、每个 RETAINED 独立 cache;不得退回旧 Bottom/Continuous/Top 固定三明治模型。
本次用户决定:**暂不执行 V13 正式运行态确认**。本任务结束的最高状态只能是 \`IMPLEMENTED / WAITING_RDC_BUILD_AND_RUNTIME_VALIDATION\`;不能写 Android/iOS GPU、FPS、长期稳定性、正式发布或 NavSea 接入 PASS。本任务不要求 Codex 编译、安装、操控设备或执行性能测试。运行态集中验证清单由 Codex准备,之后单独授权执行,避免小变动引发多次 Android 编译/安装。
实际源码基线以执行时 \`particle/main\`、\`origin/particle/main\` 和 clean worktree 为准;编写任务书时 HEAD 为 \`b5c1c4879a9d36608242e788afab5012bd9388f2\`,V13 软件冻结源码为 \`ee0b89e711bf01335b3aa71d36def785cd97fec3\`,功能提交 \`016fd1fff6a33cf795c10d4f5c5413d308383677\`。当前 \`.13\` 软件已发布,所有修复在后续开发提交中完成,**不得原地覆盖、移动或重新发布 \`.13\` 的 tag/manifest/产物**。若起始 HEAD 已前进,先核实源代码差异并报告,不用旧行号机械覆盖。
审查输入,必须逐份读取:
1. \`docs/20_调查/Particle_V13_严格代码审查报告_v1.md\`(F01–F10,含历史实测)。
2. \`docs/20_调查/STEP-095_V13独立严格代码与性能调查_v1.md\`(N01–N07、C01–C07、O01、E01/E02)。
3. \`docs/20_调查/STEP-096_V13隔离盲审调查_v1.md\`(B01–B13;**已冻结的独立盲审原文,不得因修复结果修改其 findings**)。
4. STEP-094、095、096 的任务/Step/完成报告与 release manifest,按真实性区分历史 PASS 与本轮 NOT RUN。
开始前读根 \`AGENTS.md\`、\`PARTICLE_BASELINE.md\`、\`docs/00_项目规范/\`、\`docs/00_项目总则/README.md\` 指定的四份有效总则。通过 RDC 确认 \`navsea-particle-integration\` 已同步到最新 \`main\`,并只读检查 \`README.md\`、\`WORKFLOW.md\`、\`coordination/TEAM_MODEL.md\`、\`contracts/\`、\`rfc/\`、\`adr/\`、\`status/\`、\`compatibility/\`。创建任务时 Integration main 为 \`39ad8415fbdb76c1822460bb09f3a3f35fe141e0\`,C-001 / RFC-001 为既有合同,不把其中历史 \`.5\` 误认为当前软件发布 \`.13\`。本任务**不修改 Integration、NavSea 仓库**,新增跨项目公共语义须先有协调/RFC/ADR/Contract 决定。
## 1. 去重修复总表与执行分类
状态枚举:\`CODE_FIX\`=本任务可实施;\`CONDITIONAL\`=只有满足设计/证据门槛才动;\`DESIGN_ONLY\`=不作为既有缺陷盲改;\`COORDINATION\`=需跨项目负责人协调。Codex 应将表中每项逐条写入实施矩阵,落实文件、测试入口、commit、实际状态;确认某项不成立也必须提供反证,不能直接删除。
| 合并问题 | 三轮原编号 | 初始分类 | 目标与安全边界 |
| --- | --- | --- | --- |
| 01 跨 render target 的 stencil CPU/GPU 状态失配 | N01 / B01 | CODE_FIX | target/pass 世代一致;首帧、R-R/R-C-R、cache ON/OFF |
| 02 Metal depth/stencil clear 与附件不完整 | N02 / B02 | CODE_FIX | 全部 RenderPass descriptor/combined attachment;不得只改颜色 |
| 03 LayerGroup render 异常后缓存错误标成功 | N03 / B12 | CODE_FIX | 失败不得 \`markBuildSucceeded\` 或 reuse;允许安全恢复 |
| 04 encoded PLAY 缺失唯一 production clock 驱动 | N04 / B06 | CODE_FIX | map/source owner 单一单调推进;无双重 tick |
| 05 GPU 粒子场身份未覆盖 V/validity | B13 | CODE_FIX | 两时间端点完整内容身份;正确复用和局部上传 |
| 06 8-bit 零向量偏差 | B07 | CONDITIONAL | 修复零表达和编码/解码一致性,不引入未验证的格式破坏 |
| 07 Mercator 东向位移缺纬度系数 | B08 | CODE_FIX | U/V 相同物理单位下正确度量,保留既定时间倍率 |
| 08 JSON 嵌入 NUL/重复键被接受 | N05 | CODE_FIX | 长度感知解析;非法计划清旧状态、fail-closed |
| 09 坏数值场先入库、物化失败替换好投影 | N06 | CODE_FIX | 事务式校验/发布;允许合法 no-data/validity=0 |
| 10 Android 重复 attach listener 积累 | N07 | CODE_FIX | 幂等 attach/detach、移除监听、对象释放 |
| 11 热路径完整 Diagnostics 深复制 | F01 / C01 | CODE_FIX | 内部轻快照与对外完整 pull 分离,线程一致性 |
| 12 Metal retained/普通 State/Trail 同步等待 | F02 / C02 / B03 | CONDITIONAL | 先记录同步点/依赖;不可直接删除 wait |
| 13 内容 dirty 导致相同尺寸 GPU allocation 重建 | F03 / C03 / B04 | CODE_FIX | contentDirty 与 resourceInvalid 分离;清屏正确性先通过 |
| 14 retained GPU 资源无总预算 | F04 / C04 | CONDITIONAL | 安全估算、分配失败完整 fallback;独立 R 不合并 |
| 15 encoded store 缺总 key/byte 预算 | C04 / B11 | CONDITIONAL | 保留绑定窗口和旧读者;若拒绝结果需新公共语义则停止 |
| 16 ordinal 前缀 O(S×G)/fast 扫全层 | F07 / C05 | CODE_FIX | topology generation 缓存索引,不能改变全局顺序 |
| 17 相同 time/mix-only 重物化和 UI 同步处理 | C06 / B05 / B10 | CODE_FIX(异步部分 CONDITIONAL) | 先幂等和不可变端点复用;owner-thread 安全发布 |
| 18 drawable/tweaker 重建与窗口端点复制 | C07 | CONDITIONAL | 只改可证明稳定的对象生命周期,避免新 stale 引用 |
| 19 旧 cache 的 release 记入新 topology | F09 / O01 | CODE_FIX | identity/generation 归属、历史/当前计数分离 |
| 20 Test Host RN \`.10\` / native \`.13\` 错配 | F05 / E01 | CODE_FIX | 默认 package/lock 与插件精确锁定;不把旧 TS 报错误归因 |
| 21 多 Continuous 退出 CPU Fast Path | F06 | DESIGN_ONLY | 保留安全 full CPU fallback,后续真实 A/B 再立项 multi-C |
| 22 source/tile 等保守全局失效 | F08 | DESIGN_ONLY | 没有完整依赖证明不得缩窄,后续 profiling/依赖设计 |
| 23 Integration compatibility/status 历史口径 | F10 / E02 | COORDINATION | 向粒子负责人提交接入证据请求,不擅改或声称 NavSea 支持 |
| 24 正式共享地理 Ocean Mask 尚未形成 | STEP-095 §7 / B09 | COORDINATION | 跨 Host/Shared Core/双平台显示契约需单独 RFC/ADR,不用合成 fixture 冒充正式实现 |
注意:B13 与 N06 **不是同一缺陷**;GPU 内容身份遗漏与 encoded ingress 事务发布必须分别修复和验证。B07/B08 是粒子数值正确性,不等于 V13 分段回归。所有 P1/P2 是调查建议优先级,不是运行事故证明。
## 2. 固定顺序与不可跨越的检查点
严格按 \`基线复核 → A正确性 → B输入/生命周期 → C安全性能 → D交付\` 串行进行。前一阶段没有明确代码完成和静态检查结果,禁止推进下一阶段;失败、进程超时、状态未知不能当作成功。任一阶段发现需要更改冻结公共 API、QBIN/FieldProfile 语义或破坏跨平台 ABI,**停在该问题**,记录接口阻塞和建议,不自行加入 Adapter/Normalizer、隐藏通道或不兼容 fallback。不得为了凑齐总表自动进行大架构重构。
### 阶段 0:基线与定点反证
1. 确认 branch/HEAD/origin、dirty/untracked、submodule、manifest、V13 tag;对照三轮行号回读实际源码,建立 \`问题→当前源码→最短复现→反例→修复文件→目标回归\` 表。
2. 确认 GL/Metal/RN/Test Host 的实际入口和平台条件,核对是否已有后续修改解决某项;有明确反证者记 \`NOT_REPRODUCIBLE_ON_CURRENT_HEAD\` 并附定位,不重复修复。
3. 固定公开语义:\`fieldId/generation/validTime\`、U 东向/V 北向、T0/T1/MIX 与 OLD_T1→NEW_T0 reuse、Style Z-order、每个 R 独立 cache、颜色与粒子分离、天气获取由 Host 所有。必要的内部对象身份可以新增,禁止创造第二套公共场协议。
4. 在 \`docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md\` 先写入逐项状态和安全不变量;后续随源码实现持续更新。这不是重新撰写第四轮审查。
### 阶段 A1:Renderer 正确性(01/02/03/19)
- **01 stencil**:明确记录属于当前 render target/pass,切换附件时重置/分代 CPU mask/ID 记录;GPU clear 与 CPU 映射同世代。保留同一 target 中安全的 tile mask 复用,不能中途全局清掉仍需使用的 stencil。针对相同 tiles 的 R-R、R-C-R、C-R-R、首次 build/rebuild/reuse/cache OFF 加最小可执行回归入口。
- **02 Metal**:从 gfx RenderPass descriptor 到 MTL descriptor 完整传递 color/depth/stencil 的 load/clear/store;正确处理 combined depth-stencil 和 device/simulator 附件存在性,创建失败显式不发布可用缓存。不能只依赖默认 loadAction 或 color isRenderable。补 descriptor/附件状态与 fallback 可观察测试入口;不宣称 iOS runtime PASS。
- **03 错误缓存**:使 layer-group 绘制失败结果贯穿 opaque/translucent、observer、finishBuild;部分绘制不得发布为 valid,不得在下一帧 reuse。正常 fallback/retry 能恢复;保留已有资源准备失败回退,不吞异常或改成进程崩溃。
- **19 计数归属**:cache 创建/析构使用稳定 identity + topology generation,旧资源 release 不记入同 index 的新段;聚合统计与当前 topology 统计分离。连续模式切换、R→C、相同 index 换边界均有验证入口。诊断错误不夸大为纹理本体泄漏。
- 本阶段有任何 stencil/Metal/异常正确性未闭合时,**不得开始资源复用/异步 GPU 性能改造**。
### 阶段 A2:Field/粒子正确性(04/05/06/07)
- **04 PLAY**:只允许一个 map/source owner 推进 forecast clock,基于单调时间处理 PLAY/PAUSE、速度、恢复前后台、clear/destroy;平台 timer 仅调度/唤醒,不允许 WeatherColor 与 Particle 各推进一次。只 setTime 一次后 PLAY 自动移动 time/mix;保持 Host 显式 setTime 路径语义。
- **05 B13**:在两个端点的 GPU 更新门槛覆盖 U、V、validity、grid、encoding/解释语义及来源版本;generation、fieldID、dataVersion 不能代替完整内容身份。只变 mix 不应上传场,真正跨窗时旧 T1→新 T0 安全复用,只上传需要的新端点,不能每 tick 重建;无效/旧 revision 不可污染有效窗口。
- **06 B07**:先确认当前 GL/Metal 纹理格式、GPU 精度与 CPU/shader 编解码约束;以零、微风、正负抵消及 NaN/validity fixture 定量约束误差。选择可准确表达零的**内部**表示或明确等效方案,GL/Metal 统一;如果必须改变公共 encoding、需要不支持的图形格式或改变已发布输入 ABI,则不强行实施,记录 \`BLOCKED_FORMAT_DECISION\`。
- **07 B08**:按 eastward U / northward V m/s 推导 Web Mercator 度量,对 U/V 一致应用纬度修正、符号、极区夹紧和既有 visual time scale。建立 0°/45°/60°、东/西/南/北及极区有限值的解析对照;不得更改 Host 输入向量单位。
### 阶段 B:输入与平台生命周期(08/09/10/20)
- **08 N05**:JSON strategy/startLayerId 必须使用长度感知字符串;拒绝内嵌 NUL、重复键、未知 schema/strategy、缺失/逆序边界。无效配置 fail-closed、旧计划清除与恢复语义保持,Android/iOS/RN 错误归一化不分叉。
- **09 N06**:在已解码 frame 真正替换 Store 前,按既有 profile/validity/no-data 语义验证有效 cell 的有限 U/V 和 scale/offset 运算不溢出;事务式提交,失败返回明确错误并保留好旧帧、投影及 GPU 显示。不把合法 validity=0 或规范允许的 noData 当成错误;不同时间/高 revision 的合法行为不改变。
- **10 N07**:同 View 重复 attach/detach 幂等;map listener 有明确 remove/dispose owner 和 map/destroy 世代。测试入口覆盖 100 次复用、切 view、销毁中的异步回调,不产生悬挂访问或多重 dispose。
- **20 E01**:核实真实发布坐标、Host \`package.json\` 和 lockfile、plugin 注入的 Native 版本;将**默认** Test Host 消费组合对齐经核对的 \`.13\` 精确配对,不将临时 node_modules/本地软链当作成功。若需要 install/codegen/Gradle,则准备 RDC/用户命令并标 \`NOT RUN\`;不发布、不覆盖现有 \`.13\` 产物、不改 NavSea。
### 阶段 C:有边界的性能与资源(11–18)
严格先做只读热点/资源依赖记录,再实施不会改变正确性和公共契约的优化,优先把同一阶段兼容变更集中,避免每个小点都要求一次 Android APK。
1. **11 轻量 Diagnostics**:内部 fast/render 热路径只取同一 generation 的轻量一致状态;完整 Style/Segments 深复制仅在外部 pull 时构造。避免用若干无同步原子值组成不一致快照;低频 pull schema 兼容。
2. **16 ordinal/扫描**:预计算 global ordinal 和粒子/待更新 group 列表,topology/style reorder 后失效重建;保持 opaque reverse、translucent forward、隐藏层及全局 Z-order,不能用局部 ordinal 替代全局编号。
3. **17 物化与 UI**:相同 requestedTime 和相同不可变端点早退;mix-only 仅更新 mix,复用完整内容 identity(U/V/validity/grid/encoding),与 B13 修复共用身份。双 binding 共享安全的不可变端点,不能跨 generation 复用旧数据。Android/iOS 解码和 CRC 若转 worker,必须保存 map/source 世代、owner-thread 事务发布、有限队列、取消和 destroy race;若涉及跨平台公共异步返回语义或不能证明生命周期,先完成同步路径早退,异步部分标 \`DEFERRED_DESIGN\`。
4. **13 retained allocation**:仅在 A1 的 target clear/错误语义已经正确后,分离内容 dirty 与资源 key invalid;同尺寸、同 gfx context/format 可清除重绘;resize/context loss/关闭明确释放。相邻 R-R 仍是独立 cache。
5. **14/15 预算**:显式估算 retained color+depth/stencil+pending 与 fieldStore key/byte 上界,处理整数溢出和在途旧快照。无法分配必须走原设计的安全 fallback 或拒绝;不得静默丢弃仍绑定窗口。若新增拒绝状态无法用现有 public error contract 表达,保留安全上界/诊断并提交接口阻塞,禁止私改 RN/Native 双方错误语义。
6. **18 Drawable**:仅在能证明版本/target/UBO 生命周期正确时保留可复用 drawable/tweaker、减少 field endpoint 深复制;区分 tick、reseed、resize、context loss 与窗口轮换。未能证明不产生 stale resources 时不做大重构。
7. **12 Metal wait**:仅盘点 retained build、State/Trail 等实际同步点、命令和资源依赖,准备 Metal capture/性能基准。未经设备/renderer 验证**不得直接删 \`waitUntilCompleted\`、改成无法证明安全的异步 present**。可在现有顺序中去除明确重复且不改变依赖的等待,但须提供静态资源所有权证据;否则标 \`DEFERRED_PROFILE_AND_RUNTIME\`。
## 3. 本任务明确不进行的架构/协调事项
- **F06 multi-C**:保留既有安全 full CPU fallback 和 GPU retained reuse,不为达到指标改变 Style 顺序或暗中合并 C/R;新的可组合 multi-C fast path 单独设计、后续 A/B 验证。
- **F08 精细失效**:保留 source/tile/glyph/image/placement 的保守全局 invalidation,直到存在完整依赖与变更序列回归;不因性能冲动将其裁窄。
- **B09 Ocean Mask**:现有 synthetic fixture 不是正式共享地理 Mask。本任务只形成 Host→Shared Core→两层消费及跨平台一致性需求、最小数据边界和阻塞报告,交粒子负责人按 Integration C-001/RFC/ADR 决定;**不借任务顺便建立新的公开 mask API、导入 provider 或让 MapLibre 理解 NavSea 业务**。
- **F10/E02**:只输出“需 Integration 更新 Compatibility/Status”的事实与建议,并提供当前软件版本与真正验证状态;不修改 Integration 文档或宣称 NavSea 已支持。
- 不扩大为 renderer 全架构重写、QBIN 格式版本升级、RN 第二套桥、全量新主题、NavSea业务变动、新软件版本发布或复原旧三明治设计。
## 4. 验证准备、执行责任与验收矩阵
Codex 负责按实际代码添加**现有框架下必要的定向 C++/平台测试源码或最小 fixture**、静态核对、命令和期望结果;仅有调用说明但没有针对关键回归的测试入口,不能把相应问题标代码完成。可按现有项目测试结构写回归,但本任务**不包含 Jest,也不运行 Jest**。不修改旧断言来掩盖真实失败,不删除测试、不降低测试门槛。
| 验证集合 | 必备情形/最低证据 |
| --- | --- |
| Renderer A1 | 同 source/same tiles R-R/R-C-R、主/离屏 stencil;Metal clear/load/store、combined attachment;异常后禁止 reuse;R→C/拓扑更换计数 |
| Field A2 | 一次 setTime 后 PLAY/PAUSE/rate;T0/T1 boundary reuse;同 identity 仅 V 或 validity 变化;零/微速量化、0°/45°/60° Mercator |
| 输入 B | NUL/重复键/invalid→valid;好旧场→坏高 revision 且旧场保留;重复 attach/销毁;Host 默认版本与 lockfile |
| CPU C | pull-only diagnostics、reorder/ordinal、相同时间/仅 mix、窗口换端点、UI owner 世代安全 |
| 资源 C | mutation vs resize vs context loss、allocation fail、R×viewport×fieldKey 阶梯、清理恢复、GL/Metal resource ownership |
| 集中运行态(后续) | Android OpenGL + iOS Metal 各自像素/数值/时间/资源复现;30/60 FPS、p50/p95/p99、峰值/长稳、方向/缩放、状态恢复;按平台独立记录 |
**现在不执行** CMake、Xcode、Gradle、Codegen、Test Host install、APK/AAR、ADB、安装、真实设备、GPU capture、profiler、NavSea 集成或正式 V13 运行确认。Codex 结束时给出可复制的 RDC 验证命令(以本仓现有 runner/build 入口为准);Android Win11 手工 Gradle/ADB 和 iOS 设备验证依后续授权,不在本任务自动触发。不得把 STEP-094 历史 43/43、旧报告 69/69 或任何已发布 manifest PASS 写成本任务结果。
RDC 在每个代码批次之后独立审查文件/静态 diff;未来获授权再集中编译/运行,而不是在本轮分阶段频繁请求重装 APK。未来完整 Android 编译应遵守 \`tools/particle_android_validation/build_validation_apk.sh\` 当前权威流水线与用户手工运行安排。Metal 修改必须留给 Mac 同 commit 验证,Android 结果不得替 iOS PASS。
## 5. 允许修改与明确禁止
允许(仅与已复核问题直接相关,实际存在的路径以源码核对为准):
- \`src/mbgl/renderer/\`、\`src/mbgl/gl/\`、\`src/mbgl/mtl/\`、\`include/mbgl/\`、所需 gfx backend;
- \`src/mbgl/particle/\`、\`src/mbgl/style/sources/raster_array_source.cpp\`、\`src/mbgl/renderer/layers/\`、对应 GL/Metal shader及其标准生成源;
- Android Java/Kotlin/JNI、iOS ObjC++、RN 公共实现及 Test Host 本任务限域依赖文件;
- 对应定向测试源码/fixture、下述交付文档和开发记录。
涉及 shader generator 必须改其权威源而非只手改自动生成产物,并附原生成/校验命令由 RDC 后续执行。
禁止:修改 Integration、NavSea、项目总则、旧三份调查/任务/报告、已发布 manifest/tag/tgz/AAR/XCFramework、无关 package/lock、官方上游基线;禁止 \`git reset --hard\`、\`git clean\`、force push、push upstream;禁止把公开 API/FieldProfile/QBIN breaking 变更悄悄塞进修复。不能自行把 V13 版本重命名为下一发布版,下一版本号属于独立发布任务。
## 6. 必做输出与报告路径
执行时必须建立、更新且全部中文:
1. \`docs/30_设计/STEP-097_V13三轮问题去重与修复矩阵_v1.md\`:保留表中全部 24 行、旧编号、实际源码定位、修复/不修理由、源码状态、测试入口、commit、平台影响、运行待测项。分阶段附可量化的变更前/后操作次数模型,不虚构 FPS/内存收益。
2. \`docs/50_Step记录/STEP-097_V13三轮审查问题合并修复_v1.md\`:阶段 0/A1/A2/B/C/D 检查点、状态、实际改动、阻塞、下一步。每一阶段结束后追加短而具体的事实,未完成不得勾选。
3. \`docs/60_测试/STEP-097_V13修复后集中验证清单_v1.md\`:按 Android GL、iOS Metal、通用 Core/测试入口、后续 GPU 性能分层记录**尚未执行**的精确命令、fixtures、预期输出和复核责任。
4. \`docs/报告/STEP-097_V13三轮审查问题合并修复_Codex完成报告_v1.md\`:按规定详列开始/结束 HEAD、每批提交、文件清单、各问题状态、跨项目阻塞、静态检查实际命令和结果、所有构建/运行 \`NOT RUN\`、软件发布 \`NOT RUN\`、下一步集中 RDC 验证。
5. 在根目录 \`粒子开发记录.md\` **仅追加**实际进展;不得覆盖历史。
6. 若实际触发跨 Contract 阻塞,额外写 \`docs/报告/STEP-097_公共接口阻塞报告_v1.md\`,明确现有 C-001/RFC-001、拟议语义、为什么无法安全局部修、Android/iOS/NavSea 各自责任;本地只记录并提升级建议,不假装协调决定已被接受。
完成状态只能细分为 \`CODE_IMPLEMENTED_STATIC_CHECKED\`、\`DEFERRED_DESIGN\`、\`BLOCKED_CONTRACT\`、\`NOT_REPRODUCIBLE_WITH_EVIDENCE\`、\`WAITING_RDC_BUILD_AND_RUNTIME\`;没有执行的验证永远写 \`NOT RUN\`,产品级 \`PASS\` 禁用。
## 7. Git 收尾与停止条件
在当前 \`particle/main\` 上执行,先保护所有已有 dirty/untracked;只 stage 本任务变更。按 A1、A2、B、C 各自边界形成 scoped commit,文档随阶段更新;阶段失败不能越过到下一阶段,不可混入不相关工作。每个成功阶段允许 push \`origin particle/main\` 供 RDC 直接核对,失败必须确认进程/状态并修复,不能以 timeout 当作已失败后盲目重复 push。最终 \`git diff --check\`、提交内容、远端 HEAD、clean status 明确记录。push 失败不得宣称交付完成;提交并不代表运行验收。
触发以下任一条件则停止相应实现并生成阻塞事实:需要新增/改变公共契约而无 Integration 决定;必须升级工具链/MapLibre;不能证明跨 GL/Metal 正确或资源所有权;修复需要删除已有功能/测试或强制绕过安全 fallback;当前 HEAD 与所需源码不符且不能安全合并;既有用户改动会被覆盖;任一关键问题没有明确退出条件。必要时整任务停在已完成的 scoped checkpoint,报告 PARTIAL,不靠继续扩大代码量冒充完成。
交付后由 RDC 独立审查并按后续授权集中执行构建、回归和设备验收。**本任务不发布下一版软件,不提前关闭 V13 GPU 运行态。**

View File

@@ -0,0 +1,141 @@
# STEP-098:QBIN V4 单帧首显与按 fieldKey 独立的八帧滚动缓存 — Codex 任务 v1
状态:**任务书 / 待契约闸门通过后执行**。本任务书不代表代码已经修改、编译或双平台运行验收通过。
## 0. 目标与执行环境
本任务落实已讨论的目标模型:**单帧可独立显示;第二帧到达后支持正常时间插值;后续时间片在 Native 内部进入每个 fieldKey 独立的、按 validTime 升序排序的固定 8 帧滚动缓存。** 满 8 帧提交新的合法时间片时应自动淘汰,不应因正常满额返回 `REJECTED_CAPACITY`。
当前用户维护和修改的源码以 **Mac** 为准:`/Users/tei/dev/MapLibreParticleLab`,分支 `particle/main`。编写本任务书时 Mac HEAD / origin HEAD 均为 `0d13552bed84f427dd43cae9856391bd26622179`。执行前重新查询 Git 事实,不以该 SHA 或本文行号覆盖后续提交;不要拿 Linux 旧工作树代替 Mac 代码。任务 MD 的目标归档位置为 `docs/40_任务/STEP-098_QBIN_V4单帧首显与八帧滚动缓存_Codex任务_v1.md`;不要创建 `docs/任务/`。
本阶段以 **Shared Core 公共语义和现有 RN V4 公共接口**为实施目标,不在 NavSea/Weather Server 中实施下载策略;Android 与 iOS 使用同一共享语义,平台专项编译/实体机验收分别另行完成。不得因 Mac 为编辑机而建立 iOS 专属 Core 行为。
### 固定边界
- RN:负责获取预报目录、QBIN 文件、本地下载缓存、用户选择的预报时间、异步命令 latest-wins 和 UI。RN 已提供 `FieldRuntime.submitEncodedField(...)`、`FieldRuntime.setEncodedForecastTime(...)`,**先 await 前者成功,再 await 后者**;无需新增组合提交 API。
- Shared Core/Native:负责 QBIN 校验、存储、每 fieldKey 内排序/淘汰/复用、单帧/双帧解析、颜色与粒子投影、GPU 资源正确性。
- 不增加 Native→RN 索取数据回调;Native 不接入 provider、HTTP、manifest、下载、缓存文件管理或 NavSea 业务语义。
- 不修改 QBIN 文件格式、Rice 解码语义、现有公开方法签名、Style Z-order 或 V3 路径,不在 JS 伪造第二帧,也不在 Host 手动清空 8 帧来实现正常拖动。
## 1. 首要闸门:Integration 公共契约
1. 先经 RDC 检查并同步 `/Users/tei/dev/navsea-particle-integration` 的最新 `origin/main`(`git fetch origin main` + 确认可 fast-forward;尊重现有本地改动)。读取该仓库 `README.md`、`WORKFLOW.md`、`coordination/TEAM_MODEL.md`、`contracts/`、`rfc/`、`adr/`、`status/`、`compatibility/`、`coordination/active/COORD-004/` 和 `instructions/PARTICLE_PROJECT_INSTRUCTIONS.md`。
2. 编写本任务书时 Integration HEAD 为 `318f8ccf911ac3136c3086a1d14e61f83e2d4bce`;`RFC-003_编码场两帧首显与增量补帧语义.md` 仍处于 DRAFT,其第一条要求至少两帧、满额返回容量拒绝,与本任务新目标**明确冲突**。本任务书不是已经接受的跨平台 RFC。
3. **开始改公共源码前,必须由粒子负责人/Integration 按既定流程正式更新 RFC-003、COORD/Contract 或 ADR,接受下述单帧和滚动淘汰语义,明确 Android/iOS 共用。** Codex 不得自行把 DRAFT 当 ACCEPTED,也不得静默更改 Integration 文档替代负责人决定。
4. 如果执行时仍没有已接受且不冲突的正式依据:先在 `docs/报告/STEP-098_公共契约阻塞报告_v1.md` 写出当前 SHA、冲突条目、拟实施语义与所需协调决策,**停止功能修改及 push**,状态记为 `BLOCKED_CONTRACT`,交回粒子负责人。不得绕过闸门做 Android/iOS 两套实现。
5. 若 Integration 已更新且批准,记录精确文档路径、版本与 commit SHA,后续实现以正式版本为准;如正式结论与本文不同,应停止并回报,不自行择一。
## 2. 开始前必须核对的 Mac 实际基线
先读根项目规范、`PARTICLE_BASELINE.md`(如存在)、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 指定总则、与 STEP-097 的正在实施范围。读取并建立文件/函数对照表:
- `include/mbgl/particle/field/field_runtime.hpp`、`src/mbgl/particle/field/field_runtime.cpp`:`FieldSeries`、`EncodedFieldStore`、`resolve`、容量及 revision/identity。
- `src/mbgl/particle/field/field_binding.cpp`、`src/mbgl/particle/field/field_projection_materialization.cpp`:单/双帧投影、独立绑定和物化。
- `src/mbgl/style/sources/raster_array_source.cpp` 及相关头文件:`submitEncodedField`、`setRequestedForecastTime`、`refreshV4RendererState`、现有 projection equality/notification。
- `src/mbgl/renderer/layers/render_weather_color_layer.cpp`、`render_particle_validation_layer.cpp` 及 shader/texture 资源:单帧/双帧下颜色、粒子、轨迹和 GPU 复用。
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Android Kotlin bridge/JNI、iOS `WeatherRuntimeBridge.mm`/`MLNParticleFieldRuntime.mm`:确认两个现有公开调用完整传递结果。
- `test/particle/field_runtime.test.cpp`、`test/particle/field_binding.test.cpp`、`test/source/raster_array_v4_host_api.test.cpp` 及现有 Renderer 定向验证入口。
编写任务书时源码事实:`FieldSeries::submit` 对第 9 个不同时间直接 `RejectedCapacity`;`resolve` 对 `frames.size()<2` 返回空;`submitEncodedField` 已会刷新投影但不修改 forecast time。以上仅供定位,若新 HEAD 有变化应以实际代码为准。若 STEP-097 仍在改相同的 runtime/projection/Renderer 文件,先确认依赖顺序,不得并行覆盖或回滚已完成的修复;尤其保留 STEP-097 的输入校验、forecast clock、GPU 身份、资源生命周期等新修正。
## 3. 正式目标语义
### 3.1 缓存所有权、容量与排序
- `EncodedFieldStore` 按 `fieldKey` 分别持有 `FieldSeries`。`wind`、`wave`、`rain`、`tide` 使用不同 key 时分别最多 8 帧;同 key 的多个 Layer/Binding 共享该序列。不能把四类天气合为“全局 8 帧”。
- 每帧按 `frameContentIdentity.validTimeMs` **升序**保存,提交顺序不影响排序;不得假设固定一小时/三小时间隔。
- 已有相同 `validTime`:先走现有 Duplicate、identity conflict、stale、合法 revision 替换语义;不额外占一个槽,不因为满额淘汰别的帧。
- 不同时间且不足 8 帧:先完整校验,按时间有序插入。
- 不同时间且已满 8 帧:在成功通过解码、profile、grid/plane、身份和相邻兼容性校验后,选择淘汰一帧并原子提交新有序集合。正常满额替换成功返回现有 `ACCEPTED_INSERTED`(或正式合同要求的**既有**成功语义),不返回 `REJECTED_CAPACITY`。为了 TypeScript/ABI 向后兼容可以保留旧结果枚举,但正常容量=8的有效插入路径不再产生它;不可偷偷更名公共结果。
- 淘汰只作用于正在提交的 `fieldKey`,不影响其他天气种类、Bindings、requestedForecastTime、V3 数据或 Style。
### 3.2 双向淘汰,按新提交帧时间取中心
由于既定调用顺序是先 `submitEncodedField()`、后 `setEncodedForecastTime()`,**提交阶段不能依赖尚未下发的新目标时间**。本版使用“新提交帧 validTime 为中心,淘汰时间距离最远的端点”的确定性规则;新帧本身必须留下。对于已升序的满额序列,只在最早端或最晚端候选之间选择;若距离相同,按正式合同规定稳定决胜,允许优先避免淘汰当前显示端点,但不可牺牲新提交帧。不得永远只删除最旧帧。
必须覆盖:
- 原有示意 `13:00 ... 21:00` 共 8 帧;提交 `22:00` → 删除最早端 `13:00`,插入 `22:00`,最终仍 8 帧。
- 已向后滚动后提交 `12:00` → 删除最远的最晚端,重新保留 `12:00`;向过去拖动同样成立。
- 已满时提交位于内部的新时刻 → 仍保持有序、8 帧和确定的端点淘汰。
- 今天跳到后天某一时刻 → 接收该时刻 QBIN,而不是要求 RN 先清空旧窗口或填齐 8 帧。之后逐帧提交,Native 自行滚动。
- 乱序提交、Duplicate、同时间新 revision 不应造成意外淘汰。
- 插入不兼容数据或解码失败时,**零淘汰、零 Store/Projection/Clock 修改**;不得先删除旧帧再验证。
### 3.3 单帧首显与双帧升级
- 当请求时间精确对应某个已验证 `validTime`,即使整个 Store 只有一帧,或它与其余 7 帧相隔很远无法形成合理双帧窗口,也应能够显示该帧:颜色为这一帧的值;有有效 U/V(或规范允许的方向向量)时粒子可持续运动,**天气时间不变不等于粒子停止**;仅标量帧不能伪装成可平流向量。
- 单帧在运行时具有真实单端点语义。可在内部使用同一 Snapshot 作为 T0/T1、`mix=0` 来复用现有双纹理 shader,但不得向 RN 要求伪造第二个 QBIN 或捏造第二个不同 validTime;优先共享解码结果、物化 Snapshot 和 GPU 纹理,避免同一单帧无意义地双份常驻。
- 第二帧补入后,如果目标时间被合法的相邻端点覆盖,则自动升级为双帧插值,保持既有 `T0/T1/MIX`,端点、mix 与实际预报时间对应;如果并未覆盖,保持合法单帧或 fail-closed,不能强行把不相关的旧帧插值进去。
- 目标时间落在两帧之间且有合法覆盖时执行正常时间插值;目标时间既不精确匹配可用单帧、也没有合法双帧覆盖时返回明确 unavailable,不使用“最近旧帧”冒充该时刻。RN 若选择最近预报帧首显,必须以该帧实际 `validTime` 作为已显示时刻;用户日历选中时间与实际显示时间不可混称。例如用户初始选择 13:30,只有最近的 12:00 QBIN 时,RN 可先提交该帧并调用现有 `setEncodedForecastTime(12:00)` 显示 12:00 静态预报,仍将 13:30 保留为业务层待完成目标;等 15:00 帧到达后再设置 `setEncodedForecastTime(13:30)` 进入插值。不得在 Native 悄悄修改 `requestedForecastTime`,也不得将临时 12:00 画面标成 13:30 的插值天气。
- 不能把跨 dataset/reference/Profile 的帧混合成一个时间序列;沿用已有权威身份契约。不凭固定时间间隔猜测有效帧,也不能用任意远离的旧时间片推断新的天气。若当前元数据不足以明确区分“同一批次的合法双端点”与“跨批次伪兼容”,作为契约阻塞项报告,不能自行新造隐含协议。
- 明确 `fieldKey` 之间可有不同 validTime 覆盖。颜色与粒子在同一全局请求时刻分别 resolve,各自可处于单帧、双帧或 unavailable;不得因一个 fieldKey 无数据清空其他 key 的合法投影。
### 3.4 两个现有 RN API 的行为不变
正常时间轴操作为:
1. RN 从本地缓存或服务器取得对应 QBIN,先 `await FieldRuntime.submitEncodedField(...)`。
2. 仅在 `ACCEPTED_INSERTED`、`ACCEPTED_REPLACED_REVISION` 或可确认的 `DUPLICATE` 等成功结果后,`await FieldRuntime.setEncodedForecastTime(reactTag, targetTimeMs)`。
3. Native 的 submit **不擅自修改目标时间**,setTime 不触发网络下载。已缓存时间直接 setTime 即可;无需每次重新 submit。
4. 两调用非原子,可能在两次调用之间短暂显示旧时间、单帧或暂时不可用,**不可对外保证无闪烁**。不得把旧时刻天气伪标为新目标。RN 侧 latest-wins、目标版本和展示状态属于后续 Host 接入范围;本任务不私改 NavSea。
5. 不新增 `submitAndSetTime`、Native→RN 数据请求事件、逐帧删除 RN API 或新的 provider 参数;保留现有 `clearEncodedField()` / `clearAllEncodedFields()` 作为显式重置能力,而不是正常滚动手段。
### 3.5 Renderer 与资源安全
- 正常向前/向后补帧且当前已选 pair 和 mix 未变化时,避免无条件 rebuild、重复 GPU 上传、清空 trail 或发布无意义 Source revision;复用 existing `sameProjection`/GPU content identity。
- 单帧→双帧、双帧窗口变化和 revision 更新必须正确刷新,shader 无非法 T1/null;若相同 Snapshot 对应 T0/T1,避免重复上传同一内容。支持 GL/Metal 共享语义,不能只修 Android Renderer。
- 淘汰旧帧不能销毁仍被当前 projection/GPU 更新流程持有的不可变 Snapshot;通过正常引用计数和资源所有权安全释放。切换不同窗口时遵守现有 trail/particle reset 规则,不错误承诺跨任意远跳跃保留轨迹。
- 坏帧不允许覆盖已验证的好投影;生命周期关闭、clear、style reload、map 销毁与异步提交有明确所有权,不使旧请求重新点亮已 OFF 的画面。
### 3.6 内存边界
- 8 帧是**每 fieldKey 的数量上限**,不是无限制内存保证。每个 decoded frame 由原始 Plane 字节、validity bitset 和元数据构成;参与显示的端点另有 Float32 物化数组、图像、纹理和粒子/trail 资源。不得错误地用压缩 QBIN 大小表示运行内存。
- 不在本任务未经批准添加任意固定全局 MiB 上限或第二套容量公开错误码。为风/浪/雨/潮各 8 帧、单端点/双端点和跳跃替换提供 CPU/GPU 常驻及峰值测量入口或现有计数;记录可计算的 decoded byte 占用、资源复用/释放及最坏边界风险。
- 评估单帧 T0=T1 情况下重复物化/重复上传;保证淘汰与资源 release 后无持续增长。若测量显示 32 帧和显示资源会触及移动端内存风险,报告下一步“全局字节预算/多 fieldKey 优先级”设计建议,**不可把内存申请失败伪装成正常 REJECTED_CAPACITY**。
## 4. 实施顺序与测试矩阵
阶段 A:只读复核与契约闸门 → 阶段 B:Core Store 滚动淘汰及单帧 resolver → 阶段 C:Binding、物化、Renderer 单/双帧和资源复用 → 阶段 D:已有 RN Bridge 端到端语义检查 → 阶段 E:文件、报告、Git 收尾。前一阶段未明确通过不进入后一阶段;命令 timeout/进程未知不能算通过。发生公共 ABI、QBIN、profile、dataset 身份或无法证实跨平台一致性的阻塞时停止扩大范围。
至少具备以下定向验证;优先扩展既有 C++ 源级测试和可复用 Test Host 场景,不新建无关测试框架:
| 编号 | 场景 | 必须得到的结果 |
|---|---|---|
| T01 | 只有一帧,target=validTime | 投影合法;色场可显示;有 U/V 时粒子可动;实际时刻正确 |
| T02 | 首帧后补入第二帧,target 在两者中间 | 单帧→双帧,MIX 正确;不用伪造第二帧 |
| T03 | 8 帧未满乱序提交 | 按 validTime 升序;各 key 相互隔离 |
| T04 | 满 8 帧,向未来提交新帧 | 淘汰最早端、保留新帧,计数仍 8,无正常容量拒绝 |
| T05 | 满 8 帧,向过去提交新帧 | 淘汰最晚端、保留新帧,计数仍 8 |
| T06 | 满 8 帧,提交中间时刻 | 确定性端点淘汰;排序/唯一性不变 |
| T07 | 今天→后天单帧→后续补帧→返回今天 | 新时刻单帧可用,后续升级双帧;旧数据需 RN 重交时可正常滚动,不越界、不混批 |
| T08 | Duplicate、同时间合法新 revision、stale、identity conflict | 保留现有判定;重复/替换不淘汰其他时刻 |
| T09 | 错 QBIN、Profile/Grid/Plane 不兼容、无效向量 | 拒绝且 Store、forecast clock、原投影不被破坏 |
| T10 | 目标时间位于已缓存相邻两帧之间 | 正常插值,mix 首/中/末精确 |
| T11 | 目标不被合法帧覆盖也不精确匹配 | unavailable,不把最近帧冒充目标时间 |
| T12 | 新帧在当前 pair 外围、当前投影不变 | requestedTime、当前 pair/mix 保持;避免无意义资源重建 |
| T13 | 新帧改变当前 bracket,或当前端点被淘汰 | 正确更新;不沿用失效旧 GPU 数据、错误轨迹 |
| T14 | `wind` 满额后提交 `wave`;同 key Color+Particle | 独立 8 帧;其他 key 不受影响;同 key 不复制 Store |
| T15 | 单/双端点 Materialization/GL/Metal 资源 | 单帧无需两份独立 QBIN/CPU/GPU 内容;复用、release 与上下文丢失恢复正确 |
| T16 | 连续 100+ 次双向滚动、反复 clear、切 Runtime | 帧上限不破、无持续内存增长和悬挂引用 |
| T17 | RN 两步调用成功与失败分支 | 既有 API/TypeScript/Android/iOS bridge 类型兼容,失败不继续 setTime;不新增公开 API |
注意:Native 静态/定向测试只能证明对应源码路径,不等于手机 GPU 首帧或 NavSea UI 通过。对尚未执行的 Android/iOS 真机、滚动拖动、跨天回退、内存峰值必须标 `NOT RUN / WAITING_USER_VALIDATION`。
## 5. 修改范围与明确禁止项
允许根据当前真实源码在上述 Shared Core/Source/Renderer 及对应现有测试文件做必要的最小修改;仅在证明是既有 API 传递障碍时修改 Android/iOS bridge,保持方法签名和结果语义。可在 `docs/30_设计/` 记录淘汰不变量与单帧/双帧选择表,在 `docs/50_Step记录/` 记录实施进度。
禁止:修改 NavSea、Server、天气 provider、下载缓存、Manifest、日历 UI;重新生成 PBF、修改 Style Z-order、强行更改 QBIN/QBV3 格式;新建第二套 FieldStore/Timeline、额外 JS Normalizer/Wrapper、私有 Native 通道、自动请求 RN 数据、提交单帧伪造第二份不同时间、给四类天气合用 8 帧、让正常满额仍返回容量拒绝。不允许顺带完成 STEP-097 的未授权项,也不允许覆盖现有发布 tag/manifest 或把源码通过称为正式发布通过。
## 6. 构建、交付与 Git
- 本次编译、Gradle/Pod/Xcode 构建、APK/IPA 安装、ADB/设备操作及实际 GPU 验收由用户人工执行。Codex 可做源码、`git diff --check`、格式/静态检查及环境许可的低成本 C++ 定向测试;**不得擅自启动完整 Android/iOS 编译或设备操作**,也不得把未运行项目写成 PASS。
- 准备一份 Windows 11 可直接执行的 Android 手动验证清单,ADB 命令如需涉及当前虚拟机必须显式 `adb -s 192.168.200.139 ...`;iOS 只列 Mac 用户手工构建/设备验收条件。合并一轮验证 T01–T17,不为单个按钮或日志要求反复重装。
- 必做输出:`docs/30_设计/STEP-098_QBIN_V4单帧与八帧滚动缓存设计_v1.md`、`docs/50_Step记录/STEP-098_QBIN_V4单帧与八帧滚动缓存_v1.md`、`docs/报告/STEP-098_QBIN_V4单帧与八帧滚动缓存_Codex完成报告_v1.md`。目录不存在先创建;同主题报告已存在则递增版本,不覆盖旧报告。全部中文。
- 报告明确记录:当前 HEAD、获接受的 Integration 协议版本/commit、真实改动列表、API/ABI 是否变化、每项 T01–T17 执行状态、`REJECTED_CAPACITY` 新语义、单帧显示实际范围、CPU/GPU 内存测量与未验证项、下一步 Android/iOS/Host 责任。最多只能报告 `IMPLEMENTED / WAITING_PLATFORM_RUNTIME_VALIDATION`;契约闸门未过则 `BLOCKED_CONTRACT`。
- 完成可实施阶段且检查通过后,只提交本次任务相关变更到 **Mac 当前项目 `particle/main`**,并 push 到公开 `origin/particle/main`。提交前检查未跟踪/用户既有文件,不得 `git add -A` 混入无关变更;先核验 `origin` 是否前进,冲突时停下处理而非强推。完成报告必须写最终 commit hash、commit message、push 结果和远端 branch/HEAD;push 失败不能声称完成。若被契约闸门阻塞,不提交或 push 功能代码。
## 7. 最终验收口径
用户能在 RN 维持两个现有 API 和顺序:第一帧即显示其真实有效时间;第二帧具备合法覆盖时升级插值;后续任意顺序提交,Native 每 key 自动保持最多 8 帧。向未来提交淘汰最早,向过去提交淘汰最晚;正常已满不会 `REJECTED_CAPACITY`;四类天气互不挤占。无效帧零副作用、时间语义不造假、Core 不掌握下载/日历。Renderer 不因正常补帧无谓清屏或持续泄漏。Android 和 iOS 分别通过用户实际设备验证后,才能由 Integration 更新兼容性及正式状态。

View File

@@ -0,0 +1,690 @@
# STEP-100 Source → RenderSegment 局部 Revision / Invalidation / Readiness 与独立 Fallback — Codex 任务 v2
## 0. 任务性质
这是 STEP-099 之后的正式修复任务。
STEP-099 已经完成纯诊断并 CLOSED / PASS,已确认 V17 的核心问题:
1. 动态 GeoJSON Source/Tile mutation 会推进 global retained content revision;
2. 即使 R0 不依赖该 GeoJSON,R0 retained cache 仍跟着 invalid/build;
3. GeoJSON 更新期间约 200 ms 的 transient `isLoaded=false` 会通过 global `RenderOrchestrator::isLoaded()` 把整个 retained path 拉成 `style-not-loaded`;
4. 当前 renderer 还存在 `useRetainedRanges` all-or-none gate:一个 RETAINED Segment fallback 时,会使其他本来可安全 reuse 的 RETAINED Segment 一起退出 retained path;
5. STEP-099 diagnostics 已能正确给 source mutation 计算 `affectedSegmentIndices`,但该依赖关系仍只是诊断,不参与正式 invalidation。
本任务目标是把 **Source/Tile 变化对 RETAINED Segment 的影响正式局部化**。
本任务不是 V18 发布任务。Codex 只负责实现与源码级说明;Android/iOS 构建、APK、ADB、设备测试、最终 Git push、V18 release 由 RDC/ChatGPT 在 Codex 完成后独立执行。
未经用户明确要求,本任务 **不加入、不运行 Jest**。
---
## 1. 冻结基线
工作目录:
`/Users/tei/dev/MapLibreParticleLab-step100`
分支:
`step100-segment-local-source-invalidation`
开始基线必须重新核对,当前预期:
- `origin/particle/main`
- HEAD:`c4b481fd2c5b5da20d31f6ae54011f0552d7dbd0`
- STEP-099 implementation:`8a5214d92dd2f8899db76d2194da42999fa0c87d`
- V17 frozen tag:`maplibre-particle/rn/13.4.1-particle.17`
- V17 frozen commit:`e8b451fe70054107773e82484c80e90df3889147`
禁止修改、移动、覆盖 V17 tag / release worktree。
STEP-100 是 V17 之后的新开发,最终只有 RDC 验收通过后才允许进入 V18 发布。
开始前必须读取:
- 当前仓库实际 HEAD / status / diff;
- 全局 Codex AGENTS;
- `ARCHITECTURE.md`(若存在);
- STEP-092 / 094 / 099 的任务、Step 记录、完成报告;
- 当前:
- `renderer_impl.cpp`
- `render_orchestrator.*`
- `retained_underlay_cache.*`
- `retained_underlay_runtime_state.hpp`
- `render_segment.*`
- STEP-099 diagnostics 与 Android fixture;
- 当前 RenderSegment / retained cache / particle fast path tests。
不得凭历史印象修改。
---
## 2. 最终架构原则
长期 RenderSegment 模型继续保持通用模型:
```text
Style Z-order
↓
R / C / R / C / R / ...
```
其中:
- R = RETAINED
- C = CONTINUOUS
本任务只用 **R-C-R** 做最小验证,不把 R-C-R 固化成最终架构。
必须保持:
1. Style layer 顺序是唯一 Z-order 真相;
2. Renderer 不理解 AIS / Route / Weather / GeoJSON 等业务名;
3. Source → Segment 依赖由当前实际 Style / RenderLayer / Source 关系推导;
4. 一个 Source 可依赖多个 Segment;
5. source-less layer 不制造假 dependency;
6. unknown/global dependency 必须 fail closed;
7. 相邻 Segment、多个 C、多个 R 的通用模型不能被破坏;
8. 本任务不实现 multi-continuous Fast Path。
---
## 3. 本任务必须实现的核心目标
### 3.1 Source/Tile mutation 局部化
假设:
```text
R0 depends [staticSource]
C depends [particleField]
R1 depends [dynamicGeoJson]
```
当:
```text
dynamicGeoJson mutation
```
正式 renderer 必须达到:
```text
R0:
content/revision 不因该 source mutation 改变
cache 不 invalid
cache 不 rebuild
持续 reuse
R1:
revision/invalidation 正常变化
按需要 build/reuse/fallback
C:
正常 continuous render
```
禁止继续出现:
```text
dynamicGeoJson mutation
↓
global revision 变化
↓
R0 + R1 一起 rebuild
```
### 3.2 保留 global revision 兼容信号
STEP-100 不要求删除现有 global `retainedUnderlayContentRevision`。
为了保持:
- diagnostics;
- 现有 fast-path correctness;
- 旧 API / compatibility;
- conservative fallback;
允许并推荐继续维护 global revision。
但 **RETAINED Segment cache 的正式内容 revision / invalidation 不能再只依赖这个 global source/tile revision**。
当前 `retainedUnderlayRevisionTracker` 同时消费 global content revision 与 `renderTreeParameters.loaded`。STEP-100 必须明确拆成两种用途:
```text
global retained revision / tracker
-> compatibility / Fast Path / diagnostics
segment-local retained content state
-> full CPU renderer 的每个 RETAINED Segment Build / Reuse / Fallback
```
**Segment-local cache content state 禁止继续直接消费:**
- global source/tile content revision;
- global `renderTreeParameters.loaded`。
否则即使 source-scoped revision 已实现,R0 仍会被无关 GeoJSON transient unloaded 或 global revision 连带失效。
允许 global revision / global tracker继续按原逻辑变化;禁止为了让测试 PASS 而停止 global revision 增长或放宽 Fast Path。
### 3.3 per-RETAINED-Segment readiness
不得修改任何 Source `isLoaded()` 定义。
不得修改:
```cpp
renderTreeParameters->loaded = updateParameters->styleLoaded && isLoaded();
```
这个 global loaded 仍作为 MapLibre 现有全局兼容事实。
但 retained segment 的 cache Build/Reuse/Fallback 必须有自己的安全 readiness 判断。
最低语义:
- Style 本身未 ready:fail closed;
- image manager / sprite / glyph 等无法安全定位到单 Segment 的全局前置条件未 ready:fail closed;
- Segment 已知依赖的 source 中任一缺失或 `isLoaded()==false`:仅该 Segment fail closed;
- Segment 已知依赖的 source `hasFadingTiles()==true`:仅该 Segment视为 unstable;
- **不被该 Segment 引用的 source unloaded 或 fading,不得让该 Segment cache fallback/rebuild**;
- `retainedRangeIsStable(begin,end)` 的 source fading 检查必须按该 range 的真实 source dependency 缩小,不能继续遍历全部 `renderSources` 作为 full-CPU retained gate;
- Fast Path 原有 global fading gate 保持不变;
- source ID 无法解析、dependency 缺失或真正无法证明安全时 fail closed。
必须区分:
1. **已知 Source dependency**;
2. **symbol/global prerequisite**(glyph/image/placement 等);
3. **真正 unknown dependency**。
STEP-099 的 `unknownDependencies` 目前会把 symbol 相关条件标为 unknown。STEP-100 不得机械地把“存在 symbol”解释成永久 fallback;symbol 所需 glyph/image/placement 应作为明确的 global prerequisite/fail-closed 条件处理。
因此 T3 中一个:
```text
no layer
enabled=false
isLoaded=false
```
的 RasterArray source 可以继续让 global `RenderOrchestrator::isLoaded()==false`,但不得因此让与它无依赖关系的 R0/R1 cache 全部 `style-not-loaded`。
本 Step 的运行态硬验收范围是 **MapMode::Continuous**(粒子正式实时渲染模式)。当前非 Continuous / still 路径如果在 global unloaded 时提前返回,本 Step 不强行改变其兼容语义;完成报告必须明确该适用范围。
### 3.4 RETAINED Segment 独立决策
当前 V17 路径存在类似:
```text
useRetainedRanges = all retained segments supported
```
的 all-or-none 语义。
STEP-100 必须改为每个 RETAINED Segment 独立拥有:
- readiness/support decision;
- Build / Reuse / Fallback;
- content/source revision;
- invalidation reason。
如果:
```text
R0 = Reuse
R1 = Fallback
```
则本帧必须允许:
```text
R0 composite cached texture
C live render
R1 live render fallback
```
而不是退化为“整个 Style 全部 live render”。
如果某 retained cache 自身 build 失败,应优先只 fallback 该 Segment;只有无法证明局部安全的 context/global resource failure 才允许全 retained fail closed。
---
## 4. Source-scoped revision 设计要求
Codex 必须基于当前代码选择最小、稳定设计,并在完成报告说明实际方案。
推荐方向但不强制具体类名:
- 为 Source/Tile mutation 维护 source-scoped monotonic revision;
- keyed by stable sourceId,但必须处理 source 删除后同 ID recreate,不能让新 source 错误继承可复用的旧 cache identity;可用 generation/epoch 或保留单调 tombstone 语义实现;
- source diff、feature-state mutation、tile changed 等所有当前会进入 Source/Tile retained content invalidation 的正式路径都必须覆盖;
- 能定位 sourceId 的 mutation 只推进对应 source revision;无法定位 sourceId 的 mutation继续 global fail closed;
- Segment content revision 由:
- 该 Segment 的 dependent source revisions;
- 当前 Segment layer fingerprint / layer mutation;
- 必须全局影响 retained 的 global revision 类别;
- topology / camera / viewport 等既有安全条件
共同决定。
Source/Tile mutation 不应推进“所有 Segment 都消费的 retained cache revision”。
必须避免:
- 用屏幕位置猜 dependency;
- 在 renderer 中硬编码 source/layer ID;
- 从 STEP-099 diagnostics JSON 反向驱动正式 renderer;
- 建立 diagnostics 与 production 两套互相漂移的 dependency 真相。
如果需要提取 production-grade dependency helper,应让 diagnostics 和正式逻辑共享同一只读推导源。正式 renderer 不得从 diagnostics JSON 或 `RetainedUnderlayRuntimeState` 反向取 dependency。
Source-scoped revision 属正式 correctness state,不得沿用 STEP-099 diagnostics 的“best-effort 记录失败可忽略”语义。正式 revision/dependency state 若无法可靠更新,必须 conservative global invalidation/fail closed。
---
## 5. Global mutation 仍可保守处理
本任务只要求 **Source/Tile mutation** 与明确 Source readiness 局部化。
以下无法安全局部化的事件,本轮允许继续 invalidate/fail closed 全 retained:
- context reset;
- style reload / topology change;
- image mutation;
- glyph mutation;
- sprite/image manager 全局 readiness;
- placement / symbol fade / glyph/image/sprite 全局状态;
- camera / transform / viewport / resize / pixel ratio;
- debug mode;
- 3D / unsupported / legacy ordering;
- 无法定位 dependency 的 mutation;
- 真正 unknown/global dependency。
注意:**Source 自身的 loaded / fading 不属于这里的 global fail-closed 项**;若 source dependency 可定位,必须按 Segment 局部判断。Fast Path 的 global source/fading gate除外,本轮保持原状。
完成报告必须列出:
- 已局部化的 mutation 类型;
- 仍为 global 的 mutation 类型;
- 为什么保守处理。
---
## 6. Fast Path 本轮严格不改
STEP-099 已确认 Fast Path 仍有 global gates:
- global update signature(sources/layers/images 等);
- global content revision;
- global fade;
- pendingChanges;
- placement/light/non-particle transition/repaint;
- annotation mutation;
- topology/boundary/revision/cache validity;
- `singleContinuousSegment`。
STEP-100 **禁止修改这些 gate 的语义**。
允许:
- Source mutation 后 Fast Path miss;
- transient source readiness 时 Fast Path fail closed;
- full CPU renderer 被调用。
但进入 full CPU renderer 后,必须能做到:
```text
R0 retained cache reuse
R1 rebuild/fallback
```
这正是本任务验收目标。
Multi-continuous Fast Path 明确不在本任务。
---
## 7. Render 顺序与混合 Build/Reuse/Fallback
实现 per-segment mixed decisions 时必须保持原 MapLibre 绘制语义:
- Style Z-order;
- global layerIndex;
- opaque reverse traversal;
- translucent forward traversal;
- currentLayer;
- depthRangeSize;
- legacy item 不双绘;
- debug overlay 不重复;
- retained texture composite 不改变 segment 顺序。
推荐的逻辑行为:
```text
for segment in Style order:
RETAINED + Build:
build cache
composite cache
RETAINED + Reuse:
composite cache
RETAINED + Fallback:
live render only this range
CONTINUOUS:
live render this range
```
实际代码可按现有 RenderPass 安全实现,不要求机械照搬伪代码。
必须特别检查:
- 一个 R fallback 不得导致另一个 R live redraw;
- resource prepare 在 Build 前失败时,必须先把该 Segment 本帧 decision 改为 Fallback,再进入主 buffer 绘制;
- retained build 中途失败时,必须 invalidate/标记该 Segment失败并把该 Segment 本帧 decision 改为 Fallback,禁止 composite 旧纹理;
- 如果失败意味着 context / shared GPU state 已不可信,允许全局 fail closed;
- fallback range 的 legacy items 只绘一次,不得因 per-segment 主循环重复 render;
- debug overlay 全帧只绘一次;
- Segment 间 Opaque/Translucent 语义不得被破坏;
- 相邻 R、多个 C、透明叠加与 legacy scene 不能因 mixed execution 出现漏绘/双绘。
推荐执行顺序:先完成各 Segment resource prepare 与必要离屏 Build;再 clear 主 buffer;最后按 Style Segment 顺序逐段选择 cached composite 或 live range render。
---
## 8. Diagnostics 必须继续可证明局部性
保留 STEP-099 diagnostics,并 additive 增加足够字段,使 RDC 能验证正式行为。
每个 RETAINED Segment 至少应能观察:
- dependentSourceIds;
- segment readiness / support;
- segment-local source/content revision 或等价可证明值;
- Build / Reuse / Fallback 的**最终本帧 execution decision**;
- build/reuse/fallback/invalidation count;
- last invalidation/fallback reason。
Source mutation 继续输出:
- sourceId;
- sequence/reason/action;
- affectedSegmentIndices。
要求:
```text
step099-dynamic mutation
affected=[R1]
R0 revision unchanged
R0 cache reuse++
R0 build unchanged
R0 invalidation unchanged
```
Diagnostics 仍是 pull/event low-frequency,不允许逐帧高频 spam。
---
## 9. Android Test App
复用 STEP-099,不另建独立 App。
至少保留并完善:
### T1 STATIC
验证静态 R-C-R:
- loaded settle;
- R0/R1 build 一次后稳定 reuse;
- 不产生持续 rebuild。
### T2 GEOJSON_1HZ
两个 GeoJSON source 交错,各约 1 Hz。
重点:
- global loaded transient false window 可以继续存在;
- 只引用某 source 的 retained Segment 才允许因该 source mutation 改变;
- 无关 retained Segment build/invalidation 不得跟随增长。
### T3 PERMANENT_UNREADY
保留一个:
- no layer;
- enabled=false;
- isLoaded=false;
- no field submission
的 RasterArray source。
预期:
- global renderOrchestratorLoaded 仍可为 false;
- 但无关 R0/R1 retained cache 不应因它持续 `style-not-loaded` fallback;
- 能安全判断依赖的 retained Segment应继续 build/reuse。
### T4 R-C-R(主验收)
必须保持清晰依赖:
```text
R0 -> dedicated static source only
C -> WeatherColor/Particle field
R1 -> dedicated dynamic GeoJSON source
```
运行动态 GeoJSON 约 1 Hz。
必须调整 fixture,避免 STEP-099 中 R1 再复用 R0 的 static source:
- R0 static source 只出现在 R0;
- R1 dynamic source 只出现在 R1;
- 若 MapLibre 内建 annotation source/layer 自动进入 R1,必须在 diagnostics 中列明,并保证验收窗口内 annotation 不发生 mutation;
- 不允许用业务 ID 特判 renderer。
RDC 验收区间必须支持“settled baseline → 60 s pure dynamic-source mutation window → final”对比。每个 aggregate sample 同时记录 mutation source/sequence、各 Segment local revision、readiness、最终 execution decision 与 cache counters。
---
## 10. STEP-100 硬验收标准
### A. R0 locality
T4 settle 后记录 baseline。
随后至少 60 s dynamic GeoJSON:
- R0 `buildDelta == 0`
- R0 `invalidationDelta == 0`
- R0 `reuseDelta > 0`
- R0 segment-local revision/content state 不因 R1 dynamic source mutation 改变
- 即使某采样点 global `renderOrchestratorLoaded=false`,只要 R0 自身 prerequisites/dependencies ready,R0 final decision 仍可为 Reuse
如果为了安全出现明确 global event,应在日志中可解释并重新选择纯动态 mutation 区间;不能把 global event 混入后声称 PASS。
### B. R1 reacts
同一时间窗:
- R1 必须能观察到 source mutation;
- R1 revision / build / invalidation / fallback 中至少有与动态 source 对应的实际变化;
- `affectedSegmentIndices` 必须只包含真实依赖 Segment。
### C. global revision compatibility
- global `retainedUnderlayContentRevision` 允许并预期继续增长;
- 不能通过停止 global revision 增长来制造 R0 PASS。
### D. readiness locality
T2 transient unloaded 或 T3 permanent unrelated unloaded 时:
- global loaded 可 false;
- R0 不得仅因无关 source 的 unloaded 而 invalid/rebuild/fallback;
- R0 cache 必须继续可复用。
### E. correctness
- 无 crash / ANR;
- 无 stale cache composite;
- 无 Segment Z-order 错乱;
- 无 fallback range 双绘;
- Cache OFF 仍安全走 live renderer;
- plan invalid 仍 fail closed。
任一硬门槛失败,STEP-100 不得宣布 PASS,不得进入 V18。
---
## 11. Native 非 Jest 测试要求
Codex 必须新增或扩展现有 C++ 测试源码,至少覆盖:
1. source A mutation 只改变依赖 A 的 Segment revision;
2. source A 被多个 Segment 共享时,对应多个 Segment 都变化;
3. unrelated Segment revision 不变;
4. source-less Segment 不制造 dependency;
5. unknown/global dependency fail closed;
6. unrelated source unloaded 不使安全 Segment readiness false;
7. dependent source unloaded 使对应 Segment readiness false;
8. R0 Reuse + R1 Fallback 的 mixed decision;
9. 单 Segment build failure 不 composite stale cache;
10. query diagnostics 不改变 revision/cache state;
11. global non-source invalidation 仍影响所有 retained;
12. source 删除后同 ID recreate 不误复用旧 Segment cache identity;
13. dependent source fading 只影响对应 Segment;unrelated source fading 不影响安全 Segment;
14. Cache OFF / invalid plan 行为不回归;
15. 至少有一组能证明实际 mixed render 没有漏绘/双绘的 renderer-level draw-count、pixel 或现有等价测试入口;不能只用 decision unit test 代替。
不得修改测试断言去适应错误实现。
---
## 12. 允许修改范围
允许:
- RenderOrchestrator source mutation/readiness bookkeeping;
- RenderSegment dependency production helper;
- Renderer retained per-segment revision/readiness/decision;
- RetainedUnderlayCache/RuntimeState 为局部状态增加必要字段;
- additive diagnostics JSON/TS;
- Android Validation Test App STEP-099 fixture/log;
- 相关 Native C++ tests;
- STEP-100 Step 记录/完成报告/开发记录。
禁止:
- NavSea;
- Integration;
- V17 tag/release worktree;
- Source `isLoaded()` 定义;
- Particle field/provider/network;
- Fast Path gate semantic;
- `singleContinuousSegment`;
- multi-continuous Fast Path;
- 自动改变 Style Z-order;
- provider/business-specific source/layer ID;
- unrelated cleanup/refactor;
- Jest。
---
## 13. Codex 执行边界
Codex 本轮负责:
1. 读取实际源码和任务书;
2. 实现代码;
3. 编写 Native 非 Jest 测试源码;
4. 更新 Android Test App 验证入口;
5. 执行轻量静态检查,如 `git diff --check`;
6. 生成中文完成报告。
Codex **不得执行**:
- Android Gradle / APK;
- ADB / 安装 / 实机;
- Xcode / iOS device;
- V18 release;
- tag;
- push;
- Jest。
本轮 Codex 完成报告固定:
`docs/报告/STEP-100_SourceScoped_RenderSegment局部失效_Codex完成报告_v1.md`
Step 记录固定:
`docs/50_Step记录/STEP-100_SourceScoped_RenderSegment局部失效_v1.md`
完成报告必须写清:
- 实际 Codex model / reasoning effort;
- start HEAD;
- 修改文件;
- 生产设计;
- source revision/readiness 规则;
- mixed retained decision 规则;
- Fast Path 明确未改;
- 测试源码新增情况;
- 实际执行 / NOT RUN;
- 风险与 RDC 验收点;
- 未 commit / 未 push(如果确实如此)。
Codex 不 commit、不 push。由 RDC 验证通过后统一 Git 收尾。
---
## 14. RDC / ChatGPT 后续独立验收
Codex 返回后由 RDC 负责:
1. source/diff 审计;
2. Native Mac 编译;
3. Native focused + broad related C++ tests;
4. Android x86_64 OpenGL validation APK 单次构建;
5. Mac ADB 直连 FZG1-2;
6. 自动跑 T1/T2/T3/T4;
7. 解析 R0/R1 build/reuse/invalidation/revision/readiness;
8. 确认硬验收标准 A~E;
9. 若失败,回到 STEP-100 修复,不发布;
10. 若全部 PASS,完成 STEP-100 Git push;
11. 再按现有统一发行流程升级到:
`maplibre-particle/rn/13.4.1-particle.18`
12. V18 build/release/public artifact 验证通过后才宣布 V18 发布完成。
---
## 15. 停止条件
Codex 遇到以下任一情况必须停止并报告,不得扩大范围:
- 要修改 Source `isLoaded()` 才能继续;
- 需要放宽 Fast Path gate;
- 必须改变 singleContinuous / multi-continuous;
- production dependency 无法可靠从现有 Style/RenderLayer/Source 推导;
- per-segment fallback 会破坏 RenderPass/Z-order 且无法在当前范围安全解决;
- 发现需要 NavSea/Integration 配合;
- 需要业务 layer/source ID;
- 需要改变 V17 release。
---
## 16. 本任务最终原则
STEP-100 的目标不是让“全局世界看起来 loaded”。
目标是:
> **即使某个 GeoJSON Source 正在变化或短暂 unready,只要 R0 与它没有依赖关系,R0 retained cache 就必须继续安全 reuse,不能被迫一起刷新。**
Fast Path 是否还能 miss,是后续独立问题。
只有这一点经过 Native + Android R-C-R 实测成立,才允许进入 V18 发布。

View File

@@ -0,0 +1,405 @@
# STEP-101 RenderOrchestrator teardown 生命周期修复 — Codex 任务 v1
## 0. 任务目的
修复 Particle V18 Android 在正常 Renderer teardown / MapView reset 时发生的 Native SIGABRT。
现场已在 NavSea Android x86_64 设备稳定复现:
```text
Fatal signal 6 (SIGABRT)
thread: RenderThread 11
FORTIFY:
pthread_mutex_lock called on a destroyed mutex
libmaplibre.so
std::__ndk1::mutex::lock()
...
mbgl::android::MapRenderer::resetRenderer()
```
V17 正常,V18 接入后稳定在启动约 9~11 秒触发。
本任务只修 Particle Native 生命周期;禁止在 NavSea 增加绕路、private wrapper、禁止阻止正常 MapView reset。
未经用户明确要求:不加入、不执行 Jest。
---
## 1. 冻结基线
工作树:
`/Users/tei/dev/MapLibreParticleLab-step101`
分支:
`step101-render-orchestrator-teardown`
起点必须是:
`origin/particle/main = d29e84c1a90daeea104b389d28b000c4f017d08d`
即正式 V18 Final HEAD。
V18 已发布,不得覆盖、重发或移动:
- `maplibre-particle/rn/13.4.1-particle.18`
- Android `13.4.1-particle.18`
- iOS `6.28.0-particle.18`
修复通过后由 RDC/ChatGPT 发布新不可变版本 V19。
---
## 2. 已知根因证据
V18 STEP-100 新增:
```cpp
RenderSegmentRevisionBook retainedSegmentRevisions;
mutable std::mutex readinessDiagnosticsMutex;
uint64_t readinessEventSequence;
std::unordered_map<std::string, SourceReadinessEventState> sourceReadinessEvents;
RenderMutationDiagnostics lastReadinessMutation;
```
多个 RenderSource / Tile observer callback 会进入:
```cpp
advanceRetainedSourceRevision(...)
recordReadinessMutation(...)
onTileChanged(...)
onTileAction(...)
buildReadinessDiagnostics(...)
retainedSegmentSourceState(...)
```
并 lock:
```cpp
readinessDiagnosticsMutex
```
V18 当前成员声明中:
```text
renderSources
renderLayers
...
retainedSegmentRevisions
...
readinessDiagnosticsMutex
sourceReadinessEvents
...
threadPool
```
C++ 成员按声明反序析构,因此 readiness state / mutex 会早于 renderSources 销毁。
而 RenderSource 在创建时:
```cpp
renderSource->setObserver(this);
```
observer 指向 RenderOrchestrator。
当前 `RenderOrchestrator::~RenderOrchestrator()` 只:
1. contextLost 时标记 layer;
2. `threadPool.waitForEmpty()`;
没有在 readiness state 仍有效时显式 detach RenderSource observer / 清理 renderSources。
因此 V18 存在明确生命周期危险:
```text
readinessDiagnosticsMutex destroyed
↓
renderSources still destroying/alive
↓
late tile/source callback
↓
RenderOrchestrator::onTile*
↓
lock destroyed mutex
↓
FORTIFY SIGABRT
```
---
## 3. 修复原则
不要只依赖调整成员声明顺序。
本任务应建立显式 teardown contract:
```text
RenderOrchestrator destructor begins
↓
stop / drain deferred scheduler work
↓
detach observer from every RenderSource
↓
destroy/clear renderSources while all orchestrator state is still alive
↓
only then continue member teardown
```
同时 `RenderSource::setObserver(nullptr)` 必须安全恢复到 NullObserver。
目标是即使未来成员声明顺序变化,Source callback 也不能在 owner teardown 后访问 RenderOrchestrator。
---
## 4. 必做实现
### 4.1 RenderSource nullptr observer 语义
当前:
```cpp
void RenderSource::setObserver(RenderSourceObserver* observer_) {
observer = observer_;
}
```
改成与 Style Source / Layer 一致的安全语义:
```cpp
observer = observer_ ? observer_ : &nullObserver;
```
要求:
- constructor 仍默认 NullObserver;
- `setObserver(nullptr)` 是合法 detach;
- onTileChanged / onTileError / onTileAction 不需要加 null 分支;
- 不吞掉正常有 observer 时的回调。
必须加非 Jest C++ 回归测试验证 nullptr detach 后 callback 不 crash、不调用旧 observer。
### 4.2 RenderOrchestrator 显式 teardown
在 `RenderOrchestrator::~RenderOrchestrator()` 中,在 readiness mutex/state 仍确定存活时:
1. 保留现有 contextLost layer handling;
2. 等待已有 deferred work 完成;
3. 对所有 `renderSources` 执行 observer detach;
4. 显式 `renderSources.clear()`;
5. 必要时对 glyph/image observer 也做已有 API 支持的 detach,但不要扩大范围;
6. 不允许 teardown callback 再进入 RenderOrchestrator source callback。
顺序必须结合当前 TaggedScheduler / RenderSource / TilePyramid 的真实生命周期检查,而不是机械照抄上述编号。
如果存在“clear renderSources 本身可能产生新的 scheduler work”,需要设计第二次 drain 或更安全顺序,并在报告解释。
### 4.3 生命周期状态可选保护
如果仅 detach + explicit clear 仍无法证明并发安全,允许增加最小 teardown flag,例如 atomic bool,但只作为二次防线。
禁止用 flag 代替 observer detach。
禁止 catch FORTIFY;destroyed mutex abort 不是 C++ exception。
---
## 5. 不得修改
- STEP-100 Source→Segment locality 语义;
- per-segment revision;
- per-segment readiness;
- mixed Build/Reuse/Fallback;
- Fast Path gate;
- singleContinuousSegment;
- Source::isLoaded;
- Style Z-order;
- provider/network;
- NavSea;
- MapRenderer::resetRenderer 正常生命周期;
- Android Activity 为避免 reset 而加 workaround。
---
## 6. 必做测试
### 6.1 Native 非 Jest
至少覆盖:
1. RenderSource 默认 NullObserver 可安全接 callback;
2. setObserver(real) 正常转发;
3. setObserver(nullptr) 后不再调用旧 observer;
4. detach 后 onTileChanged / onTileError / onTileAction 安全;
5. RenderOrchestrator teardown 有至少一个含 RenderSource 的场景;
6. source callback 与 teardown 边界不访问已销毁 readiness state;
7. STEP-100 locality 相关 focused tests 不回归。
如果现有测试设施不方便直接构造完整 RenderOrchestrator teardown,允许提取最小可测试 teardown helper,但禁止为了测试重构大范围生产结构。
### 6.2 Android Test App
在现有 ParticleCoreLayerValidationActivity / Validation App 增加专门 STEP-101 teardown 场景。
场景必须真实触发 Native Renderer teardown,而不是只 clear field。
至少提供:
```text
T1 RESET_ONCE
T2 RESET_LOOP_20
T3 RESET_WITH_DYNAMIC_GEOJSON
T4 RESET_WITH_DIAGNOSTICS
```
目标:
- recreate/destroy MapView 或等价真实 renderer.reset;
- 每轮等待足够让 source/tile work 进入正常运行;
- T3 同时保持动态 GeoJSON/source mutation;
- T4 在 reset 前调用一次 RendererRuntime diagnostics,模拟 NavSea probe 时序;
- 20 次循环无 SIGABRT / crash / ANR;
- reset 后新 renderer 能继续正常显示 / 获取 diagnostics。
不得通过延迟 reset、禁用 observer、关闭 GeoJSON 来逃避问题。
---
## 7. NavSea 现场等价验收
RDC 在 Codex 完成后独立执行。
Android 设备:
`192.168.200.139:5555`
必须显式 `adb -s`。
最终至少执行:
1. Validation App STEP-101 T1~T4;
2. crash buffer 检查;
3. 20 次 reset/recreate;
4. 安装 V19 进 NavSea 后做真实启动;
5. 保留 NavSea 当前 `key={map-style-generation-...}` 正常 reset 触发条件;
6. 保留/启用一次 RenderSegment diagnostics probe A/B;
7. 连续至少 5 次冷启动,每次观察 >30 秒;
8. 不允许出现:
- `pthread_mutex_lock called on a destroyed mutex`
- SIGABRT RenderThread
- `MapRenderer::resetRenderer` teardown crash。
---
## 8. STEP-100 回归验收
修复 teardown 后必须确认 V18 的核心 locality 没被破坏。
至少复测 T4 R-C-R:
无关 R0:
```text
buildDelta = 0
invalidationDelta = 0
reuseDelta > 0
```
动态 R1 正常 build/invalidation。
禁止通过删除 mutex/readiness/revision 功能来修 crash。
---
## 9. Codex 执行边界
Codex负责:
- 只读检查真实 V18 代码;
- 实现最小 Native 修复;
- 编写/更新非 Jest C++ tests;
- 更新 Android Validation STEP-101 场景;
- 静态检查;
- 生成报告。
Codex不负责:
- NavSea 修改;
- V19 release;
- tag;
- publish;
- 覆盖 V18;
- Jest。
Mac Native 开发/编译原则保持;不要使用 Linux Native 环境。
---
## 10. 完成报告
生成:
`docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md`
Step 记录:
`docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md`
报告必须写:
- root cause;
- start HEAD;
- 修改文件;
- destructor teardown 顺序;
- observer detach 语义;
- 为什么不依赖成员声明顺序;
- tests added;
- 实际运行 / NOT RUN;
- STEP-100 locality 是否保持;
- 风险;
- 未发布 V19(Codex 阶段)。
Codex阶段不 push,由 RDC 验收后统一 commit/push。
---
## 11. V19 发布门槛
仅当:
- Native compile PASS;
- focused C++ tests PASS;
- Android Validation teardown loop PASS;
- STEP-100 locality regression PASS;
- NavSea V19 真实 5 次冷启动 >30s PASS;
- crash buffer 无 destroyed mutex;
才允许发布:
- RN / Android:`13.4.1-particle.19`
- iOS:`6.28.0-particle.19`
- tag:`maplibre-particle/rn/13.4.1-particle.19`
V18 保持不可变,不覆盖、不删除。
---
## 12. 最终原则
正常:
```text
MapView unmount
Style generation replacement
renderer.reset()
Activity recreate
```
都是合法生命周期。
Particle Native 必须做到:
> Renderer teardown 永远不能因为 observer late callback 访问已销毁 mutex/state 而杀掉宿主 App。

View File

@@ -0,0 +1,510 @@
# STEP-101 RenderOrchestrator teardown 生命周期修复 — Codex 任务 v1
## 0. 任务目的
修复 Particle V18 Android 在正常 Renderer teardown / MapView reset 时发生的 Native SIGABRT。
现场已在 NavSea Android x86_64 设备稳定复现:
```text
Fatal signal 6 (SIGABRT)
thread: RenderThread 11
FORTIFY:
pthread_mutex_lock called on a destroyed mutex
libmaplibre.so
std::__ndk1::mutex::lock()
...
mbgl::android::MapRenderer::resetRenderer()
```
V17 正常,V18 接入后稳定在启动约 9~11 秒触发。
本任务只修 Particle Native 生命周期;禁止在 NavSea 增加绕路、private wrapper、禁止阻止正常 MapView reset。
未经用户明确要求:不加入、不执行 Jest。
---
## 1. 冻结基线
工作树:
`/Users/tei/dev/MapLibreParticleLab-step101`
分支:
`step101-render-orchestrator-teardown`
起点必须是:
`origin/particle/main = d29e84c1a90daeea104b389d28b000c4f017d08d`
即正式 V18 Final HEAD。
V18 已发布,不得覆盖、重发或移动:
- `maplibre-particle/rn/13.4.1-particle.18`
- Android `13.4.1-particle.18`
- iOS `6.28.0-particle.18`
修复通过后由 RDC/ChatGPT 发布新不可变版本 V19。
---
## 2. 已知根因证据
V18 STEP-100 新增:
```cpp
RenderSegmentRevisionBook retainedSegmentRevisions;
mutable std::mutex readinessDiagnosticsMutex;
uint64_t readinessEventSequence;
std::unordered_map<std::string, SourceReadinessEventState> sourceReadinessEvents;
RenderMutationDiagnostics lastReadinessMutation;
```
多个 RenderSource / Tile observer callback 会进入:
```cpp
advanceRetainedSourceRevision(...)
recordReadinessMutation(...)
onTileChanged(...)
onTileAction(...)
buildReadinessDiagnostics(...)
retainedSegmentSourceState(...)
```
并 lock:
```cpp
readinessDiagnosticsMutex
```
V18 当前成员声明中:
```text
renderSources
renderLayers
...
retainedSegmentRevisions
...
readinessDiagnosticsMutex
sourceReadinessEvents
...
threadPool
```
C++ 成员按声明反序析构,因此 readiness state / mutex 会早于 renderSources 销毁。
而 RenderSource 在创建时:
```cpp
renderSource->setObserver(this);
```
observer 指向 RenderOrchestrator。
当前 `RenderOrchestrator::~RenderOrchestrator()` 只:
1. contextLost 时标记 layer;
2. `threadPool.waitForEmpty()`;
没有在 readiness state 仍有效时显式 detach RenderSource observer / 清理 renderSources。
因此 V18 存在明确生命周期危险:
```text
readinessDiagnosticsMutex destroyed
↓
renderSources still destroying/alive
↓
late tile/source callback
↓
RenderOrchestrator::onTile*
↓
lock destroyed mutex
↓
FORTIFY SIGABRT
```
---
## 3. 修复原则
不要只依赖调整成员声明顺序。
本任务应建立显式 teardown contract:
```text
RenderOrchestrator destructor begins
↓
stop / drain deferred scheduler work
↓
detach observer from every RenderSource
↓
destroy/clear renderSources while all orchestrator state is still alive
↓
only then continue member teardown
```
同时 `RenderSource::setObserver(nullptr)` 必须安全恢复到 NullObserver。
目标是即使未来成员声明顺序变化,Source callback 也不能在 owner teardown 后访问 RenderOrchestrator。
---
## 4. 必做实现
### 4.1 RenderSource nullptr observer 语义
当前:
```cpp
void RenderSource::setObserver(RenderSourceObserver* observer_) {
observer = observer_;
}
```
改成与 Style Source / Layer 一致的安全语义:
```cpp
observer = observer_ ? observer_ : &nullObserver;
```
要求:
- constructor 仍默认 NullObserver;
- `setObserver(nullptr)` 是合法 detach;
- onTileChanged / onTileError / onTileAction 不需要加 null 分支;
- 不吞掉正常有 observer 时的回调。
必须加非 Jest C++ 回归测试验证 nullptr detach 后 callback 不 crash、不调用旧 observer。
### 4.2 RenderOrchestrator 显式 teardown
在 `RenderOrchestrator::~RenderOrchestrator()` 中,在 readiness mutex/state 仍确定存活时:
1. 保留现有 contextLost layer handling;
2. 等待已有 deferred work 完成;
3. 对所有 `renderSources` 执行 observer detach;
4. 显式 `renderSources.clear()`;
5. 必要时对 glyph/image observer 也做已有 API 支持的 detach,但不要扩大范围;
6. 不允许 teardown callback 再进入 RenderOrchestrator source callback。
顺序必须结合当前 TaggedScheduler / RenderSource / TilePyramid 的真实生命周期检查,而不是机械照抄上述编号。
如果存在“clear renderSources 本身可能产生新的 scheduler work”,需要设计第二次 drain 或更安全顺序,并在报告解释。
### 4.3 生命周期状态可选保护
如果仅 detach + explicit clear 仍无法证明并发安全,允许增加最小 teardown flag,例如 atomic bool,但只作为二次防线。
禁止用 flag 代替 observer detach。
禁止 catch FORTIFY;destroyed mutex abort 不是 C++ exception。
---
## 5. 不得修改
- STEP-100 Source→Segment locality 语义;
- per-segment revision;
- per-segment readiness;
- mixed Build/Reuse/Fallback;
- Fast Path gate;
- singleContinuousSegment;
- Source::isLoaded;
- Style Z-order;
- provider/network;
- NavSea;
- MapRenderer::resetRenderer 正常生命周期;
- Android Activity 为避免 reset 而加 workaround。
---
## 6. 必做测试
### 6.1 Native 非 Jest
至少覆盖:
1. RenderSource 默认 NullObserver 可安全接 callback;
2. setObserver(real) 正常转发;
3. setObserver(nullptr) 后不再调用旧 observer;
4. detach 后 onTileChanged / onTileError / onTileAction 安全;
5. RenderOrchestrator teardown 有至少一个含 RenderSource 的场景;
6. source callback 与 teardown 边界不访问已销毁 readiness state;
7. STEP-100 locality 相关 focused tests 不回归。
如果现有测试设施不方便直接构造完整 RenderOrchestrator teardown,允许提取最小可测试 teardown helper,但禁止为了测试重构大范围生产结构。
### 6.2 Android Test App
在现有 ParticleCoreLayerValidationActivity / Validation App 增加专门 STEP-101 teardown 场景。
场景必须真实触发 Native Renderer teardown,而不是只 clear field。
至少提供:
```text
T1 RESET_ONCE
T2 RESET_LOOP_20
T3 RESET_WITH_DYNAMIC_GEOJSON
T4 RESET_WITH_DIAGNOSTICS
```
目标:
- recreate/destroy MapView 或等价真实 renderer.reset;
- 每轮等待足够让 source/tile work 进入正常运行;
- T3 同时保持动态 GeoJSON/source mutation;
- T4 在 reset 前调用一次 RendererRuntime diagnostics,模拟 NavSea probe 时序;
- 20 次循环无 SIGABRT / crash / ANR;
- reset 后新 renderer 能继续正常显示 / 获取 diagnostics。
不得通过延迟 reset、禁用 observer、关闭 GeoJSON 来逃避问题。
---
## 7. NavSea 现场等价验收
RDC 在 Codex 完成后独立执行。
Android 设备:
`192.168.200.139:5555`
必须显式 `adb -s`。
最终至少执行:
1. Validation App STEP-101 T1~T4;
2. crash buffer 检查;
3. 20 次 reset/recreate;
4. 安装 V19 进 NavSea 后做真实启动;
5. 保留 NavSea 当前 `key={map-style-generation-...}` 正常 reset 触发条件;
6. 保留/启用一次 RenderSegment diagnostics probe A/B;
7. 连续至少 5 次冷启动,每次观察 >30 秒;
8. 不允许出现:
- `pthread_mutex_lock called on a destroyed mutex`
- SIGABRT RenderThread
- `MapRenderer::resetRenderer` teardown crash。
---
## 8. STEP-100 回归验收
修复 teardown 后必须确认 V18 的核心 locality 没被破坏。
至少复测 T4 R-C-R:
无关 R0:
```text
buildDelta = 0
invalidationDelta = 0
reuseDelta > 0
```
动态 R1 正常 build/invalidation。
禁止通过删除 mutex/readiness/revision 功能来修 crash。
---
## 9. Codex 执行边界
Codex负责:
- 只读检查真实 V18 代码;
- 实现最小 Native 修复;
- 编写/更新非 Jest C++ tests;
- 更新 Android Validation STEP-101 场景;
- 静态检查;
- 生成报告。
Codex不负责:
- NavSea 修改;
- V19 release;
- tag;
- publish;
- 覆盖 V18;
- Jest。
Mac Native 开发/编译原则保持;不要使用 Linux Native 环境。
---
## 10. 完成报告
生成:
`docs/报告/STEP-101_RenderOrchestrator销毁生命周期修复_Codex完成报告_v1.md`
Step 记录:
`docs/50_Step记录/STEP-101_RenderOrchestrator销毁生命周期修复_v1.md`
报告必须写:
- root cause;
- start HEAD;
- 修改文件;
- destructor teardown 顺序;
- observer detach 语义;
- 为什么不依赖成员声明顺序;
- tests added;
- 实际运行 / NOT RUN;
- STEP-100 locality 是否保持;
- 风险;
- 未发布 V19(Codex 阶段)。
Codex阶段不 push,由 RDC 验收后统一 commit/push。
---
## 11. V19 发布门槛
仅当:
- Native compile PASS;
- focused C++ tests PASS;
- Android Validation teardown loop PASS;
- STEP-100 locality regression PASS;
- NavSea V19 真实 5 次冷启动 >30s PASS;
- crash buffer 无 destroyed mutex;
才允许发布:
- RN / Android:`13.4.1-particle.19`
- iOS:`6.28.0-particle.19`
- tag:`maplibre-particle/rn/13.4.1-particle.19`
V18 保持不可变,不覆盖、不删除。
---
## 12. 最终原则
正常:
```text
MapView unmount
Style generation replacement
renderer.reset()
Activity recreate
```
都是合法生命周期。
Particle Native 必须做到:
> Renderer teardown 永远不能因为 observer late callback 访问已销毁 mutex/state 而杀掉宿主 App。
---
# 13. Codex 6-Sol High 架构评审修订(v2,优先级高于前文冲突表述)
只读评审结论:`APPROVE_WITH_CHANGES`。
## 13.1 根因表述修正
当前已经**代码层证实**存在生命周期漏洞:`readinessDiagnosticsMutex` / readiness state 的析构顺序早于 `renderSources`,而 `RenderSource` observer 仍指向 `RenderOrchestrator`;source/tile teardown 可同步或延迟回调进入 `onTileAction/onTileChanged` 并访问 mutex。
该漏洞与现场 `pthread_mutex_lock called on a destroyed mutex` 高度吻合;在没有完整符号化 crash stack 精确到具体 callback 前,不把“唯一现场根因”写成数学意义上的唯一证明。
## 13.2 teardown 必须双 drain
最低安全顺序调整为:
```text
contextLost layer handling
↓
第一次 threadPool.waitForEmpty()
↓
detach RenderSource observers
↓
detach GlyphManager / ImageManager observers
↓
renderSources.clear() // readiness mutex/state 此时仍存活
↓
第二次 threadPool.waitForEmpty()
↓
继续既有 backend/context render-job teardown
↓
成员析构
```
原因:source/tile destruction 本身可能投递新的 tagged cleanup/deferred work,第一次 drain 不能覆盖 clear 过程中产生的新任务。
同时必须审计并在报告说明:`TaggedScheduler::waitForEmpty()` 不等于清空 `runOnRenderThread` 队列;后者依赖现有 backend/context render-job 生命周期。不得声称双 drain 自动证明所有 render-thread job 已清空。
## 13.3 Observer detach 范围
除了所有 `RenderSource`:
- `glyphManager->setObserver(nullptr)`;
- `imageManager->setObserver(nullptr)`;
也应在 owner teardown 时显式 detach(前提是现有 API 的 nullptr 已安全回 NullObserver)。
`RenderSource::setObserver(nullptr)` 必须改为 NullObserver fallback。
不得直接给 `TilePyramid` / `Tile` observer 写 nullptr,除非其 API 本身有同等安全语义;本任务不扩展到这一步。
## 13.4 teardown flag
默认不增加 teardown atomic flag。
若实现审计发现真实并发 callback 不受 renderer thread / tagged scheduler 串行保证约束,必须先报告;atomic flag 只能二次防护,不能替代 observer detach、drain 和可等待取消。
## 13.5 Android Validation 证据升级
T1~T4 每一轮必须能证明发生了**真实 Native renderer reset**:
- 旧 MapView detach / SurfaceView detach;
- `nativeReset()`;
- `MapRenderer::resetRenderer()` 进入/完成;
- 新 renderer style ready;
- 新 renderer diagnostics 可用。
不能把 `setStyle()`、clear field、只读 diagnostics 当成 teardown 测试。
推荐按代次销毁并重建 MapView,或 Activity recreate;T2 至少 20 次已确认 reset。
T3:每轮 reset 前保持动态 GeoJSON/source mutation。
T4:每轮 reset 前调用一次 Native diagnostics,模拟 NavSea 8 秒 probe 的等价条件。
每轮应等待 source/tile 确实进入运行态,而不是只按固定 sleep 机械计数。
## 13.6 测试必须覆盖真实 source teardown
空 `RenderOrchestrator` 析构测试不够。
非 Jest C++ 测试至少需要一个含 RenderSource / tile callback 的 teardown 场景,使 source cancellation / callback 能在销毁边界发生,并验证旧 observer 不再收到回调。
## 13.7 正常 source erase / clearData 审计
必须只读检查 `clearData()` 和正常 source erase/replacement 路径,因为它们也会销毁 RenderSource。
本任务主要修 owner teardown;不得未经证明把 destructor 修复泛化为“所有 source replacement 异步问题均已解决”。若发现相同 observer 生命周期漏洞在正常 erase 路径存在,应做最小同源修复或明确 BLOCK,不得隐藏。
## 13.8 V19 候选验收顺序
V19 发布门槛修正为:
```text
STEP-101 source code PASS
→ 构建未公开的 V19 candidate artifacts
→ Validation App 实机 PASS
→ NavSea 使用 candidate 做 5 次冷启动/teardown 验收
→ 公开发布不可变 V19
→ public readback
→ finalize/tag
```
不得先公开 V19 再做 NavSea 验收。

View File

@@ -0,0 +1,262 @@
# STEP-102 RenderSegment 无 Placement 段局部复用 Codex 任务 v1
## 1. Step 编号和目的
STEP-102:修复 full CPU 多 Retained Segment 下 placement aggregate gate 的不必要扩散,使**结构上不具备 placement 能力**的 Retained Segment 能在其他 placement-bearing Segment fallback/live 时继续独立 composite/reuse。
本 Step 的设计依据:
`docs/20_调查/RenderSegment多Retained段全局Placement门槛_粒子管理员审查说明_v3.md`。
## 2. 当前已冻结基线
- 仓库:`/Users/tei/dev/MapLibreParticleLab-step102`
- branch:`step102-placement-free-retained-locality`
- start HEAD:`6df21dac38f072c5ad35b8dfc7ec5217509602bc`
- upstream base:MapLibre Native core 既有冻结基线,不升级
- V19/V20 release worktree:冻结,只读,不修改
- STEP-100 source-scoped locality:必须保持
- STEP-101 teardown 修复:必须保持
- Fast Path:本 Step 不修改
- C-001:不升级
## 3. 本 Step 目标
必须实现:
1. 增加结构性 placement-capable range 查询;
2. full CPU 每个 Retained Segment 独立计算 placement gate;
3. placement-free Retained 不再被其他 Segment 的 placementChanged/symbolFadeChange 纯 aggregate gate 拖入 fallback;
4. placement-bearing Retained 继续使用现有保守 placement gate;
5. 从 full CPU `globalLiveFallback` 移除纯 aggregate placement 条件;
6. 保持真正全局 fail-closed 条件;
7. diagnostics 增加结构 placement、per-segment gate、实际 composite/live 与全局 main-buffer execution 信息;
8. RN diagnostics TS 类型做 additive 扩展;
9. 增加对应 C++ 测试源码与 Android Validation 场景源码;
10. 不改变 Style Z-order、mixed executor、C-001 和 RN method surface。
## 4. 非目标 / 禁止事项
禁止:
- placement-bearing Segment 之间的局部碰撞缓存;
- per-segment collision graph / symbol fade / placement fingerprint;
- Fast Path 改造;
- multi-continuous Fast Path;
- 重写 `visitRenderSegmentExecution()`;
- 修改 NavSea;
- 新增 NavSea 私有 Native API;
- 修改 C-001;
- 修改 V19/V20 release worktree/tag;
- 升级 MapLibre、NDK、Gradle、AGP、Java、Node 或其他工具链;
- 为通过测试降低 fail-closed 门槛;
- Android Gradle/build、APK/AAR build、ADB、设备操作;
- Jest。用户未要求 Jest,本任务不得新增或执行 Jest。
## 5. 允许修改范围
生产代码优先限制在:
- `src/mbgl/renderer/render_orchestrator.hpp`
- `src/mbgl/renderer/render_orchestrator.cpp`
- `src/mbgl/renderer/renderer_impl.cpp`
- `include/mbgl/renderer/render_segment.hpp`
- `src/mbgl/renderer/retained_underlay_runtime_state.hpp`
- `src/mbgl/renderer/render_segment_json.hpp`
- `packages/maplibre-particle-rn/src/types.ts`
测试/Validation 允许:
- `test/renderer/retained_underlay_cache.test.cpp`
- 与现有 Particle Validation 场景直接相关的最小 Android Test App 文件
文档允许:
- 本任务书
- `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`
- `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
- `粒子开发记录.md`
- v3 审查说明只读,除非发现明确事实错误;若需改,停止并先报告
如果实现必须扩大生产代码范围,先停止并报告原因,不得自行扩张架构。
## 6. 禁止修改范围
- NavSea 仓库
- `navsea-particle-integration` contract/RFC/ADR
- Fast Path 逻辑、`tryRenderParticleFastFrame()`、`singleContinuousSegment()` 语义
- `retainedRangeUsesPlacement()` 现有动态语义
- mixed executor 算法
- Source `isLoaded()` 定义
- STEP-100 source revision/locality 语义
- STEP-101 teardown 生命周期语义
- renderer/backend 架构
- release/version/tag/publish 坐标
## 7. 实施要求
### 7.1 结构性 placement-capable 查询
新增 full CPU retained 专用查询,必须基于 layer 结构,而不是当前帧 placementData。
最小保守语义:
- range 中存在 `symbol` layer → placement-capable
- 无 `symbol` layer → placement-free
- custom/custom-drawable 继续由 unsupported gate 处理
不得用 `needsPlacement()==false` 证明 placement-free。
### 7.2 per-segment placement gate
每个 Retained Segment 至少形成两个明确事实:
- structural placement capability
- current full-CPU placement gate result
placement-free Segment 不受纯 global placement gate 阻塞。
placement-bearing Segment 本 Step 继续沿用旧保守 gate,不承诺稳定后 reuse。
变量/字段命名不得把 `symbolFadeChange==0` 错称为严格 placement stable。
### 7.3 full CPU mixed execution
`segmentSupported` 改为使用对应 Segment placement gate。
`globalLiveFallback` 仅移除纯 aggregate placement 条件,其他真正 global 条件继续保留。
不得重写 `visitRenderSegmentExecution()`。
### 7.4 fallback reason
`static-placement` 必须按 Segment 自身 placement capability/gate 判定,不能从 aggregate reason 无差别传播。
### 7.5 diagnostics
每个 Segment 最少新增等价语义:
- `placementCapable`
- `placementGatePassed`
- `actualExecution = composite | live`
全局最少新增等价语义:
- `placementChanged`
- `symbolFadeChange`
- `placementTransitionActive`
- `crossSourceCollisions`
- `mainBufferExecutionMode = mixed | global-live`
- `globalLiveFallbackReason`
`actualExecution` 必须反映本帧 main-buffer 最终实际执行,不能根据 cache finalDecision 推导。
Diagnostics 查询必须无副作用、不得推进 revision。
### 7.6 RN 类型
只扩展现有 diagnostics 返回类型,不新增 method,不改变 `setRenderSegmentPlan()` 输入 contract。
Android/iOS Shared Core JSON 语义保持一致。
## 8. 构建要求
Codex 本阶段**不得执行任何 Android 编译/Gradle/APK/AAR build**,也不得启动长时间 CMake/Ninja/Bazel 构建。
Codex 完成代码后只需提供精确验证命令,由后续 RDC/用户按规则执行。其中 Android 编译由用户手工执行。
Codex 可执行的机械静态检查仅限:
- `git diff --check`
- 只读 grep/rg/git diff/status
- 不产生构建产物的格式/静态检查(若仓库已有且无需安装依赖)
不得安装或升级系统依赖来制造测试 PASS。
## 9. 测试要求
Codex 必须新增/补强测试源码,但本阶段不执行大型测试。
至少覆盖:
1. placement-free R0 + placement-bearing R1:R0 可 composite,R1 live;
2. 含 symbol 但当前 placementData 为空:仍判定 placementCapable=true;
3. 两个 placement-free Retained:持续 reuse/composite;
4. source-local fading/mutation 回归不误伤 R0;
5. placement-bearing 在 transition disabled / `symbolFadeChange=1` 等状态仍不被本 Step 放宽;
6. 真正 global gate 继续 global-live/fail-closed;
7. topology 变化释放旧 resource;
8. cache decision=reuse 但 global-live 时,diagnostics 的 actualExecution 必须为 live;
9. diagnostics JSON/TS 字段语义一致且查询无副作用。
Android Validation 场景应能在后续手工构建后明确输出 STEP-102 验收所需字段,不依赖 NavSea。
## 10. 验收标准
代码审计门槛:
- [ ] 新增结构性 placement-capable 查询
- [ ] 未改变 `retainedRangeUsesPlacement()` 旧语义
- [ ] full CPU per-segment placement gate 生效
- [ ] placement-bearing 继续保守
- [ ] `globalLiveFallback` 只移除纯 aggregate placement 条件
- [ ] 真正 global gate 保留
- [ ] mixed executor 未重写
- [ ] Fast Path 无语义修改
- [ ] per-segment fallback reason 正确
- [ ] diagnostics 有 actualExecution 与 main-buffer mode/fallback reason
- [ ] RN method surface 不变
- [ ] C-001 不变
- [ ] 测试源码覆盖设计矩阵
- [ ] `git diff --check` PASS
运行态 PASS 不在 Codex 阶段宣称。Android 手工编译与真机验证未完成前,STEP-102 状态只能写“代码完成 / 等待验证”或同等非 PASS 状态。
## 11. Git 要求
- 工作分支:`step102-placement-free-retained-locality`
- 禁止修改 particle/main 工作区
- 禁止 `git add -A`、`git add .`、`git reset --hard`、`git clean`
- 只 scoped staging 本任务文件
- 不提交无关 dirty/untracked 文件
- Codex 可在实现完成、静态检查通过后创建 scoped implementation commit,但**不得 merge 到 particle/main、不得 push release 分支/tag、不得发布制品**
- 是否 push/merge 由后续独立验收决定
## 12. 必做输出
Codex 完成后必须交付:
1. 生产代码修改;
2. C++ 测试源码;
3. Android Validation STEP-102 场景源码/日志字段;
4. `docs/50_Step记录/STEP-102_RenderSegment无Placement段局部复用_v1.md`;
5. `docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`;
6. 追加 `粒子开发记录.md`;
7. 精确列出后续验证命令;
8. 明确列出所有 NOT RUN / BLOCKED 项;
9. Git diff/status/commit(若创建)事实。
## 13. 执行报告完整文件名
固定:
`docs/报告/STEP-102_RenderSegment无Placement段局部复用_Codex完成报告_v1.md`
同主题后续报告只递增 `_v2/_v3`,不得另造近义文件名。
## 14. 执行报告固定内容
至少包含:
1. 执行结论:只能按实际写代码完成/等待验证、PARTIAL、BLOCKED 或 FAIL;未运行 Android 不得写运行态 PASS
2. Git 基线:branch/start HEAD/end HEAD/status
3. 实际修改文件
4. 新增文件
5. 实现内容逐条对照任务书
6. 与 v3 设计差异
7. Fast Path / mixed executor / C-001 未修改审计
8. diagnostics 新字段与语义
9. 测试源码覆盖矩阵
10. 实际执行的静态检查
11. 未执行的 build/test/Android/设备项目
12. 用户手工 Android 构建命令
13. 后续 RDC/真机验收命令与硬门槛
14. 已知风险与未完成事项
15. Git commit 信息(若有)
## 15. 阻塞与停止条件
遇到以下任一情况必须停止扩大修改并报告:
- 结构性 placement capability 不能仅靠既有 layer type 安全判定,需要新增更大公共抽象;
- 必须修改 Fast Path 才能完成本任务;
- 必须重写 mixed executor;
- 必须改变 collision/Placement 核心算法;
- 必须修改 C-001 / RN method surface;
- 必须修改 NavSea;
- 必须扩大到 renderer/backend 架构重构;
- 发现 global revision 仍有未预期的 placement 推进,需要新设计;
- 需要升级工具链/依赖;
- 需要删除或弱化既有测试/安全门槛。
若实现范围保持在 v3 冻结边界内,可直接完成代码并停在“等待手工构建/运行验证”。

View File

@@ -0,0 +1,43 @@
# STEP-104 官方 MapLibre Native Android 13.6.1 迁移:第一阶段 Codex 任务 v1
## 1. 基线与职责
- 用户授权 **GPT-6.1 Sol / High** 实施官方跟进,使用 Mac 固定 GNU Screen 会话 `20825.7445.codex`,不要自行切换模型和推理档位。
- 项目独立 worktree:`/Users/tei/dev/MapLibreParticleLab-upgrade-android-1361`,分支 `upgrade/android-native-13.6.1-20261009`。
- 当前第一父基线:**官方 Android Native 13.6.1** peeled commit `c7506d6aff3800e946f89e1a51432fc22d1a6534`;原有自研稳定版本(.21)HEAD `630ec657687bad4200c059bbccbb950d9c6f7ce9`,审计与报告闭环 HEAD `b433775348373b10fba02dc0e1f728bacd72f9e7`。
- Integrated repo Linux 远端 `main` 已同步至 `80667d6c343411d03a760425438e3d292f452118`;当前遵守 C-001/C-002、RFC-004、ADR-001,不得擅自改公开 API 或将 Android 新行为写进跨平台 contract。
- 先读原工作树 `/Users/tei/dev/MapLibreParticleLab/AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 与四份有效总则,再读现有审计报告 `/Users/tei/dev/MapLibreParticleLab-upstream-audit-20261009/docs/报告/UPSTREAM-001_MapLibre官方升级兼容性架构审计_Codex完成报告_v1.md`、近期 STEP-100/101/102 记录以及本任务。
- 另一个 Mac `particle/main` 工作树有用户未跟踪文件且落后远端,禁止修改;历史 release/STEP-103 工作树均不可改。
## 2. 本轮的工程目标和停点
目标是形成一个**可测试的 Android Native 13.6.1 官方底座候选**,继续保留自研 Field/QBIN/Particle/WeatherColor/RenderSegment/Retained 功能,不重写、不扩大功能。当前不是新版发行。
- A:在本分支以官方 13.6.1 为第一父,安全合入 `b433775348...` 自研历史的有效差异。优先使用 `git merge --no-commit --no-ff b433775348...`;解决全部 rename/content/modify-delete 冲突,不允许选择整文件 `ours/theirs` 丢失官方修复或自己的功能。核实 `git diff --check`、冲突标记和完整树,再创建明确 merge commit。可启用 rerere。
- B:适配官方 `mbgl→mln` 命名空间、include、Layer/Source/Shader factory、`RenderLayer::update(PaintParameters)`、`RenderStaticData::backendSize` 移除、Renderer lifecycle、RenderSegment 和 GL 粒子后端。只修改当前升级必要的官方接入点,保留现有 fail-closed、source/placement/revision/fast-path gate、map Style 顺序、stencil/target 安全。
- C:源码修改完成后,列出**精确可执行**的 Android Native C++/OpenGL 编译与定向测试命令,让 RDC 在 Mac 或 Linux 执行;Codex 自身不得进行耗时 build、link、codegen、打包或 Jest。可以执行 `git diff --check`、少量静态检查。
- D:若完整迁移无法在本轮完成,优先形成单独的、可复现且可回退的 Git checkpoint、明确残留冲突/失败清单;未清除 Git 冲突不得提交伪造的成功 merge。发生公共 API/Contract 变化或 iOS 专属需求立即停下来报告,不自行扩大跨团队范围。
## 3. 必须保留的现有行为
- C-002 QBIN V4 单帧 exact match、双帧合法插值、每 fieldKey 八帧和原子拒绝语义不变;不同 reference/dataset/profile 不混用;RN Host 负责加载、requestedForecastTime,Native submit 不调整时间。
- WeatherColorLayer 与 RasterParticleLayer 独立;粒子 GPU ping-pong、trail 和 RenderSegment 局部缓存存在;不允许用 CPU 动画替代、不允许关闭关键保护逻辑使其编译通过。
- Retained 保留 Z-order、opaque/translucent 绘制语义与 segment 索引;Source/layer/style/context/placement 变更失效安全、销毁不泄漏或使用陈旧 texture。
- 官方新版 Feature State 接口不得被自研逻辑覆盖或静默禁用;只记录 Native 后续 RN 11.5 测试 Gate,不升级 RN 包本轮版本锁。
## 4. 范围
- 允许:升级分支中的官方 Native Core/Shader/CMake、必要的 Android Native/JNI 接入、粒子模块源码、测试代码的机械迁移与必要接口适配,以及必要记录。
- 禁止:修改 `particle/main`、`release/particle.21`、STEP-103 工作树、NavSea、Integration 合同、iOS 专属 Metal/Bridge 功能实现、官方仓库、Node/Gradle/NDK 工具链;禁止发布 AAR/npm、build APK、改 public API/ABI 语义。
- 旧 .21 所有发布坐标保持冻结。iOS 旧实现应尽可能保留源码,但本轮只允许写 **NOT VERIFIED**,不允许声称 Metal/Swift/XCFramework PASS。
## 5. 记录和 Git:强制多次小提交
- 在仓库**根目录的 `recoder_step.md`** 追加每项实质进展,简明中文且 append-only,记录日期、基线、当前变化、已完成、剩余问题、下一步、关联 commit。还需追加 `粒子开发记录.md`(合入后存在),同步更新 `docs/50_Step记录/STEP-104_官方Native13.6.1升级阶段A_v1.md`。
- 本任务已有独立启动 checkpoint 提交。后续**至少尝试**按合并/结构迁移/接口修复/文档三个功能批次提交。每次提交限定本主题的文件并有清晰中英 commit message。不允许为了凑提交提交空文件或破坏历史;若阻塞,就如实说明只完成哪些。
- **仅 push 本次升级分支**到 `origin/upgrade/android-native-13.6.1-20261009`;切勿 push `particle/main`、force push、合并回主线、push upstream。需等 RDC 独立编译验收后再由 RDC 最终执行 push;Codex 此轮只 commit,不负责最终远端验收。
- 用户正式要求阶段记录必须叫 `recoder_step.md`,**不可擅自纠正拼写或用其它文件代替**。
- 工作期间若发现现存不相关改动,不能纳入任何提交。
## 6. 完成报告与检查
- 完成报告固定 `docs/报告/STEP-104_官方Native13.6.1升级阶段A_Codex完成报告_v1.md`,必须记录:实际模型与推理档位、原始 SHA 和候选 SHA、每个 commit 与变更范围、merge 冲突解决的准确状态、迁移清单、具体核心 API/行为检查、无 build 时 NOT RUN、给 RDC 的精确构建命令、剩余问题、Android/iOS/RN 明确边界、下一 Gate。
- 命名绝对不能自称正式 SDK PASS;如只完成局部迁移标 PARTIAL / BLOCKED。Codex 结束前检查 `git status --short`、`git diff --check`、阶段日志存在,任务范围与 commit 对得上。
- 对用户的最终下一停点是 Android 官方 13.6.1 候选的 Core/Android OpenGL 精确编译 Gate;RN 11.5/iOS/发布留后续。
- 若需要新增跨平台语义/接口必须暂停并记录 Integration 协调所需证据,而不是自行做新合同。
## 7. 本阶段特殊实施规则
按现有项目 `AGENTS.md`:**Codex 写代码,RDC 负责独立编译和验收**。仅在完成局部代码和提交后提供精确编译命令,等待 RDC 结果,如失败再进入下一轮修复。不要自行执行长时间构建。

View File

@@ -0,0 +1,161 @@
# STEP-110 R0 Placement 跨段归因与缓存安全边界只读调查 — Codex 任务 v1
## 一、任务性质与核心问题
**性质:Particle Native / Shared Core 专项,只读调查;不实现、不修复、不编译、不上机。**
用户要求粒子项目独立回答:
> Native 能否区分“R0 自己的 Placement 变化”和“其他 RenderSegment 引起的 Placement 变化”?如果能区分,是否足以在**不破坏 Symbol 碰撞、文字/图标位置及图层顺序**的条件下让未变化的 R0 复用缓存?
必须把以下不同概念分开审查:
1. **事件来源**:哪个 source/layer/segment 引发了 placement/repaint/revision;
2. **实际影响范围**:全局碰撞计算是否使 R0 的 Symbol 位置、可见性、fade 或绘制结果改变;
3. **缓存安全判据**:能否证明 R0 内容/Placement 仍与已缓存画面等价;
4. **当前实现**:上述信息目前是否已有、是否按 Segment 使用、还是仅采用全局保守 Gate。
禁止预设“全局 Placement 变化一定是 Bug”,也禁止以“R0 自己没有提交数据”直接推导“R0 视觉必然没有变化”。
## 二、仓库、版本与事实优先级
- 项目:`/Users/tei/dev/MapLibreParticleLab`,正式分支 `particle/main`、远端 `origin/particle/main`。
- 创建任务时只读确认:`particle/main` HEAD `2df504cdc0403bdad85d3f31f16fd54a8744abac`、干净工作树。执行时必须**重新读取 Git 实际状态**,不得把此 HEAD 当作永恒基线。
- NavSea 产品实机所用 APK 内 `assets/component-manifest.json` 记录:RN / Android Native AAR `13.6.1-particle.23`。V23 正式来源对应 `refs/tags/maplibre-particle/rn/13.6.1-particle.23`、GSRL Git commit `87f3d1c59a52581fb8339f0ea7d106b9f4b94fef`。
- **必须分别标明 V23 发布源码与当前 `particle/main` 的事实**。如当前 main 有未发行后续改动,不能以 main 代替 V23,不能以 V23 APK 推断未发行修复已经生效。
- 跨项目事实遵循 `D:\React\navsea-particle-integration\instructions\NAVSEA_PROJECT_INSTRUCTIONS.md` 及最新 COORD / RFC / ADR / Contract / Status;当前既有外部契约 C-001。此任务不新增/更改公共 API 或契约。
- 粒子项目根 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、`docs/00_项目总则/README.md` 与四份有效总则必须先读,按当前项目事实处理过时的历史环境/发布坐标。
- 必读既有证据:STEP-088、089、090、092、094、099、100、102、106、108、109;重点对比 STEP-090 早期缓存/快路成功条件、STEP-102 placement-free 分段隔离范围、STEP-106 T1/T2 Symbol 与 Annotation 边界的真实验收。
## 三、来自 NavSea 的问题证据(仅作为输入,不由本任务修改 NavSea)
FZG1-2 / Android 13 / V23 当前 APK,NavSea 采用**两段 R-C**:
- R0:RETAINED,31 层,`background` 至 `place-labels-land-ja`,包含有 Placement 能力的 Symbol;
- C1:CONTINUOUS,82 层,`rn-weather-color` 至 `chartDangerScanCenterLineLayer`,包含天气/粒子和其他业务层。
固定同一风场、同一 map 生命周期,连续约 15.24 秒,`mapReactTag=864`、`generation=4`、`configRevision=1`、`topologyRevision=8`、113 层均不变:
- 风场期间进程 CPU 样本均值约 98.6% / 99.1%,关闭天气后约 4%~8%;
- `fastPathHit Δ0`、`fastPathMiss Δ452`、`fullCpuFrame Δ453`;
- R0 `build Δ0`、`reuse Δ0`、`invalidation Δ453`、`fallbackCount Δ453`、texture/composite create `Δ0`;
- 两端 `valid=true`、`fallback=false`、`sourceReady=true`、`sourceStable=true`、`placementChanged=true`、`placementGatePassed=false`、`cache.supported=false`、`cacheState=fallback`、`lastInvalidationReason=static-placement`;
- `mainBufferExecutionMode=mixed`、`globalLiveFallbackReason=none`、`fastPathFallbackReason=cache-invalid`。
**这些是症状,不代表已证明 C1 导致 R0 Placement 改变。** 此外,历史较早的 V23 短时快照曾观察到 `placementChanged=false` 但 R0 记录仍为 `static-placement`,需要区分“当前门槛值”和“上次失效原因/诊断时序”,避免误读。
Mac 粒子团队如无法访问 Windows 原始快照,可依据上述冻结事实做 Native 只读审计;缺失证据须列为“需要后续验证”,不得自行编造 NavSea 图层或运行结果。
## 四、必须回答的调查问题
### Q1:Placement 的产生与传播
从真实 V23 Native 代码绘制完整的因果链:
- 触发 RenderTree / Placement 计算的入口与时机是什么?
- `placementChanged`、`symbolFadeChange`、`placementTransitionActive`、`crossSourceCollisions` 分别表示**当前帧/上帧/全局/局部**的什么事实?何时清零或稳定?
- Source/Layer 的 layout、tile、visibility、camera、animation 等变化,分别可能影响哪些 Placement 状态?
- R0 以外的 C1 Symbol/Annotation/业务层变化,**在哪些条件下**可能影响 R0 的符号碰撞与最终视觉?
- 如果 RenderTree 每次都重新算 Placement 但画面没有变化,如何区分“执行过计算”和“R0 最终结果发生变化”?
### Q2:现有 R0 Gate 的作用域
调查 V23 实际调用链及数据结构,至少解释:
- R0 结构性 `placementCapable` 如何判定,与动态 `needsPlacement()` 有何区别;
- R0 `placementGatePassed` 如何使用全局 `placementChanged`、fade、transition;
- R0 `cache.supported`、`static-placement`、`actualExecution=live` 的完整因果关系;
- R0 的 source-local mutation、segment fingerprint、retained revision 与 placement gate 是否真正各自独立;
- STEP-102 对 **placement-free** R 的隔离已解决什么、**placement-bearing** R 仍未解决什么;
- `lastInvalidationReason` 与当前失败条件是否可能属于不同采样/缓存生命周期。
### Q3:能否证明“R0 不受 C1 变化影响”
针对以下反事实场景逐项作出有证据的判断:
1. R0 含静态地名,C1 只有普通非 Symbol 动画/颜色/粒子更新;
2. R0 含静态地名,C1 的 Symbol 内容变化,但不同 Source 且跨源碰撞关闭;
3. R0 含静态地名,C1 的 Symbol 内容变化且跨源碰撞开启;
4. R0 Symbol 自身 layout/tile/visibility/zoom 发生变化;
5. Camera/pan/zoom/rotation/pitch 变化;
6. Symbol Fade/Placement Transition 活跃或刚结束;
7. C1 有内部自动插入的 Annotation Symbol(参照 STEP-106)。
每行必须分别给出:
- “谁触发 Placement”能否识别;
- “R0 最终 Placement/可见性是否改变”能否独立证明;
- **允许 R0 Reuse 的必要安全条件**;
- 当前 V23 是否具备这种判定能力及证据来源;
- 无法证明时为什么必须继续 fail-closed。
严禁把“不同 Segment”简单等同于“碰撞和 Placement 相互独立”。
### Q4:对 Fast Path 的影响(仅限前置依赖)
解释“R0 缓存有效”是否为 Fast Path 的必要条件但**非充分条件**。只读标出 R0 Gate 与单 C / unsupported continuous layer / revision / pending changes 等后续 Gate 的先后关系。不要扩大成 C1 全图层性能调查,不实施多 C 优化。
## 五、只读检查范围与方法
允许读取:
- 当前 Particle Core 的 Renderer、RenderOrchestrator、PlacementController、RenderTree、Symbol/Collision、Retained Cache、RenderSegment Plan、Diagnostics 与 Fast Path 相关源码;
- V23 tag 与当前 `particle/main` 的只读 `git show`、`git diff`、`git log`;
- STEP-090/102/106 等历史设计、报告、设备验证结果;
- 现有 C++ 定向测试源码与 Validation 场景源码(只读,不执行、不修改)。
建议从当前实际路径 `src/mln/renderer/` 与相关 Shared Core 源码定位,**不得沿用旧 STEP 中过时的路径假设**。
每一条重要结论必须给出:**文件路径 + 类/函数或结构名称 + 适当行范围或 commit + 相关调用顺序**。核心论断不得只引用历史文档或本任务描述。
允许纯只读 Git/文本搜索,不要求也不允许额外建立实验分支、修改 Native/JS/Kotlin/测试代码、插桩、添加日志、运行 Jest、运行测试、Gradle、CMake/Ninja/Bazel、ADB 或重新安装 App。不得改 NavSea、Integration 或正式 release/tag。
## 六、最终必须提供的结论矩阵
**每个问题必须选择明确判定**,而不是只罗列建议:
| 维度 | 可选结论 |
|---|---|
| 能否定位 Placement 事件来源 | 现有数据可区分 / 现有数据不足 / 条件性可区分 |
| 能否证明 R0 的实际视觉结果未变化 | 已能证明 / 需扩展内部安全证据 / 不能按段安全证明 |
| 现有 Gate 是什么性质 | 正确的设计保守限制 / 明确可证的过度阻塞 Bug / 混合 / 证据不足 |
| 缓存失效的主要责任边界 | Native Placement 数据模型 / Native Gate 判定 / 当前跨段碰撞语义 / Host Style 组合 / 多因素 / 未确定 |
| 可选后续方向 | 无需代码调整 / Native 内部诊断补强 / Native 内部安全判据设计 / 宿主层序方案需协调 / 需真机对照实验 |
如果判断是 Bug,必须指出**违背了哪项既有语义/可复现的状态不变量**;如果判断是安全限制,必须指出**缺少何种证明才不能放宽**。如有多个阻塞项,按必要性和因果顺序排序,避免“修第一个之后才继续猜下一个”。
## 七、报告与交接产物
只允许新建/更新以下本 Step 文档(不改现有源码):
1. `docs/20_调查/R0_Placement跨段归因与缓存安全边界调查_v1.md`(主报告);
2. `docs/50_Step记录/STEP-110_R0_Placement跨段归因只读调查_v1.md`(状态及完成项);
3. `docs/报告/STEP-110_R0_Placement跨段归因只读调查_Codex完成报告_v1.md`(执行报告);
4. `粒子开发记录.md`(简短追加式记录)。
调查主报告必备章节:
- V23 tag / 当前 main 基线对照,说明是否有相关行为变化;
- Q1~Q4 的逐条答案及代码证据;
- **数据流/状态流**:Placement 事件 → 全局/局部状态 → R0 安全判定 → Cache Build/Reuse/Fallback → Fast Path;
- 七类反事实场景矩阵,明确跨源碰撞、Fade、Annotation 的影响;
- 已确定事实、假设、证据不足之处三栏;
- 结论矩阵、根因优先级、最小下一步验证建议;
- 若需调整公共 Contract / API,则仅提出 Integration 阻塞,不自行实施。
完成报告必须逐项按“**已完成 / 未完成 / 无法验证**”回答任务项并说明原因;记录实际检查范围、当前 branch/HEAD、Git 提交及推送信息。不得用“源码已读”代替代码证据。
**状态判定**:只读因果审计证据完整才可标为 `调查 PASS(不等于修复 PASS)`;否则 `PARTIAL/BLOCKED` 并列出缺失项。严禁声称“Native 已修复”、“R0 已缓存”或“CPU 已下降”。
## 八、Git 收尾与执行界限
- 本任务**只负责调查**。Codex/粒子项目未经用户后续明确授权,不得实施任何缓存、Placement、Fast Path 逻辑修改。
- 完成相关文档后,仅提交本 Step 新增报告、Step 记录及追加的开发记录;保留所有无关 dirty/untracked 文件,不得强制清理。
- 将本任务相关文档 scoped commit 到正式主线 `particle/main` 并 push 到公开远端 `origin/particle/main`。禁止 force push、改写历史、推送 upstream 或增加无关文件。
- 完成报告必须给出**实际 commit hash、commit message、push 结果、远端分支**;push 失败不得宣称完成,应明确报告阻塞。
- 本 MD 是交给粒子项目执行的**待办合同**。写入任务书本身不代表调查已执行、已验证或已得出结论。
- 本任务不包含 Jest,不涉及原生编译、测试执行或 Android 设备操作。
## 九、停止条件
遇到以下情形须停留在调查层,提交 PARTIAL/BLOCKED 事实,不得扩大实现:
1. V23 tag 与当前代码差异导致无法确认实际执行版本;
2. Placement 影响关系必须依赖运行时数据,静态源码无法证明;
3. 证明缓存安全需新增 per-segment collision/placement fingerprint 或改写 Core 算法;
4. 需要改变对外 Native/RN 公共接口或跨项目责任;
5. 需要 NavSea 配合修改 Style、增加调试或重新编译/测试;
6. 所需证据缺失或与 STEP-090/102/106 的历史结论相冲突。
出现以上情况,在报告中给出**最小下一步任务建议**,等待用户/Integration 决定;不得在本任务中直接实施。

View File

@@ -0,0 +1,41 @@
# STEP-111 R 段 Placement 结果等价与独立刷新可行性只读调查 — Codex 任务 v1
## 一、用户目标
用户希望 Native 对 R-C-R-C-R-C(简称 RCRCRC)中的每一个 RETAINED R 段单独决定是否更新。若该段本身最终视觉输出未变化,不应仅因另一段 Symbol/Placement/Fade 全局状态更新而被动重建;确实受跨段碰撞影响的 R 段必须正确更新,不能破坏文字、图标或层序。此任务是下一步架构/可实现性调查,不是授权实施优化。
**核心区别**:A. 哪个 Layer/Source/Segment 触发重新计算;B. 全图 Placement 后每一个 R 段的最终 Symbol 视觉结果是否改变;C. Retained R 段缓存能否安全复用;D. 是否可进一步省略 R 段 prepare/placement 计算。A≠B、B≠C、C≠D。不要把各 R 分段直接当作 Collision Domain,也不要承诺每段无需参与全图 Placement 计算。
## 二、正式边界与必读
- Mac /Users/tei/dev/MapLibreParticleLab,particle/main,origin/particle/main;执行时重新记录 HEAD、branch、dirty、远端基线;保留未跟踪 STEP-110 任务书。
- 先读仓库 AGENTS.md、PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 和全部现行总则;读 STEP-110 主报告和 Codex 完成报告,以及 STEP-102、106、109、Fast Path 相关历史证据;以当前源码事实为准。
- 先检查 /Users/tei/dev/navsea-particle-integration 的 main 与 origin/main 同步状态,读取其 README、WORKFLOW、TEAM_MODEL、contracts、rfc、adr、status、compatibility;已有跨项目结论优先。本 Step 只讨论 Shared Core Native,不直接修改 Integration/NavSea/Android/iOS。
- STEP-110 已证明:PlacementController 仍是整图语义;R 的 placementCapable=true 时 Gate 消费 global placementChanged、symbolFadeChange、placementTransitionActive;跨 Source Collision 可能影响 R0,source≠segment。STEP-102/106 只验收过 placement-free R 的安全复用。本 Step 不能简单重复这些事实,重点给出**新增的判等/判无变化能力**具体可行性。
## 三、只读调查工作项
1. **从源码定位可实现性**:追踪 CrossTileSymbolIndex、Placement::placeLayers/commit、CollisionIndex/CollisionGroups、symbol buckets、placed icon/text、opacity/fade、RenderTree、RenderLayer、RenderSegment/Retained Cache 的真实控制与数据所有权。指出计算结束何处可以读取每一个 R 的最终 Placement 输出,是否已有稳定 ID、可见性/位置/opacity;写明路径、函数、行号和生命周期。辨别层间/Source 间/段间影响。
2. **定义每段结果等价契约候选**:提出具体 per-R PlacementResultKey/Signature/Revision 候选字段(Symbol 实例稳定 ID、tile/source/bucket generation、placement result、collision visible/hidden、text/icon anchor/offset、paint opacity/fade/transition、zoom/camera、glyph/image atlas、style/layer order、render target boundary 等)。逐项解释哪些是 Placement 专属,哪些已有其他 cache gate 覆盖,哪些必须动态比较,哪些不需要加入,避免对每个 Symbol 每帧做过度 O(N) 或漏比较;比较发生于何时、如何处理首次帧、transition、reset、missing data。
3. **RCRCRC 示例与判定表**:至少 R0/C0/R1/C1/R2/C2,其中 R0/R1/R2 都有 Symbol。分别分析:
- C1 仅粒子/颜色动画,R 均无可见变化;
- C1 Symbol 变动且只影响 R2;
- C1 Symbol 变动,碰撞也影响 R0/R1;
- R1 自己改变但 R0/R2 不变;
- 同一 Source 跨段、不同 Source 且 crossSourceCollisions=true/false;
- Annotation Symbol 自动插入,layer 顺序、camera/zoom/pitch、tile/glyph fade/opacity transition;
- 不能证明者 fail-closed。
结论表需区分“依然执行全局 Placement”、“该段仅避免 cache rebuild/composite 是否可复用”、“能否避免 render/prepare”、“Fast Path 仍被哪些其它 Gate 阻断”。
4. **方案对比与难度**:对 A 只加 tracing/per-R 指标(不改变判定)、B 新增 per-R result fingerprint 但 shadow compare(仅记录真假)、C 利用已证明等价替换 global-only Symbol Gate 允许单段 Reuse、D 深改 Placement/Collision 成分段增量计算,逐一评估前提、复杂度(1-5)、改动范围、CPU/GPU/内存开销、风险、能否只改 Shared Core、对 Android OpenGL/iOS Metal 的影响。不能因为理论上可 hash 就声称 hash 一定安全。
5. **执行与验收计划**:给出最小分阶段实现建议和真实可验证指标,包括 per-R signature stable/change、false invalidation、false reuse(严格 0)、build/reuse/fallback、视觉 A/B、Fade 长尾、CPU/frame、same frame draw ordering;指出先做诊断或原型是否需要新 Native 内部接口,哪些行为必须同时覆盖 Android/iOS。分别评估改善 retained cache 命中与改善整帧 CPU/Fast Path 的边界。
6. **明确推荐结论**:回答每个 R 段可否“自身未变就不刷新”、可做到的最小粒度、不能保证的情形、最佳第一阶段(原型)与中长期方案,给出具体源码位置和工程量区间;区分源码已证实 vs 推断 vs 必须运行时验证。
## 四、禁止项
调查只允许读取 Native/历史 Git/本机历史实验文件以及新增本 Step 文档。禁止修改任何 C++/JS/Kotlin/Swift/编译脚本/测试代码;禁止执行编译、链接、测试、Jest、Gradle、ADB、Bazel、CMake、设备操作;禁止更改 NavSea、Integration、公用 Contract/API、GSRL、release/Source Lock、发行 Tag;禁止放宽当前 fail-closed Gate。禁止重置/清除现有 dirty、禁止 force push。除非明确用户授权,不执行后续实施。
## 五、必做输出(实际生成)
1. docs/20_调查/STEP-111_R段Placement结果等价与独立刷新可行性调查_v1.md — 主报告:代码路径/行号、RCRCRC 场景矩阵、Signature 草案字段逐项依据与生命周期、可行方案 A-D 对比、复杂度与工程量估计、风险、可执行的后续 Step 拆分、源码已证/待验证标识。
2. docs/50_Step记录/STEP-111_R段Placement结果等价调查_v1.md — 简短中文 Step 记录,状态 PASS(调查完整)/PARTIAL/BLOCKED,记录未验证项与下一步。
3. docs/报告/STEP-111_R段Placement结果等价调查_Codex完成报告_v1.md — 完成报告:实际模型与 reasoning effort(仅凭运行会话证据)、读取资料、逐任务结果、调查起/终 HEAD、branch、dirty、文件修改、未运行构建和测试、检查命令、限制、Git/Push 状态如实记录。
4. 粒子开发记录.md — 本阶段简短追加(不要重写历史)。
Codex 仅负责内容调查和上述报告落盘,Git scoped commit/push/远端验证由 GPT 指挥 RDC 独立负责,完成报告不得预报未执行 Git 结果。原有未跟踪 STEP-110 任务文件不纳入提交。本任务书自身保持未跟踪,避免误提交。
## 六、验收与停止
只在调查结果有对应源码位置/函数/调用顺序,明确安全条件、未证实风险、工程阶段与复杂度对比时,记“调查 PASS”;如生产实际视觉等价无法仅通过源码证明,可记“调查 PASS / 运行验证未执行”,不得声称优化已通过。必要的实机状态/插桩超出只读范围时明确给出下一阶段最小需求,不得扩大当前范围。不得把历史 STEP-110 的 PARTIAL 当成本次已经实现结果。任务退出前向 GPT 汇报生成文件、最后状态和未完事项。

View File

@@ -0,0 +1,138 @@
# STEP-112:V23 粒子 Full Render 循环与 Symbol 缓存准入全面源码调查 — Codex 任务 v1
**状态:待执行|类型:全面只读源码调查|编写日期:2026-10-10**
## 一、用户目标与必须回答的问题
NavSea 的 FZG1 连续风场场景中,粒子约 30 FPS,Native Particle Fast Path 持续 0 hit,约 1,787~1,796 次 Full CPU Frame/分钟;粒子关闭对照约 83 次 Full CPU Frame/分钟。含 Symbol 的 R2 缓存长期失效,理由为 static-placement。用户要求由粒子系统全面分析 Native 源码,找出导致“粒子 Tick → Fast Path 失败 → Full Render → R2 仍无法建立缓存 → 下一次继续失败”的真实原因,并提出安全修复方向。本任务**不授权修复**。
必须明确回答:
1. 谁请求每帧重绘?粒子是否只是要求下一帧,而非要求 Full Render?什么时候 MapLibre 能进入 Idle?
2. Fast Path 全部准入条件的实际来源与执行顺序是什么?为什么 FZG1 的首个拒绝原因是 cache-invalid?
3. R2 为什么 Build=0,而不是建好后偶尔失效?static-placement 实际检查了什么?
4. placementChanged、symbolFadeChange、placementTransitionActive 是否在正常连续运行下形成难以通过的组合?原因来自时间机制、Transition 配置、Source,还是其它条件?
5. Full Frame 已经正确绘制 Symbol 时,什么条件下可以安全地建立缓存供后续 Particle Tick 复用?修复 R2 后还会遇到哪些独立阻塞?
**假设不是结论**:此前讨论认为 symbolFadeChange == 0 可能被误用为“Fade 已稳定”,这只是必须严谨验证或推翻的假设;不得直接断言为 Bug。
## 二、环境、权威文档和既有事实
- Particle 源码:Mac /Users/tei/dev/MapLibreParticleLab;执行时记录实际 branch/HEAD/dirty。编写时 branch=particle/main,HEAD=50ae48ad70,不能把该 HEAD 当执行时已固定版本。
- 按 AGENTS.md 顺序先读 PARTICLE_BASELINE.md、docs/00_项目规范/、docs/00_项目总则/README.md 及现行总则、本任务、STEP-102/106/109/110/111 相关调查与报告;以执行时源码事实为优先。
- 跨项目事实依照 /Users/tei/dev/navsea-particle-integration 的最新 COORD/RFC/ADR/Contract/Status。公共 API、Host/Native 职责改变只写提案,跨项目协调另立事项。
- 现有未跟踪 STEP-110 与 STEP-111 任务书不得移动、覆盖、代为提交。现有 STEP-110/111 调查属于 PARTIAL,不表示 per-R 安全复用已经实现。
- 执行模型建议 GPT-6 Sol Medium,实际完整模型与 reasoning effort 在开始前核实并报告;MD 不负责自动切换。提高推理档位须先停止说明理由、由用户确认。
### FZG1 现场证据(不等于逐帧因果证明)
| 指标(约 60 秒连续风场) | RC1 | RCRR_PREFIX | RC2 |
|---|---:|---:|---:|
| Full CPU Frame 增量 | 1792 | 1787 | 1796 |
| Fast Path Hit | 0 | 0 | 0 |
| R0 Reuse | 0 | 1787 | 0 |
| R2 Build/Reuse | — | 0/0 | — |
| R2 Invalidation/Fallback | — | 1787/1787 | — |
| R3 Reuse | — | 1755 | — |
| Fast 首个拒绝 | cache-invalid | cache-invalid | cache-invalid |
RCRR_PREFIX 分段:[0,13) R0;[13,33) C1;[33,107) R2;[107,113) R3。C1 **含普通 Symbol/Line,以及 index31 WeatherColor 与 index32 Particle**,并非纯粒子段。R2 含大量 Symbol。FZG1 末态记录 placementChanged=true、symbolFadeChange=0、placementTransitionActive=false,**只是快照不是每帧时间序列**。此前也观察到导航 Source Mutation 计数增长,不能未经证据归咎于它。Full CPU Frame 不等于所有 Layer 重绘,不等于 Placement 实际重算次数。
## 三、必须完成的源码分析 A:从粒子请求到 Full Render 的调度链
逐级核查:
- RenderRasterParticleLayer::isParticleAnimationActive / needsRepaint,SourceTimeline / V4Cadence / cadenceDue,粒子启动、暂停、无数据、隐藏时的返回条件。
- RenderOrchestrator::hasTransitions,RenderTreeParameters::needsRepaint,Map::Impl::onDidFinishRenderingFrame / onUpdate,RendererObserver、RendererFrontend/Backend、Renderer::render / tryRenderParticleFastFrame。
- 明确粒子 Tick、下一帧调度、Native Full 准备、GPU 粒子更新及 MapLibre Idle 的不同概念。检查 30 FPS 来自哪个实际 cadence,以及是否存在独立的 Placement 定时器强制 Full。
- 输出**可核查调用图**,带文件路径、函数、行号及关键条件;区别“必然调用”“在特定配置调用”和“只是假设”。
## 四、必须完成的源码分析 B:Fast Path 全条件审计
检查 src/mln/renderer/particle_fast_path.hpp 的 evaluateParticleFastPath 与 renderer_impl.cpp 的 tryRenderParticleFastFrame:
- Prepared RenderTree、Retained enabled、**所有 R 段** Cache valid、Build/Static Revision、Content Revision、Camera/Viewport、non-camera update signature、Style、Boundary/Topology/RangeConfig。
- staticStable、Placement transition、pending changes、non-particle transition/repaint、fading tiles、ParticleNeedsRepaint、still image、MapMode、Debug、Continuous 支持层、scene support、annotation mutation。
- **必须产生全 Gate 矩阵**:条件/来源函数与行号/通过条件/失败原因/是否被粒子 Tick 本身改变/是否在一次新 Full 后可能恢复/FZG1 已证实或未知。
- 确认短路返回首个错误是否掩盖 unsupported-continuous-layer、unsupported-scene 等后续失败;不得因只看见 cache-invalid 就宣布其他条件已通过。
- 比较 Full 与 Fast 路径的 CPU Prepare、RenderTree、GPU 绘制工作,避免将 Fast 解释成零 GPU render。
## 五、必须完成的源码分析 C:R2 缓存 Build/Invalidate 状态机
从图层结构到 GPU 缓存逐级核查:
- render_segment.hpp 的 renderSegmentHasPlacementCapableLayer / renderSegmentPlacementGatePassed;隐藏 Symbol 是否同样触发结构级保护。
- render_orchestrator.cpp 的 retainedRangeUsesPlacement / retainedRangeIsStable、Source/Tile/Glyph/Sprite/Annotation 的有效性及跨段 Collision 依赖。
- renderer_impl.cpp 的 retainedPlacementStable 全局 Scene Gate、每段 segmentPlacementGatePassed、segmentSupported、Segment fingerprint/source revision、segmentFallbackReason、缓存 evaluate 与合成控制。
- retained_underlay_cache.cpp 的 RetainedUnderlayCache::evaluate / invalidate / BuildPending / markBuildSucceeded / markBuildFailed / releaseResources;区分“首次未建立”“有效缓存因 Gate 被主动 invalidated”“底层资源损坏”“revision 变化”。
- 以 FZG1 的 R2 Build=0、Reuse=0、Fallback/Invalidation≈1787 为约束,写出完整状态转换和所有可支持或反驳的来源;解释为什么 Full 的正常绘制不自动等于缓存离屏纹理 Build 成功。
- 对 placementChanged=true 分清“发生 Placement 计算”与“R2 Symbol 最终可见性/位置/像素变化”。还须分析跨 Source/跨段 Collision、Fade、Shader opacity、Camera、异步资源所需安全边界。
## 六、必须完成的源码分析 D:Placement 和 Fade 真正语义
逐函数追踪 PlacementController::placementIsRecent / setPlacementStale / hasTransitions;Placement::getUpdatePeriod / commit / symbolFadeChange / hasTransitions;Style TransitionOptions;Symbol 在 OpenGL/Metal Shader 中如何消费 symbol_fade_change、opacity。
**必须分别分析两种运行配置**:
1. enablePlacementTransitions=true:默认 300ms 更新间隔、Symbol Bucket Added 可能导致 30ms override、Zoom 修正、Fade 从 0 到终态的数值变化、hasTransitions 与 setPlacementStale 的交互。
2. enablePlacementTransitions=false:placementIsRecent() 为什么可能持续返回 false、Placement 是否每次 Full 都重算、symbolFadeChange 实际取值、为何末态可能为 true/0/false。
针对 Gate **!placementChanged && symbolFadeChange == 0 && !placementTransitionActive**,逐行给出真值表与相应真实可达时刻:
- Placement 刚计算后;
- Placement 未重算但仍有淡入淡出;
- Fade 完全结束后;
- 关闭 Transitions;
- Symbol Bucket 增减、跨段碰撞等变化;
- Camera/Style/Source 不变、只有粒子的稳态。
回答:在正常运行下是否存在可用于 Build 的安全窗口?如果不存在,是哪一条造成的?全局 Scene Gate 与每段 Placement Gate 是否构成第二重锁死?FZG1 末态数据不能被机械套进默认 300ms 时间表。**绝不通过直接删除 Gate 或仅更改 ==0 比较符来宣布安全修复。**
## 七、必须完成的源码分析 E:反事实场景与因果闭环
对下列情况分别输出逐帧状态推演与 Fast Gate 判定(明确“已证明/条件推断/需设备验证”):
- **S1 纯粒子稳态**:Camera 不动、所有 Source/Tile/Style/Glyph/Sprite ready 且不变、无外部 Full 指令、粒子 30 FPS。能否从第一次正常 Full 构建 R2 缓存并进入 Fast?何时会卡死?
- **S2 粒子关闭**:没有粒子连续重绘,仅偶尔正常地图刷新。分析为何 Full CPU Frame 频率低,不能误判 Placement 从不执行。
- **S3 真实 Host Source Mutation**:导航相关 Source/Annotation 更新与 Tile 异步输入同时存在,量化其对 Revision、Cache Build、Full Frame 的理论影响;不能假定所有变化都来自粒子。
- **S4 不同布局**:原 RC/RCRR、实验 RCRR_PREFIX、理想保留图层顺序的 [0,13) R / [13,32) R / [32,33) C / [33,107) R / [107,113) R。检查 C 范围有普通 Symbol/Line 是否会触发 unsupported-continuous-layer;不能将最后一个仅为反事实的配置写成已实现。
- **S5 动态安全回退**:相机缩放/旋转、Tile/Glyph/Sprite 完成加载、Opacity Fade、跨段 Collision、Style/Layer 改变时,哪些 R 必须重建,哪些状态不能盲目复用。
务必清楚界定:“粒子导致下一帧”与“粒子本身让静态 R 内容变了”不等价;“需要 Full 准备”与“所有图层从零画一次”也不等价。
## 八、方案对比与建议(仅提交提案)
至少比较四类选项并给优先级:
- **A 全 Gate 与 R2 状态诊断**:一帧内采集所有 Gate 状态,而不是仅返回第一个 Miss;记录 Placement 配置、计算原因、Fade 值、各 R 的 Build/Reuse/Fallback/Invalidate,控制 tracing CPU 开销。
- **B Full Frame 后安全建立 Symbol R 缓存**:寻找 Placement、Opacity、GPU Shader、资源与 RenderTree 都满足安全条件的 Build 时机,打破可能的“永远无法首次 Build”自举问题。
- **C 更细粒度 per-R 结果等价**:与 STEP-111 对照,是否有必要做 per-R Signature/Revision 或可采用更轻量的生命周期证据;跨段 Collision 不能被忽略。
- **D 外部 Full 更新与 Particle Tick 分流**:用户要求外部明确 Full 指令到来即完整更新,不因数据相同去重/延迟;无外部指令时粒子优先 Fast,仍保留 Native 必要安全 Full 回退。核对目前内部/公共接口是否支持;涉及责任或公共语义必须由 Integration 正式协调,本任务不得私建 API。
每项必须列:是否解决首次 Build、是否能解决全局 Scene Gate 和 C 段 Gate、数据正确性、GPU/CPU/内存代价、需改文件、风险、复杂度 1~5、Android OpenGL / iOS Metal 一致性、最小验证方案。
**性能目标是 30 FPS 粒子与低频外部 Full 更新各司其职;2 Full + 28 Fast 是理想例子,不是当前 V23 已实现或零安全回退的承诺。** 不以降低粒子 FPS、删除海图 Symbol、移动一批普通静态 Layer 到 C、放宽 fail-closed 或忽略外部指令作为优化手段。
## 九、本轮权限、限制与停止条件
**仅执行全面只读调查**,允许阅读本仓库源码、历史 Git、已有 FZG1 数据、现有调研报告;允许写入本任务要求的调查文档、Step 记录、Codex 完成报告与对粒子开发记录的短条追加。不得修改 Native、Android/iOS、Shader、JS/TS、构建脚本、测试源码、Host/NavSea、Integration 契约、Style、Renderer Gate 或公共 API。
不执行编译、链接、打包、设备操作、发布或新的性能测试。本轮如无法通过源码证实最终触发频率,必须给出下一阶段**最小诊断插桩提案**,记录字段、调用点、采样窗口、FZG1 验收场景与预期判断方法;不可未经授权直接实施插桩。严禁清理其他 dirty/untracked、变更上游、强推、改历史 Step。无法证明安全则维持 fail-closed。
需要涉及跨项目职责/API 调整的提案,先输出 Interface Blocker,返回 Integration 决策。遇到重大事实冲突时报告并停止冲突部分,不得自行扩大修改范围。
## 十、必做输出(调查完成时必须真实落盘)
1. **主报告**:docs/20_调查/STEP-112_V23粒子FullRender循环与Symbol缓存准入全面源码调查_v1.md。必须包含:
- 完整粒子调度→Fast→Full 调用图、源码路径/函数/行号;
- Fast Path **全部 Gate** 对照矩阵(含短路掩盖条件);
- R2 Cache 的首次 Build、Invalidate、Fallback 状态机与实际决策链;
- enablePlacementTransitions 开/关及 Fade 时间/数值状态表;
- 原始、PREFIX 和理想单粒子 C 的分段对照;
- S1~S5 反事实和证据强度;
- A~D 方案风险/成本/工程改动/优先级及推荐的下一步;
- 不同问题分别标注“**源码证明** / **FZG1 实测** / **条件推断** / **仍需设备验证**”。
2. **阶段记录**:docs/50_Step记录/STEP-112_V23粒子FullRender与Symbol缓存调查_v1.md。写状态、目标、完成项、未完成、阻塞与下一步。
3. **Codex 完成报告**:docs/报告/STEP-112_V23粒子FullRender与Symbol缓存调查_Codex完成报告_v1.md。逐项按“已完成 / 未完成 / 无法验证”报告,必须解释未完成原因;记录实际模型/推理档位、Git 起终 HEAD、branch、dirty、变更文件、检查结果、未做构建/运行的事实、Git/Push 实际结果。
4. **粒子开发记录.md**:本阶段追加简短事实记录,不能更改历史。
## 十一、验收与 Git 收尾
只有 A~E 的关键问题有源码级证据、全部 Gate 有出处、root-cause 假设被验证或明确证伪、方案风险和最小验证路径充分时,才能写“源码调查 PASS”;**运行时未验证、Native 未修复、Fast Hit 未改善**必须明确列出,不能写实现/性能 PASS。没有决定性证据,结论应 PARTIAL,明确下一轮需要的最小运行时信息。
本任务研究完成后,对**本任务范围的文档变更**进行 scoped Git 提交并 push 到 origin/particle/main,核对远端 HEAD;不得纳入现有未跟踪 STEP-110/STEP-111 任务书或无关修改。依 AGENTS.md 的 Codex→RDC 职责分工,Codex 交付调查结果,RDC/获授权操作方独立完成 Git/Push 与核验;Push 未确认不得冒称 Git 收尾完成。记录真实 commit hash、message、远端分支与 push 结果。禁止 force push、upstream push。
**本任务结束条件:调研证据、报告与 Git 收尾;不得继续直接实施 Native 修复。**

Some files were not shown because too many files have changed in this diff Show More