Compare commits

..

179 Commits

Author SHA1 Message Date
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
tei
f47469d2ab test: integrate particle.10 online QBIN host
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 20:21:16 +09:00
tei
822663cd5d release: finalize unified particle.10
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 19:11:02 +09:00
tei
d5ce2006be fix: validate iOS release slice without pipefail false negative 2026-09-21 18:52:20 +09:00
tei
73bebcf827 release: prepare unified particle.10 2026-09-21 15:52:51 +09:00
tei
3f7ec99f85 fix: preserve canonical color semantics after QBIN decode 2026-09-21 15:52:22 +09:00
tei
a4cb727b16 docs: close STEP-087 particle.9 release
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 10:02:33 +09:00
tei
63047540cb release: finalize unified particle.9
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 10:01:40 +09:00
tei
0e8c87fe49 feat: add QBIN V3 decoder for particle.9 2026-09-21 08:03:39 +09:00
tei
b2eac3e0bf build: publish iOS device arm64 only 2026-09-21 08:03:39 +09:00
tei
5ed49c06f9 release: prepare unified particle.8 2026-09-21 08:03:39 +09:00
OpenAI Codex
c0bad3df6e docs: record STEP-086 continuous repaint investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-21 06:20:05 +08:00
tei
70f112ea27 fix: close STEP-085 V4 renderer efficiency
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 22:16:36 +09:00
tei
bc6b8dd84b fix: disable particle speed color by default
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 21:58:48 +09:00
tei
91d4859121 release: finalize unified particle.7
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-19 10:31:18 +09:00
tei
de935818d9 release: prepare unified particle.7 2026-09-19 07:52:16 +09:00
tei
8e807982b1 fix: decouple release from generated test host podfile 2026-09-19 07:52:00 +09:00
tei
fd5595ebbc build: add unified particle release pipeline 2026-09-19 07:50:31 +09:00
tei
9e20233288 docs: close STEP-083 ios RN validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 22:42:47 +09:00
tei
d8bb9f547b feat: validate ios RN particle metal path
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 22:21:00 +09:00
tei
bdd7f40f90 feat: extend ios particle field runtime parity facade
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 17:57:02 +09:00
tei
44af5e68b8 docs: close STEP-082 ios xcframework validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 17:05:45 +09:00
tei
136ed726cf fix: close ios xcframework packaging blockers 2026-09-18 17:03:05 +09:00
tei
dd9687f5ed docs: record STEP-082 ios arm64 build
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 09:16:14 +09:00
tei
1af5aab6f8 docs: record STEP-082 ios runtime distribution baseline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 09:14:56 +09:00
tei
d94f2bffd6 feat: add ios particle field runtime facade 2026-09-18 09:13:50 +09:00
tei
3e03949d34 docs: record STEP-081 final validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 07:59:31 +09:00
tei
f1c0f92b2b fix: close STEP-081 iOS Metal particle rendering
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-18 07:58:51 +09:00
tei
8fd517bac2 fix: validate STEP-081 ios device build
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-17 14:51:22 +09:00
tei
79c0ff89f1 feat: bring up particle renderer on ios metal
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-17 09:27:35 +09:00
OpenAI Codex
f23f4143b9 feat: connect QBIN V4 renderer and release particle.5
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-15 14:40:17 +08:00
OpenAI Codex
3896c461fa docs: define Codex and RDC working roles
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 10:26:29 +08:00
OpenAI Codex
13a3d9e1e6 docs: align QBIN V4 particle.3 project baseline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 10:03:37 +08:00
OpenAI Codex
de8d230b87 docs: record public AAR to RN Test Host validation flow
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-14 09:17:22 +08:00
OpenAI Codex
618ad59ab8 fix: align QBIN V4 Android particle.3 validation baseline 2026-09-14 09:16:44 +08:00
OpenAI Codex
8d49d943f3 chore: add Android RN build pipeline
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 21:31:28 +08:00
OpenAI Codex
d490578b97 docs: complete STEP-079 QBIN fixture handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 20:07:30 +08:00
OpenAI Codex
f1ea88b0ce feat: add V4 QBIN golden fixture test host 2026-09-13 20:05:59 +08:00
OpenAI Codex
a83ab9cc94 docs: record STEP-078 color binding revision
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 19:47:24 +08:00
OpenAI Codex
e02353820a fix: expose V4 velocity component color binding
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 19:46:53 +08:00
OpenAI Codex
dd1d701471 docs: finalize STEP-079 blocker record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:44:01 +08:00
OpenAI Codex
afcc74066f docs: record STEP-079 V4 fixture API blocker
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:43:35 +08:00
OpenAI Codex
2ea8f03064 docs: record STEP-078 guard audit delivery
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:15:39 +08:00
OpenAI Codex
75a974e775 fix: close STEP-078 V4 bridge guard audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 17:14:50 +08:00
OpenAI Codex
73779b89bc feat: complete STEP-078 V4 bridge
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 16:10:49 +08:00
OpenAI Codex
4b5192e224 docs: add STEP-077 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:26:09 +08:00
OpenAI Codex
e0088a5f08 docs: complete STEP-077 V4 bridge investigation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:25:34 +08:00
OpenAI Codex
0759a06104 test: close STEP-076 V4 host API audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 15:02:17 +08:00
OpenAI Codex
ae03d8bfd2 feat: wire V4 QBIN host API into RasterArraySource
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:43:51 +08:00
OpenAI Codex
b148fb6a0c docs: record STEP-075 v3 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:22:25 +08:00
OpenAI Codex
2645bd725f fix: close STEP-075 binding selector identity audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:21:41 +08:00
OpenAI Codex
653e6f2f15 docs: record STEP-075 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:11:07 +08:00
OpenAI Codex
90d05f596a fix: close STEP-075 projection identity audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 14:10:32 +08:00
OpenAI Codex
3616daec30 docs: record STEP-075 push result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 13:58:57 +08:00
OpenAI Codex
c465b804ce feat: add V4 field binding projection identities
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 13:58:26 +08:00
OpenAI Codex
31783f25a8 docs: finalize STEP-074 git record
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:03:11 +08:00
OpenAI Codex
bba99f8acf docs: record STEP-074 delivery
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:02:18 +08:00
OpenAI Codex
3274c11a66 test: finalize STEP-074 QBIN2 padding boundary
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 12:01:54 +08:00
OpenAI Codex
f4a495ff38 test: close STEP-074 QBIN2 decoder coverage
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:51:45 +08:00
OpenAI Codex
56b8dab90f fix: close STEP-074 QBIN2 decoder audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:26:13 +08:00
OpenAI Codex
83a84452f2 feat: add QBIN2 V4 encoded field ingress
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 11:05:57 +08:00
OpenAI Codex
b8a629b7b6 fix: close STEP-073 validator runner audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:32:45 +08:00
OpenAI Codex
0b66a5cd30 docs: record STEP-073 v3 audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:19:02 +08:00
OpenAI Codex
4b8d200831 fix: close STEP-073 QBIN2 V4 validator audit 2026-09-13 10:18:33 +08:00
OpenAI Codex
1a453a6623 docs: record STEP-073 audit result
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:02:13 +08:00
OpenAI Codex
d6a784a791 fix: correct STEP-073 QBIN2 V4 core contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 10:01:20 +08:00
OpenAI Codex
c3033eb5a4 docs: update STEP-073 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:45:07 +08:00
OpenAI Codex
3526e324ce fix: enforce validity descriptor class
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:44:43 +08:00
OpenAI Codex
6537996d05 docs: add STEP-073 completion report
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:43:34 +08:00
OpenAI Codex
0a190adff3 feat: add QBIN2 V4 generic field profile validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-13 09:42:41 +08:00
OpenAI Codex
a0aa367f30 fix: close V3-08 Android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 18:10:33 +08:00
OpenAI Codex
1e84de3dd5 docs: record STEP-072 android validation preparation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 12:43:49 +08:00
OpenAI Codex
11cf58d344 chore: prepare V3-08 android validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 12:42:49 +08:00
OpenAI Codex
dd356a277d docs: record STEP-071 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:52:34 +08:00
OpenAI Codex
a4bf6b7aea test: close V3 Linux freeze
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:51:34 +08:00
OpenAI Codex
fcc533dc95 fix: finalize STEP-070 fixture validation
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 11:22:44 +08:00
OpenAI Codex
8588e6fcb8 feat: add V3 field fixtures and RN regression host
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:56:53 +08:00
OpenAI Codex
3b01275983 docs: finalize STEP-069 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:02:13 +08:00
OpenAI Codex
12827489f4 docs: record STEP-069 v2 field ingress
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 09:01:20 +08:00
OpenAI Codex
148bf80fe4 feat: add v2 multi-plane field ingress 2026-09-11 09:00:29 +08:00
OpenAI Codex
9e9ea07bc7 docs: record STEP-068 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 08:28:35 +08:00
OpenAI Codex
f3c33c6727 feat: add direction particle advection
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 08:27:28 +08:00
OpenAI Codex
89c72554c5 docs: close STEP-067 scalar GPU renderer
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-11 07:05:46 +08:00
OpenAI Codex
06fcbef9cc docs: record STEP-067 scalar GPU renderer
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:42:19 +08:00
OpenAI Codex
09ead7f201 renderer: add generic scalar field color path 2026-09-10 22:41:55 +08:00
OpenAI Codex
9dabb95e44 docs: close STEP-066 task handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:20:34 +08:00
OpenAI Codex
9a09278a28 docs: record STEP-066 push handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:16:54 +08:00
OpenAI Codex
b366a2d161 docs: record STEP-066 core contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 22:16:00 +08:00
OpenAI Codex
6d1f3e051c core: preserve fixture times in generic snapshots 2026-09-10 22:15:44 +08:00
OpenAI Codex
379c9ed0e4 core: add scalar vector field snapshots 2026-09-10 22:12:17 +08:00
OpenAI Codex
d23ae2079d docs: record STEP-065 git handoff
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 21:54:26 +08:00
OpenAI Codex
44999edb25 docs: freeze V3 scalar vector field contract
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 21:53:56 +08:00
OpenAI Codex
2e3ab26275 docs: close STEP-064 RN release
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
2026-09-10 09:40:37 +08:00
480 changed files with 49606 additions and 1269 deletions

1
.gitignore vendored
View File

@@ -13,6 +13,7 @@
/platform/android/build
/platform/android/MapLibreAndroid/src/main/assets/sdk_versions/com.mapbox.mapboxsdk
/platform/android/MapLibreAndroid/android-binaries-*.zip
/artifacts/maplibre-particle/
*.code-workspace
local.properties

153
AGENTS.md
View File

@@ -1,11 +1,154 @@
# MapLibreParticleLab — Codex 总规则
# MapLibreParticleLab — Codex / ChatGPT RDC 总规则
任何修改前必须阅读 `PARTICLE_BASELINE.md`、`docs/00_项目规范/`(包括官方基线来源)、当前 Step 和当前任务。
本文件是 Codex 与 ChatGPT Remote Desktop Commander(RDC)进入本仓库后的第一入口。
## 1. 开始工作前必须读取
任何修改、构建、Git、发布、同步或设备操作前,必须先读取:
1. 根目录 `AGENTS.md`(本文件);
2. 根目录 `PARTICLE_BASELINE.md`;
3. `docs/00_项目规范/`(包括官方基线来源);
4. `docs/00_项目总则/README.md`;
5. `docs/00_项目总则/` 中 README 标记的全部当前有效总则;
6. `docs/00_项目总则/当前基线与开发路线_v2.md`(或 README 指定的后续当前版本);
7. 当前 Step / 当前任务,以及与本任务直接相关的当前设计、测试或发布文档。
不得只依赖聊天记忆、旧窗口摘要或历史 Step 直接修改仓库。
## 2. RDC 启动规则
ChatGPT 使用 RDC 对本仓库进行实际操作时,必须把上面的阅读步骤作为固定启动流程。RDC 本身不会自动读取 `AGENTS.md`,因此由 ChatGPT 在第一次写文件、执行构建、Git、发布或设备命令前主动完成。
开始实际修改前至少确认:
- 当前机器/设备;
- 仓库绝对路径;
- 当前 branch;
- 当前 HEAD;
- `git status`;
- 与任务有关的当前版本号/发布坐标。
## 3. 冲突与事实优先级
若文档与代码/构建事实冲突,不得自行猜测或用旧规则覆盖当前实现。按以下顺序处理:
1. Git 当前代码与实际构建/运行结果 = 实现事实;
2. 当前基线与项目总则 = 设计与流程意图;
3. 当前 Step/任务 = 本轮工作合同;
4. 历史 Step/旧验收文档 = 历史证据,不自动代表当前流程。
发现冲突时先报告并限定修改范围;除非任务明确要求,不重写历史文档来伪装一致。
## 4. 项目固定边界
项目目标:在 MapLibre Native Core 中 clean-room 独立实现 Raster/Vector Field Particle,支持 Android OpenGL/Vulkan 与后续 iOS Metal。
固定边界:正式实现不使用 React Native/WebView/外部 Overlay;粒子模块高度隔离;官方原文件只做薄接入;Android/iOS 共用 C++ Core;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 且共用 Land/Ocean Mask;第一阶段只用离线 fixture;不得复制 Mapbox 新源码或 Windy APK 内代码。
固定边界:正式实现不使用 React Native/WebView/外部 Overlay;粒子模块高度隔离;官方原文件只做薄接入;Android/iOS 共用 C++ Core;WeatherColorLayer 与 RasterParticleLayer 分离;Wind=GLOBAL;Wave/Tide/Current=OCEAN_ONLY 且共用 Land/Ocean Mask;不得复制 Mapbox 新源码或 Windy APK 内代码。
每一阶段必须有 `docs/50_Step记录/STEP-xxx_*.md`。完成报告固定写入 `docs/报告/STEP-xxx_<主题>_Codex完成报告_vN.md`。
Host/NavSea 负责 provider、HTTP、manifest、下载、缓存和业务调度;MapLibre Native 从宿主提交的 canonical/encoded field 开始负责校验、解码、Runtime Field Window 与 GPU 渲染。
修改结束必须按构建规范验证、更新 Step、追加 `粒子开发记录.md`、生成中文完成报告,并明确所有未完成/阻塞。
## 5. Codex 与 RDC 的职责分工
### Codex 主要负责
- 大规模源码修改与跨文件重构;
- 新功能实现、接口调整、批量工程化修改;
- 按 Codex 任务书完成明确范围内的代码与文档变更;
- 需要较长连续编辑、依赖全仓上下文的实现工作。
Codex 的核心角色是“实现者”,不作为其自身修改结果的唯一最终验收者。
### RDC 主要负责
- 编译、链接、codegen、运行测试程序与测试命令;
- AAR/APK/npm tgz 等 artifact 的构建、检查、发布与下载;
- 公网发布后的回读、SHA-256/版本/文件内容验证;
- Win11、Linux、模拟器、ADB、Metro、安装、启动和运行态检查;
- 日志、进程、端口、文件系统、Git 状态与构建产物审计;
- 对 Codex 完成结果做独立代码/配置/文档/构建审计;
- 小范围、事实明确、风险可控的修正。
## 6. Codex → RDC 固定切换流程
默认工作流:
1. ChatGPT/用户先明确设计边界与任务目标;
2. 需要大规模实现时交给 Codex;
3. Codex 完成任务书范围后停止,不继续承担最终环境验收;
4. 切换到 RDC,重新读取 `AGENTS.md`、当前基线、Codex 交付文件和 Git diff;
5. RDC 做独立审计,并负责实际编译、测试、artifact、发布、下载、安装、Metro/ADB/模拟器和日志验证;
6. 若审计发现小型明确问题,可由 RDC 直接修正并重新验证;
7. 若发现架构性问题、跨模块大改或修改范围明显扩大,停止 RDC 扩改,重新生成/调整 Codex 任务再进入实现;
8. 最终以 RDC 实际环境验证结果 + Git 当前代码作为验收事实。
禁止默认让 Codex 同时承担“大规模实现 + 唯一审计 + 唯一最终环境验证”三种角色。
## 7. 记录与收尾
每一阶段必须有 `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

@@ -180,6 +180,12 @@ list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/uniform_buffer.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/gfx/vertex_attribute.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_target.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/particle_fast_path.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_cache.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_cache.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/retained_underlay_runtime_state.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_segment_plan.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/render_segment_json.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/change_request.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/layer_group.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/renderer/tile_layer_group.cpp
@@ -213,12 +219,28 @@ list(APPEND SRC_FILES
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/offline_fixture_particle_adapter.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/offline_fixture_particle_adapter.hpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_data_adapter.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_runtime.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_binding.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_projection_materialization.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_profile_validator.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/field_profile_contract_json.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/ingress/qbin_v2_decoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/ingress/qbin_v3_decoder.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/particle/field/local_particle_field_feed.cpp
${PROJECT_SOURCE_DIR}/src/mbgl/shaders/shader_program_base.cpp
)
list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_data_adapter.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_contract.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_validator.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_profile_contract_json.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_runtime.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/field_binding.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/renderer_debug_counters.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/qbin_v2_decoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/qbin_v3_decoder.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/ingress/encoded_field_transport.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/particle/field/local_particle_field_feed.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/actor/actor_ref.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/actor/actor.hpp
@@ -289,6 +311,8 @@ list(APPEND INCLUDE_FILES
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_observer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer_state.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/renderer.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/retained_underlay.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/renderer/render_segment.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/program_parameters.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/shaders/shader_source.hpp
${PROJECT_SOURCE_DIR}/include/mbgl/storage/database_file_source.hpp

View File

@@ -64,7 +64,6 @@ MLN_GENERATED_STYLE_SOURCE = [
"src/mbgl/style/layers/color_relief_layer.cpp",
"src/mbgl/style/layers/background_layer_properties.cpp",
"src/mbgl/style/layers/background_layer.cpp",
"src/mbgl/style/layers/particle_validation_layer.cpp",
"src/mbgl/style/layers/location_indicator_layer_properties.cpp",
"src/mbgl/style/layers/location_indicator_layer.cpp",
"src/mbgl/style/light.cpp",
@@ -100,6 +99,7 @@ MLN_GENERATED_OPENGL_SHADER_HEADERS = [
"include/mbgl/shaders/gl/fill_extrusion_pattern_instanced.hpp",
"include/mbgl/shaders/gl/heatmap.hpp",
"include/mbgl/shaders/gl/heatmap_texture.hpp",
"include/mbgl/shaders/gl/particle_gpu_validation.hpp",
"include/mbgl/shaders/gl/hillshade_prepare.hpp",
"include/mbgl/shaders/gl/hillshade.hpp",
"include/mbgl/shaders/gl/color_relief.hpp",
@@ -164,6 +164,8 @@ MLN_CORE_SOURCE = [
"src/mbgl/gfx/vertex_vector.hpp",
"src/mbgl/layermanager/background_layer_factory.cpp",
"src/mbgl/layermanager/particle_validation_layer_factory.cpp",
"src/mbgl/layermanager/raster_particle_layer_factory.cpp",
"src/mbgl/layermanager/weather_color_layer_factory.cpp",
"src/mbgl/layermanager/circle_layer_factory.cpp",
"src/mbgl/layermanager/custom_layer_factory.cpp",
"src/mbgl/layermanager/fill_extrusion_layer_factory.cpp",
@@ -203,6 +205,17 @@ MLN_CORE_SOURCE = [
"src/mbgl/map/zoom_history.hpp",
"src/mbgl/math/log2.cpp",
"src/mbgl/platform/settings.cpp",
"src/mbgl/particle/field/offline_fixture_particle_adapter.cpp",
"src/mbgl/particle/field/offline_fixture_particle_adapter.hpp",
"src/mbgl/particle/field/field_data_adapter.cpp",
"src/mbgl/particle/field/field_runtime.cpp",
"src/mbgl/particle/field/field_binding.cpp",
"src/mbgl/particle/field/field_projection_materialization.cpp",
"src/mbgl/particle/field/field_profile_validator.cpp",
"src/mbgl/particle/field/field_profile_contract_json.cpp",
"src/mbgl/particle/ingress/qbin_v2_decoder.cpp",
"src/mbgl/particle/ingress/qbin_v3_decoder.cpp",
"src/mbgl/particle/field/local_particle_field_feed.cpp",
"src/mbgl/renderer/backend_scope.cpp",
"src/mbgl/renderer/bucket.hpp",
"src/mbgl/renderer/bucket_parameters.cpp",
@@ -237,6 +250,9 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/layers/render_background_layer.hpp",
"src/mbgl/renderer/layers/render_particle_validation_layer.cpp",
"src/mbgl/renderer/layers/render_particle_validation_layer.hpp",
"src/mbgl/renderer/layers/render_raster_particle_layer.hpp",
"src/mbgl/renderer/layers/render_weather_color_layer.cpp",
"src/mbgl/renderer/layers/render_weather_color_layer.hpp",
"src/mbgl/renderer/layers/render_circle_layer.cpp",
"src/mbgl/renderer/layers/render_circle_layer.hpp",
"src/mbgl/renderer/layers/render_custom_layer.cpp",
@@ -263,6 +279,8 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/paint_parameters.hpp",
"src/mbgl/renderer/paint_property_binder.hpp",
"src/mbgl/renderer/paint_property_statistics.hpp",
"src/mbgl/renderer/particle_viewport_reseed.hpp",
"src/mbgl/renderer/particle_fast_path.hpp",
"src/mbgl/renderer/pattern_atlas.cpp",
"src/mbgl/renderer/pattern_atlas.hpp",
"src/mbgl/renderer/possibly_evaluated_property_value.hpp",
@@ -274,6 +292,9 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/render_light.hpp",
"src/mbgl/renderer/render_orchestrator.cpp",
"src/mbgl/renderer/render_orchestrator.hpp",
"src/mbgl/renderer/continuous_layer_range.hpp",
"src/mbgl/renderer/render_segment_plan.hpp",
"src/mbgl/renderer/render_segment_json.hpp",
"src/mbgl/renderer/render_pass.hpp",
"src/mbgl/renderer/render_source.cpp",
"src/mbgl/renderer/render_source.hpp",
@@ -287,6 +308,9 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/renderer_impl.cpp",
"src/mbgl/renderer/renderer_impl.hpp",
"src/mbgl/renderer/renderer_state.cpp",
"src/mbgl/renderer/retained_underlay_cache.cpp",
"src/mbgl/renderer/retained_underlay_cache.hpp",
"src/mbgl/renderer/retained_underlay_runtime_state.hpp",
"src/mbgl/renderer/sources/render_custom_geometry_source.cpp",
"src/mbgl/renderer/sources/render_custom_geometry_source.hpp",
"src/mbgl/renderer/sources/render_geojson_source.cpp",
@@ -297,6 +321,7 @@ MLN_CORE_SOURCE = [
"src/mbgl/renderer/sources/render_raster_dem_source.hpp",
"src/mbgl/renderer/sources/render_raster_source.cpp",
"src/mbgl/renderer/sources/render_raster_source.hpp",
"src/mbgl/renderer/sources/render_raster_array_source.hpp",
"src/mbgl/renderer/sources/render_tile_source.cpp",
"src/mbgl/renderer/sources/render_tile_source.hpp",
"src/mbgl/renderer/sources/render_vector_source.cpp",
@@ -410,7 +435,13 @@ MLN_CORE_SOURCE = [
"src/mbgl/style/layer_observer.hpp",
"src/mbgl/style/layers/background_layer_impl.cpp",
"src/mbgl/style/layers/background_layer_impl.hpp",
"src/mbgl/style/layers/particle_validation_layer.cpp",
"src/mbgl/style/layers/particle_validation_layer_impl.hpp",
"src/mbgl/style/layers/particle_render_layer_properties.hpp",
"src/mbgl/style/layers/raster_particle_layer.cpp",
"src/mbgl/style/layers/raster_particle_layer_impl.hpp",
"src/mbgl/style/layers/weather_color_layer.cpp",
"src/mbgl/style/layers/weather_color_layer_impl.hpp",
"src/mbgl/style/layers/circle_layer_impl.cpp",
"src/mbgl/style/layers/circle_layer_impl.hpp",
"src/mbgl/style/layers/custom_layer.cpp",
@@ -462,6 +493,8 @@ MLN_CORE_SOURCE = [
"src/mbgl/style/sources/raster_dem_source.cpp",
"src/mbgl/style/sources/tile_source.cpp",
"src/mbgl/style/sources/raster_source.cpp",
"src/mbgl/style/sources/raster_array_source.cpp",
"src/mbgl/style/sources/raster_array_source_impl.hpp",
"src/mbgl/style/sources/tile_source_impl.cpp",
"src/mbgl/style/sources/tile_source_impl.hpp",
"src/mbgl/style/sources/vector_source.cpp",
@@ -628,6 +661,20 @@ MLN_CORE_SOURCE = [
})
MLN_CORE_HEADERS = [
"include/mbgl/particle/field/field_data_adapter.hpp",
"include/mbgl/particle/field/field_profile_contract.hpp",
"include/mbgl/particle/field/field_profile_validator.hpp",
"include/mbgl/particle/field/field_profile_contract_json.hpp",
"include/mbgl/particle/field/field_projection_materialization.hpp",
"include/mbgl/particle/field/field_runtime.hpp",
"include/mbgl/particle/field/particle_gpu_math.hpp",
"include/mbgl/particle/field/field_binding.hpp",
"include/mbgl/particle/renderer_debug_counters.hpp",
"include/mbgl/renderer/layers/weather_color_texture_lifecycle.hpp",
"include/mbgl/particle/ingress/qbin_v2_decoder.hpp",
"include/mbgl/particle/ingress/qbin_v3_decoder.hpp",
"include/mbgl/particle/ingress/encoded_field_transport.hpp",
"include/mbgl/particle/field/local_particle_field_feed.hpp",
"include/mbgl/gfx/command_encoder.hpp",
"include/mbgl/gfx/debug_group.hpp",
"include/mbgl/gfx/context.hpp",
@@ -666,6 +713,8 @@ MLN_CORE_HEADERS = [
"include/mbgl/i18n/number_format.hpp",
"include/mbgl/layermanager/background_layer_factory.hpp",
"include/mbgl/layermanager/particle_validation_layer_factory.hpp",
"include/mbgl/layermanager/raster_particle_layer_factory.hpp",
"include/mbgl/layermanager/weather_color_layer_factory.hpp",
"include/mbgl/layermanager/circle_layer_factory.hpp",
"include/mbgl/layermanager/custom_layer_factory.hpp",
"include/mbgl/layermanager/fill_extrusion_layer_factory.hpp",
@@ -680,6 +729,10 @@ MLN_CORE_HEADERS = [
"include/mbgl/layermanager/raster_layer_factory.hpp",
"include/mbgl/layermanager/symbol_layer_factory.hpp",
"include/mbgl/style/layers/particle_validation_layer.hpp",
"include/mbgl/style/layers/raster_particle_layer.hpp",
"include/mbgl/style/layers/weather_color_layer.hpp",
"include/mbgl/style/layers/weather_color_stop.hpp",
"include/mbgl/style/sources/raster_array_source.hpp",
"include/mbgl/map/bound_options.hpp",
"include/mbgl/map/camera.hpp",
"include/mbgl/map/change.hpp",
@@ -702,6 +755,8 @@ MLN_CORE_HEADERS = [
"include/mbgl/renderer/renderer_frontend.hpp",
"include/mbgl/renderer/renderer_observer.hpp",
"include/mbgl/renderer/renderer_state.hpp",
"include/mbgl/renderer/retained_underlay.hpp",
"include/mbgl/renderer/render_segment.hpp",
"include/mbgl/shaders/program_parameters.hpp",
"include/mbgl/storage/database_file_source.hpp",
"include/mbgl/storage/file_source.hpp",
@@ -1013,6 +1068,7 @@ MLN_DRAWABLES_HEADERS = [
"include/mbgl/shaders/fill_extrusion_layer_ubo.hpp",
"include/mbgl/shaders/heatmap_layer_ubo.hpp",
"include/mbgl/shaders/heatmap_texture_layer_ubo.hpp",
"include/mbgl/shaders/particle_gpu_validation_ubo.hpp",
"include/mbgl/shaders/hillshade_layer_ubo.hpp",
"include/mbgl/shaders/color_relief_layer_ubo.hpp",
"include/mbgl/shaders/hillshade_prepare_layer_ubo.hpp",
@@ -1094,6 +1150,8 @@ MLN_DRAWABLES_MTL_SOURCE = [
"src/mbgl/shaders/mtl/color_relief.cpp",
"src/mbgl/shaders/mtl/line.cpp",
"src/mbgl/shaders/mtl/location_indicator.cpp",
"src/mbgl/shaders/mtl/particle_gpu_validation.cpp",
"src/mbgl/shaders/mtl/retained_underlay_composite.cpp",
"src/mbgl/shaders/mtl/raster.cpp",
"src/mbgl/shaders/mtl/symbol.cpp",
"src/mbgl/shaders/mtl/widevector.cpp",
@@ -1137,6 +1195,8 @@ MLN_DRAWABLES_MTL_HEADERS = [
"include/mbgl/shaders/mtl/color_relief.hpp",
"include/mbgl/shaders/mtl/line.hpp",
"include/mbgl/shaders/mtl/location_indicator.hpp",
"include/mbgl/shaders/mtl/particle_gpu_validation.hpp",
"include/mbgl/shaders/mtl/retained_underlay_composite.hpp",
"include/mbgl/shaders/mtl/raster.hpp",
"include/mbgl/shaders/mtl/shader_group.hpp",
"include/mbgl/shaders/mtl/shader_program.hpp",

View File

@@ -47,6 +47,8 @@ list(APPEND
${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/retained_underlay_composite.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
@@ -90,6 +92,8 @@ list(APPEND
${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/retained_underlay_composite.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

View File

@@ -37,6 +37,7 @@ list(APPEND
${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/retained_underlay_composite.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

View File

@@ -7,7 +7,9 @@
1. `粒子系统项目规则与设计决策_v1.md`
2. `开发环境与系统构建规范_v1.md`
3. `Step指令书与执行报告规范_v1.md`
4. `当前基线与开发路线_v1.md`
4. `当前基线与开发路线_v2.md`(当前)
`当前基线与开发路线_v1.md` 保留为 2026-08-29 历史冻结,不再作为当前构建/发布依据。
## 阅读顺序
`AGENTS.md` → 本目录 README → 四份当前有效总则 → 当前 Step 指令书 → 上一 Step 执行报告 → 其他阶段文档。

View File

@@ -1,5 +1,8 @@
# 当前基线与开发路线 v1
> **历史冻结:2026-08-29。** 当前基线已由 `当前基线与开发路线_v2.md` 取代;本文仅保留当时事实,不用于当前 Android/RN 构建。
## 1. 冻结日期
2026-08-29

View File

@@ -0,0 +1,41 @@
# 当前基线与开发路线 v2
## 1. 冻结日期
2026-09-14。当前正式分支为 `particle/main`,本文对应 QBIN2 V4 Android/RN Test Host 已完成真机/模拟器人工验证后的基线。
## 2. 当前 Git 基线
当前整理起点 HEAD:`de8d230b87345c2538c71318681119cdecb2762a`。正式远端为 `origin/particle/main`。历史 STEP 文档用于追溯当时事实,不再自动代表当前构建方式。
## 3. 当前 Android/RN 版本
- Native OpenGL AAR:`cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3`。
- RN package:`@navsea/maplibre-particle-rn@13.4.1-particle.3`。
- MLRN:`@maplibre/maplibre-react-native@11.3.6`。
- React Native Test Host:RN `0.85.2`、New Architecture。
- Native/RN 版本必须同为 `.3`;不得再使用 `.2` 作为当前消费坐标。
## 4. 当前数据与渲染主链
正式新增链路为 QBIN V4:宿主下载/缓存 raw QBIN,本地文件通过 `FieldRuntime.submitEncodedField(...)` 进入 Android Bridge/JNI,再由 `RasterArraySource::submitEncodedField(...)` 与 `QbinV2Decoder::decode(...)` 解码。MapLibre 不负责 HTTP、provider、manifest、缓存或业务调度。
旧 V3 canonical `submitField(...)` 仍保留用于历史兼容和 Test Host 对照,不是 NavSea 新接入的推荐主链。
## 5. 当前 V4 公共控制面
NavSea/Consumer 新代码优先使用:`submitEncodedField`、`setEncodedColorBinding`、`setEncodedParticleBinding`、`setEncodedForecastTime`、`setEncodedForecastPlaybackState`、`setEncodedForecastPlaybackSpeed`、`clearEncodedField`。运行时继续由 Core 拥有 Field Window、Forecast Clock、T0/T1/mix 与 boundary reuse。
## 6. 当前发布来源
`https://pbf.jpcn.ccwu.cc/` 映射 Linux `/mnt/nas/pbf`。Native 正常发布到 `/mnt/nas/pbf/maven/`,RN tgz 正常发布到 `/mnt/nas/pbf/npm/`;Windows 11 正常验证必须从公网 npm/Maven 消费,不以 Windows Maven Local 或 LAN AAR 复制作为正式路径。
当前已验证 AAR SHA-256:`9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd`。当前 `.3` RN tgz 在 2026-09-14 Kotlin 编译修正后重新发布;今后公开版本内容变化必须升版本,不再覆盖同版本。
## 7. 当前 Test Host
正式源码:`integration/react-native/MapLibreParticleRNTestHost/`。V4 Golden fixtures:Wind/Wave/Current/Rain 各 T0/T1,共 8 个 `.qbin`。Test Host 同时保留 V3 控制用于对照,但 V4 验收必须通过 `submitEncodedField`。
Windows 推荐工作目录必须是普通子目录,例如 `D:\React\MapLibreParticleRNTestHost-V4`,不要在盘符根目录执行 Expo prebuild。Metro 监听 `8091`,Debug App 访问设备 `localhost:8081`,固定使用 `adb -s 192.168.1.146 reverse tcp:8081 tcp:8091`。
## 8. 当前权威操作文档
Android AAR 公网发布、Win11 Test Host 构建、Metro/ADB 与 V4 验收以 `docs/50_Step记录/STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准。`tools/particle_android_pipeline/README.md` 已明确 v1 本地 handoff 工具只用于历史/离线兼容。
## 9. 历史文档边界
STEP-060~072 中的 `Maven Local`、`13.4.1-particle.1/.2`、旧 `D:\V308`、旧 reverse 组合等内容保留为历史验收证据,不得直接用于当前 `.3` 构建。需要当前命令时先读本文件和 STEP-080 v2。
## 10. 下一阶段
当前 Native/RN QBIN V4 Android 基线可用于 NavSea 最小正式接入。建议先做 Wind 单纵向切片:NavSea Weather/Cache 层准备本地 QBIN → V4 submit/binding/time → 地图显示/clear;通过后再扩展 Wave/Current/Rain。NavSea 不解析 QBIN,也不把网络 URL 交给 MapLibre。

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,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,44 @@
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 现状调查
状态:完成(只读调查)
基线:`particle/main` / `2e3ab26275a8d1873d764cf6927783a7b0406836`,`origin/particle/main` 同一提交。
## 调查范围与方法
已完整阅读项目基线、协作/构建/Step/架构/官方基线规范、STEP-064 任务与冻结记录/报告;只读核对 Particle Core、两 renderer、GLSL、RasterArray Timeline、Android Host/JNI、RN package、两个 RN Test Host,以及 `/root/sourceserver/weatherV2` 指定六文件。未修改 WeatherV2/NavSea/生产代码,未执行 Gradle、APK、ADB 或 Jest。
## A. 当前 Field Contract 与 Wind-only 点
`include/mbgl/particle/field/field_data_adapter.hpp` 的 `ParticleFieldDescriptor` 只有一个 `layout/encoding/byteLength/stride`,`ParticleFieldSnapshot` 只有 `velocity: vector<float>` 与 `valid: vector<uint8_t>`。`field_data_adapter.cpp` 只接受 `interleaved-uv` + `float32-le`、每 cell 两 float;`isValid()` 与 decode 末尾硬性要求 `fieldType == "WIND"`、`displayDomain == "GLOBAL"`。`local_particle_field_feed.cpp` 的 `maplibre-particle-field/v1` parser 也强制 `vector` 与一个 `payload`。
`fieldType` / `displayDomain` 还参与 timeline-compatible 比较;当前 fixture、Consumer metadata 皆为 `WIND/GLOBAL`。这不是 provider-neutral 的能力表达,且不能承载 scalar-only 或 Wave 的 direction vector。
## B. GPU、validity、shader 的真实结构
`render_weather_color_layer.cpp` 每快照把 U/V 按快照最大绝对值编码进 RG、validity 编入 A;T0/T1 各一张 `Texture2D`。`render_particle_validation_layer.cpp`(正式 `RasterParticleLayer` 也经此 renderer)同样把 U/V+validity 压入 RGBA,维护 `fieldTexture/nextFieldTexture`、各自 scale 和 image。二者独立拥有资源,不共享 GPU texture;均以 `sameFieldGpuContent()` 比较并可把 OLD_T1 移至 NEW_T0,仅上传新内容。
`shaders/particle_gpu_validation.fragment.glsl` 的 Field pass 从 RG 恢复 U/V,A 同时是 shared validity;`weatherRampColor(length(velocity))` 是图场唯一值来源。无独立 scalar texture、mask texture 或 channel-level validity;粒子另有验证用 coast mask,但不属于 field validity。
## C. Particle 数值与单位假设
GLSL `fieldVelocityAt()` 混合 T0/T1 的 U/V,要求两时刻 A 有效且 `length > 1e-5`。状态 pass 用 `dx=u*simulationSeconds/EarthCircumference`、`dy=-v*simulationSeconds/(EarthCircumference*cos(latitude))`,明确 U east、V north、物理 m/s。C++ 的 `VisualSimulationSecondsPerRealSecond=1200` 只是展示时钟;不会改变 canonical U/V 单位。故现状不适合直接把 Wave unit direction 当 velocity。
## D. RN 到 Core binary ingress 与 ownership
公共 `FieldRuntime.submitField(metadataJson, payloadFileUri)` 在 RN 薄映射内部 `submitWindField`。Kotlin bridge 只接受 `file://` 或绝对本地路径,metadata 上限 1 MiB、payload 1..64 MiB,读为 `ByteArray`;无 number[]/JSON array/Base64。`WeatherRuntime.submitWindField()` 传给 `MapLibreMap.submitRasterArraySourceHostField()`,JNI `NativeMapView::submitRasterArraySourceHostField` 把 Java `byte[]` 复制为 C++ `vector<char>`,再调用 `RasterArraySource::submitHostField()`;Core adapter 再复制解码为 float vector。因此当前 ownership 是同步、三次宿主/核心内存持有,而非 zero-copy。
## Timeline / Runtime Window
`RasterArraySource::submitHostField()` 首帧 pending、第二帧原子建立 Source-owned `RasterArrayTimelineRuntime` T0/T1;后续按 `validTime/generation` 单调 append,容量 8。`RasterArrayTimelineRuntime` 输出 immutable T0/T1 + MIX,暂停不推进。`RenderWeatherColorLayer` 与 `RenderRasterParticleLayer` 都取同一 Source timeline;正常 overlap rollover 保持 `simulationRebuild=false`、`trailCleared=false`,无 overlap 才 reset。invalid/gap 由 timeline 状态 fail-closed。
## Field Renderer 与 speedMps 链路
`WeatherColorStop.speedMps` 位于 C++ `weather_color_stop.hpp`,Android `WeatherRuntime.ColorStop`、Kotlin native bridge 和 generated TurboModule 均仍使用该内部 ABI;RN `.2` 已将公开 `FieldColorStop.value` 薄转换为 `speedMps`。当前 shader 默认 0..30 m/s palette,stop 值也按 speed 解释。因此 V3 必须新增明确 value source,不能以有无 scalar 猜测。
## WeatherV2 输入核对
`nsft.py` / `field_pmtiles.py` 证实 NSFT PMTiles `wm_grid_257`、NW origin(row north→south、column west→east)、同 zoom tile 257 样本。Wind 与 current 都写物理 U/V;`wind.py`、`tide.py` 计算 speed 为 `sqrt(u²+v²)`。Rain 写 `rain_mm_h + valid`;虽 decoder 有 `precipitation_type`,`encode_rain_tile()` 未写它。Wave 写 `height_m`、`period_s` 和 `unit_to_u/v`;`waves.py` 将 from 转 to 后归一化,非 m/s。`target_time` 由实际任务时间写入 header,不能推定小时 cadence。任务书给出的事实与代码一致。
## 直接结论
V3 应保留现有 Source-owned timeline、两个独立 renderer 和 overlap reuse 的行为,但将单个 velocity snapshot 升级为一个快照的 optional scalar、optional vector、shared validity 三资源。业务名称、单位换算、NSFT/PMTiles、tile 拼接、provider/缓存必须停在 NavSea 侧。

View File

@@ -0,0 +1,117 @@
# STEP-077 QBIN2 V4 Android / JNI / RN 桥接调查报告 v1
状态:完成(只读调查)
日期:2026-09-13
基线:`0759a06104e1fb30f036db2efb6ef34ca5859759`(`particle/main` = `origin/particle/main` 于调查开始时)
## 结论
V4 应新增一条与 V3 完全平行的、低频的 `WeatherRuntime` / `MapLibreMap` / `NativeMapView` 通道;不得接到现有 `submitField`、`clearFields`、`setForecastTime`、`setPlaybackState` 或 `setPlaybackSpeed`。这些现有命令分别写入 V3 Host field、`clearHostFields()`、`seekRuntimeTimelineToTimestamp()` 和 `controlRuntimeTimeline()`,会操作 V3 timeline,不能承载 V4 的 `EncodedFieldStore` / `ForecastClock`。
推荐 RN 只传绝对本地 `qbinFileUri`(或 `file://` URI)及小型、typed primitive ABI 元数据;Kotlin 有界读取为 Java `byte[]`,JNI 复制为 C++ `std::vector<uint8_t>`,再调用 `RasterArraySource::submitEncodedField(...)`。不传 Base64,不由 JS 构造 `number[]` / 大型 `ReadableArray`。V4 Core 独占每个 field 的 T0/T1/MIX 选择;桥接只传 requested time、play/pause 与速度,绝不传 endpoint、mix 或 renderer snapshot。
## 实际当前链路
```text
FieldRuntime.ts public facade
-> NativeWeatherRuntimeBridge.ts TurboModule Spec (primitive arguments / Promise)
-> WeatherRuntimeBridgeModule.kt
runOnUiQueueThread -> Fabric reactTag
UIManagerHelper(FABRIC).resolveView -> MLRNMapView.mapLibreMap
local file bounded read -> WeatherRuntime
-> MapLibreMap (map-owned WeatherRuntime facade)
-> NativeMapView Java (checkState) -> registered NativeMapView JNI
-> native_map_view.cpp: style.getSource(id), dynamic_cast<RasterArraySource*>
-> RasterArraySource V3 Host API OR isolated V4 Host API
```
源码事实如下。
- `FieldRuntime.submitField` 仍是 V3 public API,封装 `FieldPayloadRefs` 为 validity/scalar/vector 三个 URI;`submitLegacyVectorField` 保留 v1 兼容入口。`NativeWeatherRuntimeBridge.ts` 的 codegen Spec 已刻意使用 primitive 参数,空 string 仅表达 V3 optional plane。
- Kotlin 对 V3 已验证本地绝对路径或 `file://`、单文件和 aggregate 64 MiB、读中长度变化;再形成 `ByteArray`。它不保存 reactTag、map 或 timeline,而是每次在 UI queue 解析当前 Fabric view。
- `WeatherRuntime.submitField` 把 V3 bytes 重新 pack 后调用 `submitRasterArraySourceHostFieldV2`;`NativeMapView` 用 `GetArrayRegion` 复制到 C++,然后拆 planes 并调用 `submitHostField`。这是可复用的“file -> Java byte[] -> JNI copy -> vector” seam,而不是可复用的 V4 API。
- V4 `RasterArraySource` 已有独立 `EncodedFieldStore`、`ForecastClock`、binding intent 和 `submitEncodedField` / `set*Binding` / `setRequestedForecastTime` / `setForecastPlayback*` / `clear*EncodedFields`;注释和 STEP-076 public-path 测试都证明它不写 V3 T0/T1/MIX 或 renderer snapshot。
## QBIN 传输形态
| 方案 | 当前支持与成本 | 结论 |
| --- | --- | --- |
| A:RN `qbinFileUri`,Kotlin 读取 | 已有 `readLocalPayload` / `resolveLocalFile` 精确先例;仅接受本地文件、1..64 MiB,16 KiB buffer,有 read-time size guard;Java/JNI 仍各有一次受控 copy | 推荐。新增 V4 方法复用读取 helper,但不复用 V3 submit/timeline API。 |
| B:RN `byte[]` / `number[]` | 当前 TurboModule Spec 无 binary array ABI;`ReadableArray` 是现有 color stops 的小对象集合,不适合 QBIN。JS number array 造成 JS/native 转换、对象/number 开销与额外 copy;Base64 更扩大内容且无现有 ABI | 拒绝。不得为了方便绕过 64 MiB file guard。 |
| C:直接复用 Java/JNI `byte[]` seam | Kotlin -> `WeatherRuntime` -> Map -> `NativeMapView` 的 `byte[]` 和 `GetArrayRegion` 已存在,适合作为 A 的下半段 | 复用实现模式;新增命名为 V4-specific JNI 方法,不复用 V3 方法。 |
64 MiB 是现有 Kotlin `MAX_PAYLOAD_BYTES` 与 Java `MAX_FIELD_PAYLOAD_BYTES` 的边界;V4 要在 Kotlin、Java、JNI 均检查 raw QBIN 长度 `1..64 MiB`,随后 Core Decoder 继续执行其自身 QBIN 全文件/decoded-raw 限制。该限制不授权桥接解析 QBIN。
## 推荐 V4 API 与跨层 ABI
所有 String 以 UTF-8 byte length 做小型上限校验;`reactTag`、generation、dataVersion、time 使用 JS 安全整数检查(当前 bridge 对 timestamp 已采用 finite/nonnegative/integral guard)。枚举使用固定 string;所有 enum numeric 值只在 Kotlin/Java/C++ 边界转换。
| RN public / Turbo Spec | Kotlin / Java facade | JNI / Core | 参数形态及责任 |
| --- | --- | --- | --- |
| `submitEncodedField(tag, fieldKey, fieldID, generation, dataVersion, contractJson, qbinFileUri)` | `submitEncodedField(...)` | `submitRasterArraySourceEncodedField(sourceId, fieldKey, fieldID, long generation, long dataVersion, contractJson, byte[] raw)` | URI 为文件;metadata 用 primitive;contractJson 是唯一结构化小协议,Kotlin/Java 各只作长度、空值、range;JNI 解析为 Core structs 并调用 V4 API。 |
| `setColorBinding(tag, fieldKey, semanticID, groupID, expectedValueClass, expectedUnitID)` | 同名 | `setRasterArraySourceEncodedColorBinding(...)` | 完全 primitive;JNI 构造 `ColorBinding`,Core resolver 仍做最终 plane assertion。 |
| `setParticleBinding(tag, mode, fieldKey, firstSemanticID, firstGroupID, secondSemanticID, secondGroupID, expectedUnitID)` | 同名 | `setRasterArraySourceEncodedParticleBinding(...)` | mode-dependent selector 仍占固定 primitive slots;Direction 使用 first selector,second 传 0/0。Kotlin/Java 检查 mode 和 uint16 range,不引入 `ReadableMap`。 |
| `setRequestedForecastTime(tag, timestampMs)` | `setEncodedForecastTime` | `setRasterArraySourceEncodedForecastTime` | 独立 V4 名称,`long`;Core ForecastClock 选择各 field window/mix。 |
| `setForecastPlaybackState(tag, "PLAY"|"PAUSE")` | `setEncodedForecastPlaybackState` | `setRasterArraySourceEncodedForecastPlaybackState` | V4 enum,绝不调用 V3 `controlRasterArraySourceTimeline`。 |
| `setForecastPlaybackSpeed(tag, "HALF"|"NORMAL"|"DOUBLE")` | `setEncodedForecastPlaybackSpeed` | `setRasterArraySourceEncodedForecastPlaybackSpeed` | Kotlin/Java 映射为 0.5/1/2;Core 保留 speed validity。 |
| `clearEncodedField(tag, fieldKey)` / `clearAllEncodedFields(tag)` | 同名 | 同名 V4-only JNI | 不清 V3 Host fields、不改变 V3 cadence/timeline。single-clear 返回 `CLEARED` / `FIELD_NOT_FOUND`;all-clear 返回 accepted command。 |
`ResolvedFieldProfileContract` 需要 `contractSchemaVersion, profileID, profileVersion, allowAdditionalPlanes, sharedValidityRequired, timeRelation, requiredPlanes[]`,每个 plane 为 `semanticID, valueClass, unitID, groupID, component, requiredFlag`。它是唯一确实需要 array 的稳定数据;当前 TurboModule codegen 对 public object ABI 已有历史约束,故不建议 `ReadableMap/ReadableArray`,也不应将每个平面拆成无边界的 JS 数组。推荐在 RN/Spec 中传受大小限制的 `contractJson`,在 JNI 的单一 parser 转为 C++ contract;Kotlin/Java 不语义解析。这样 Host/SDK 负责已经 resolved 的 profile contract,Native 不知道 provider/profile 业务;Core `FieldProfileValidator` 仍是 structural/semantic 唯一裁决者。
这不是让 JSON 承载 QBIN 或 server manifest:`contractJson` 是受 schema 版本约束的小 control-plane ABI。若 RN codegen 后续稳定支持 readonly typed object + typed array,才可在破坏性 ABI 版本中替换;本 Step 不建议 `ReadableMap`,因为它绕过 generated Spec、会把多层 field parsing 复制进 Kotlin。
## 兼容与所有权策略
- V3 `FieldRuntime.submitField`、`submitLegacyVectorField` 与现有 `FieldSubmitResult` 保留不变;V4 新增 `submitEncodedField` 与新的 `EncodedFieldSubmitResult` union,不能改名覆盖。
- `WeatherRuntime.setForecastTime`、`setPlaybackState`、`setPlaybackSpeed` 必须不复用:它们当前调用 V3 `seekRasterArraySourceTimelineToTimestamp` / `controlRasterArraySourceTimeline`。V4 用 `setEncoded...` Java/JNI names;RN public 名可按任务书使用 `setRequestedForecastTime` 等,但需明确是 V4 namespace / facade(例如 `EncodedFieldRuntime`),避免误导。
- `clearFields` 和 `clearEncodedFields` 必须分离。前者还参与 V3 state/cadence/disable 行为并调用 `clearHostFields()`;后者只清 `EncodedFieldStore`,保留 V4 bindings,正如 STEP-076 证明,且不得触及 V3。
- `setColorBinding` / `setParticleBinding` 不修改 WeatherColor/RasterParticle renderer properties;它们只存 V4 binding intent。实际 renderer 接线不在本 Step 范围。
## 错误模型
Promise rejection 仅用于桥接前条件、生命周期或无法执行;已进入 Core 的 `RasterArrayEncodedFieldSubmitResult` 必须 resolve 成可区分的 V4 result string,不能折叠为 `UNKNOWN` 或 `NATIVE_REJECTED`。
| Core result | 推荐 RN resolved result |
| --- | --- |
| `AcceptedInserted` | `ACCEPTED_INSERTED` |
| `AcceptedReplacedRevision` | `ACCEPTED_REPLACED_REVISION` |
| `Duplicate` | `DUPLICATE` |
| `RejectedDecode` | `REJECTED_DECODE` |
| `RejectedContract` | `REJECTED_CONTRACT` |
| `RejectedInvalidFieldKey` | `REJECTED_INVALID_FIELD_KEY` |
| `RejectedIdentityConflict` | `REJECTED_IDENTITY_CONFLICT` |
| `RejectedStaleRevision` | `REJECTED_STALE_REVISION` |
| `RejectedIncompatible` | `REJECTED_INCOMPATIBLE` |
| `RejectedCapacity` | `REJECTED_CAPACITY` |
桥接层 rejection code 应为:`INVALID_ARGUMENT`(tag、enum、整数、UTF-8/control size)、`PAYLOAD_FILE_UNREADABLE`(空/越限/非本地/不可读/读中变更)、`MAP_VIEW_NOT_FOUND`、`MAP_NOT_READY`、`STYLE_NOT_READY`、`WEATHER_OBJECTS_NOT_READY`、`RUNTIME_DISPOSED`,以及 V4 source 获取失败时的 `SOURCE_MISSING`。JNI 在 Source 不存在或类型不符时不得伪装为 Core `RejectedDecode`;应返回明确 native bridge status,再由 Java 映射为 `SOURCE_MISSING`。style 已存在但 source 尚未 ready 与 runtime disposed 同样不能混成 submit result。
## 生命周期与线程
`WeatherRuntime` 被 `MapLibreMap` 私有拥有,且是 style-scoped;`replaceWeatherRuntime` 先 dispose,`setStyle` 也 dispose,Kotlin 的 MapView detach listener 调用 `map.disposeWeatherRuntime()`。每个 bridge call 在 `runOnUiQueueThread` 通过 Fabric `reactTag -> UIManagerHelper -> MLRNMapView -> mapLibreMap` 当场解析;不允许新增 map registry、缓存 tag、长期保存 `MapLibreMap*` 或 native source pointer。
未来 V4 command 应沿用 `withRuntime`,在 Android UI 主线程上从当前 style 查 source。`NativeMapView.checkState` 是 Java 层 native lifetime guard;C++ 必须每次 `style.getSource(sourceId)` 和 `dynamic_cast<RasterArraySource*>`,不可缓存指针。style reload/detach/dispose 后,后续命令得到上述 lifecycle error;旧 V4 state 随 Source/style 销毁失效,Host 要在重新 attach、style ready 后按自己的缓存重新提交 raw QBIN 与 binding intent。
## 可用验证与后续验收
本调查没有执行 Android 编译、APK、ADB 或实体机操作。当前 Linux 可用入口与边界:
| 层 | 后续可验证项 | 边界 |
| --- | --- | --- |
| TS | `npx tsc -p packages/maplibre-particle-rn/tsconfig.json --noEmit`;Test Host `npm run typecheck` | package 未定义独立 typecheck script;依赖由 Test Host node_modules/paths 提供。 |
| TurboModule | RN codegen 由 package `codegenConfig`(`WeatherRuntimeBridgeSpec`、`src`、Kotlin package)与 Android Gradle/RN integration 驱动 | 没有发现完全脱离 Gradle 的仓库级 codegen 命令;实现 Step 应记录实际 generation task。 |
| C++/JNI | 头文件/`native_map_view.cpp` 的精确 compile 与 Linux Core unit tests;JNI registration list 与 Java native declarations 静态一一审计 | 精确 Android ABI/JNI compile 仍需 Android NDK Gradle/native build;Linux Core build 不证明 Android linkage。 |
| Java/Kotlin | `WeatherRuntimeStateTest` 是 Mockito/JVM facade 状态测试,可新增 V4 mapping/隔离测试 | 它依赖 Android Gradle test classpath;未发现无需 Gradle 的现成 Kotlin/Java standalone validation。 |
建议实施拆分:
1. 先冻结 RN Spec/types 与 Kotlin file/control validation,新增 V4 result union,并以 typecheck + codegen 审计验收。
2. 再新增 Java `WeatherRuntime` V4-only facade、`MapLibreMap` / `NativeMap` / `NativeMapView` Java declarations、C++ JNI registration 与 source lookup;JVM Mockito 覆盖 V3/V4 time-clear 不串线和 error mapping。
3. 最后才接 renderer consumer(独立 Step),以 C++ Source tests 证明 V4 projection、style reload/detach rejection、JNI exact build、Android Debug OpenGL/Vulkan 与离线 fixture 验收。不得下载真实 QBIN。
## 实施文件清单与风险
预期修改:`packages/maplibre-particle-rn/src/{types.ts,NativeWeatherRuntimeBridge.ts,FieldRuntime.ts}`、`WeatherRuntimeBridgeModule.kt`、`WeatherRuntime.java`、`MapLibreMap.java`、`NativeMap.java`、`NativeMapView.java`、`native_map_view.hpp/.cpp`,以及相应 JVM/C++ tests。不会改 package version、Test Host、Renderer/GPU。
主要风险为 codegen 对 complex object/array 的限制、64 MiB 下 Kotlin/Java/JNI 三份瞬时内存、JS number 精度(所有 uint64 必须限制在 safe integer 或改 decimal string ABI)、source/style 生命周期竞争、以及错接旧 V3 timeline。前四项通过 primitive+small-contract JSON、固定 64 MiB guards、UI queue late lookup 和 typed result 处理;最后一项通过 V4-only 方法命名、独立 NativeMap API 和隔离测试消除。

View File

@@ -0,0 +1,370 @@
# STEP-086 Particle 连续渲染唤醒 MapLibre 整图成本调查 v1
状态:`INVESTIGATION / SOURCE_EVIDENCE_CONFIRMED / RUNTIME_PROFILE_PENDING`
日期:2026-09-21
## 1. 调查目标
本调查针对 NavSea 完整场景中出现的以下现象:
- Particle 独立 Host / NavSea 最小 Particle Host 中,Particle 本体性能基本正常。
- 完整 NavSea 在 Particle OFF、camera 静止时能够正常 idle。
- 完整 NavSea 在 Particle ON 后,RenderThread、JS、main CPU 明显高于最小 Particle Host。
- 已排除 NavSea 侧两个已确认的 JS 问题:NavigationEngine renderPulse 导致整树重渲染,以及 JsSchedulerProbe 常驻 RAF/16ms timer。
核心问题是验证:
> Particle 连续动画是否通过 MapLibre 的全局 repaint/update/render 生命周期,把本来可 idle 的 PBF/vector/style/map scene 持续唤醒。
本调查优先检查 Shared Core / Renderer / Android frame scheduling,不修改 NavSea ↔ Particle C-001 公共责任边界。
## 2. 调查基线
Integration 仓库已同步最新 main:
- repo:`/root/sourceserver/navsea-particle-integration`
- branch:`main`
- HEAD:`39ad8415fbdb76c1822460bb09f3a3f35fe141e0`
- Contract:`C-001`
Particle 源码调查基线:
- repo:`/mnt/sda1/MapLibreParticleLab`
- branch:`origin/particle/main`
- HEAD:`70f112ea27acaaa4da0a307563b7868937f69691`
- commit:`fix: close STEP-085 V4 renderer efficiency`
- 当前该 HEAD 中 package/version 字段仍为 `13.4.1-particle.7`。
注意:用户当前运行环境称为 `.8`,而本次可直接审计的最新远端源码 HEAD 仍显示 `.7` 版本字段。
因此本文的源码结论针对 `70f112ea2` 的 MapLibre/Particle 调度架构;后续如 `.8` 对应精确发布提交不同,应再做一次 commit-level 对照。
不过本文确认的全局 repaint/render 机制位于 MapLibre Renderer 主链,不依赖 NavSea 业务代码。
## 3. 当前实测背景
用户提供的 Android 模拟环境参考量级:
### 最小 Particle Host
- RenderThread:约 38–40%
- JS `mqt_v_js`:约 4–5%
- main:约 11%
### 完整 NavSea,Particle OFF
- JS:7.08%
- RenderThread:1.31%
- main:3.34%
说明完整 NavSea 的静止地图、PBF、UI 和业务逻辑本身能够进入低成本 idle。
### 完整 NavSea,Particle ON,且已排除两个 NavSea JS 问题
- JS:29.07%
- RenderThread:80.13%
- main:15.51%
相对于最小 Particle Host,大约额外:
- JS:+24 percentage points
- RenderThread:+40 percentage points
- main:+4~5 percentage points
## 4. 源码确认的核心结论
核心假设成立:
> 当前 Particle 没有独立的“只绘制 Particle Layer”的动画循环。Particle 的连续动画通过 MapLibre 全局 `needsRepaint` / `onUpdate` / `Renderer::render` 链维持,因此一次合法的 Particle animation frame 会进入完整 MapLibre frame orchestration。
主链如下:
```text
RenderRasterParticleLayer::needsRepaint()
↓
RenderOrchestrator::hasTransitions()
↓
RenderTreeParameters::needsRepaint = true
↓
Renderer::Impl::render() 完成当前整帧
↓
Map::Impl::onDidFinishRenderingFrame()
↓
if (needsRepaint) onUpdate()
↓
重新构造完整 UpdateParameters
↓
AndroidRendererFrontend::update()
↓
MapRenderer.requestRender()
↓
Renderer::render()
↓
RenderOrchestrator::createRenderTree()
↓
完整 Map frame
```
因此 `needsRepaint()` 的语义不是“仅让 Particle Layer 再绘制一次”,而是“MapLibre 需要下一张完整地图 frame”。
## 5. Particle 每帧如何请求下一帧
`RenderRasterParticleLayer::needsRepaint()` 在 Particle active、visible、field-ready 且 cadence 到期时返回 true。
`RenderOrchestrator::hasTransitions()` 会遍历全部 RenderLayer:
- `hasTransition()`
- `needsRepaint()`
任一 layer 返回 true,整个 RenderTree 即被标记为 `needsRepaint`。
帧结束后,`Map::Impl::onDidFinishRenderingFrame()` 在 Continuous 模式下执行:
```text
if (needsRepaint || transform.inTransition()) {
onUpdate();
}
```
而 `Map::triggerRepaint()` 本身也只是:
```text
impl->onUpdate();
```
Android 显式 `FPS_30/FPS_60` 还存在一层 platform wake:
- FPS_30:Handler 约每 34ms 调用一次 `map.triggerRepaint()`
- FPS_60:Handler 约每 17ms 调用一次 `map.triggerRepaint()`
- AUTO:不使用该 Handler,但 Particle 的 `needsRepaint()` 仍可使连续 frame 自行串联。
结论:当前没有 layer-scoped repaint 调度语义。
## 6. 一次 Particle repaint 会重新进入哪些 MapLibre 工作
`Map::Impl::onUpdate()` 每次都会重新生成完整 `UpdateParameters`,其中包括:
- transform state
- style loaded state
- light
- images
- sources
- layers
- glyph/font/sprite 状态
- annotation manager
- tile LOD 参数等
随后 `Renderer::render(updateParameters)` 会重新调用:
- `RenderOrchestrator::createRenderTree()`
- `renderTree->prepare()`
- `Renderer::Impl::render()`
因此不是复用一张静态 RenderTree 后只执行 Particle pass。
## 7. camera 静止时仍重复执行的非 Particle 工作
在当前源码中,即使 camera 完全静止,连续 frame 仍会重复:
1. 遍历全部 source。
2. 对每个 source 遍历 ordered layers,计算 source/layer 关系、visibility、zoom fit、renderability。
3. 对启用 source 调用 `source->update()`。
4. Vector/PBF source 进入 `RenderTileSetSource::update()` → `TilePyramid::update()`。
5. TilePyramid 会重新处理 wrap、zoom、covering tiles、retain 等逻辑。
6. 对全部 enabled source 执行 `prepare()`。
7. 对全部 renderable layer 执行 `prepare()`。
8. symbol layer 进入 cross-tile symbol bookkeeping。
9. 根据 placement freshness 条件周期性重新执行 placement。
10. Renderer upload pass 遍历 source/layer RenderItem。
11. `updateLayers()`、layer-group tweaker、layer-group upload 继续执行。
12. opaque pass 重新遍历/绘制普通 layer groups。
13. translucent pass 重新遍历/绘制普通 layer groups。
14. 重新 present 整个默认 framebuffer。
因此“大量 PBF/vector/fill/line/symbol 在静止 camera 下由 Particle frame 唤醒”从源码结构上成立。
## 8. 需要精确区分:不是所有 Style 工作都每帧完整重算
当前代码并非每帧无条件重新 evaluate 所有 style property。
普通 RenderLayer 的 `evaluate()` 通常受以下条件约束:
- layer added/changed
- zoom changed 且该 layer 依赖 zoom
- crossfade
- active transition
所以静止 camera、style 不变时,很多 property evaluation 可以跳过。
同样,完整 symbol placement 也不是严格 60 次/秒。
`PlacementController::placementIsRecent()` 使用 placement update period 控制重新 placement;默认逻辑与约 300ms 的 transition/update period 相关。
但这些优化不改变主结论:
> 即使 property evaluation 和完整 placement 并非每帧执行,整张地图仍持续进入 source/layer traversal、source update/prepare、layer prepare、command encoding、普通 layer draw 与 framebuffer present。
这足以解释复杂 Style 与简单 Test Host 之间的大幅 RenderThread 差异。
## 9. Particle 是否每帧制造不必要的全局状态变化
本次静态审计没有发现 Particle 每个 simulation frame 都在修改以下全局状态的直接证据:
- camera state
- 普通 vector/PBF source data
- 全局 Style revision
- 普通 layer property
- sprite/image revision
STEP-085 已针对 Particle-local 的不必要成本做过修复,包括:
- V4 WeatherColor 静态状态不再因为没有 legacy timeline 而永久 `needsRepaint()`。
- V4 Particle 增加 Source-owned renderer cadence。
- V4 snapshot 改用 immutable shared pointer,减少稳定 window 的大数组深复制。
- WeatherColor 尽量复用 drawable,仅 wrap 变化时重建。
- 增加 renderer debug counters。
所以当前问题与 STEP-085 并不矛盾。
STEP-085 解决的是:
```text
Particle 自己是否产生了多余 repaint / copy / drawable rebuild
```
本调查发现的是更外层问题:
```text
即使是“合法的 Particle frame”,当前仍等价于一次完整 MapLibre frame
```
## 10. 为什么简单 Particle Host 不能证明完整 NavSea 成本正常
简单 Host 的 Style 很小,因此:
```text
Particle simulation/render
+ 少量普通 MapLibre layer 成本
≈ 当前约 40% RenderThread
```
完整 NavSea Style 包含大量:
- PBF/vector source
- fill
- line
- symbol
- text/icon placement
- navigation marks
- 其他 MapLibre layers
当 Particle 让 MapLibre 持续出帧后:
```text
Particle 自身成本
+ 大型静态 map scene 的重复 orchestration / prepare / draw
≈ 当前约 80% RenderThread
```
因此必须使用复杂真实 Style 做性能判断,不能只使用 Particle Test Host。
## 11. JsSchedulerProbe 联动现象的当前解释边界
用户实测:
- Particle OFF:Probe OFF 7.08%,Probe ON 8.93%,约 +1.85pp JS。
- Particle ON:Probe OFF 约 27~29%,Probe ON 96.47%,约 +69pp JS。
当前 Native 源码审计没有发现 Particle 每帧主动通过 RN bridge 回调 JS 的证据。
更合理的当前解释是:
- Particle 让 MapLibre/RenderThread 持续处于 frame pressure。
- FPS_30/60 时 Android Main Looper 还有 cadence wake。
- RN RAF / Choreographer / timer / JS scheduler 在不再 idle 的系统中发生严重放大或竞争。
但这部分尚不能仅靠 Native 静态源码判定根因。
需要 RN scheduler trace / perfetto 才能进一步归因。
由于 JsSchedulerProbe 将退出正常运行路径,它不应阻塞当前 Native Renderer 主问题。
## 12. 下一阶段必须做的运行时 Profile
需要固定 camera,并至少构造以下矩阵:
| 场景 | Style | Particle |
|---|---|---|
| A0 | 简单 Style | OFF |
| A1 | 简单 Style | ON |
| B0 | 大量 PBF/vector/symbol/line/fill | OFF |
| B1 | 与 B0 完全相同 | ON |
B1 还应分别验证:
- AUTO
- 60fps
- 30fps
建议记录以下 Native 阶段的调用次数与 CPU 时间:
- `Map::Impl::onUpdate`
- `RenderOrchestrator::createRenderTree`
- source × layer traversal
- VectorSource / TilePyramid update
- source prepare
- layer prepare
- cross-tile symbol bookkeeping
- full placement
- `updateLayers`
- layer-group tweaker
- upload pass
- opaque pass
- translucent pass
- present
- Particle simulation advance
- Particle render/composite
同时记录 MapLibre 已有 `gfx::RenderingStats`:
- encodingTime
- renderingTime
- numDrawCalls
- texture/buffer/uniform updates
并结合 STEP-085 的 Particle debug counters,区分 Particle 自身和非 Particle 成本。
预期验证特征:
```text
B0 + stable camera
→ frame/update 很快降到接近 idle
B1 + stable camera
→ onUpdate/createRenderTree 与 Particle frame 数量高度相关
→ Vector/PBF source update/prepare 被持续调用
→ 普通非 Particle draw calls 持续存在
→ B1 non-Particle CPU 显著高于 A1
```
如果该特征成立,则“continuous repaint 唤醒完整 scene”的运行时证据闭环完成。
## 13. 优化方向分层
### 第一层:低风险缓解
优先验证 30fps / adaptive fps。
原因:在当前 full-map repaint 架构下,降低 Particle frame 数不仅减少 Particle 本身成本,也会近似同比减少被唤醒的整个 NavSea map scene 成本。
### 第二层:Renderer animation-only fast path
值得优先调查是否可在 MapLibre Native 内部实现:
- 复用稳定 map state / RenderTree
- Particle 下一帧不重新进入完整 `Map::Impl::onUpdate`
- 跳过普通 source update、TilePyramid update、部分 prepare 和 placement
- 仍允许必要的 framebuffer redraw
这属于内部性能实现优化,原则上不需要改变 C-001。
### 第三层:静态地图 framebuffer cache + Particle composition
若目标是让静态 fill/line/symbol/PBF draw call 在 Particle 动画期间也消失,则需要 retained/offscreen composition。
这比 animation-only fast path 复杂得多,需要处理:
- layer ordering
- transparent layers
- depth/stencil
- 3D
- world wrap
- custom layer/render target
- camera/style/source invalidation
如果最终要求 Particle 固定作为静态地图之上的独立 overlay,架构可显著简化;但若这种限制成为 Host 可观察的公共语义,则必须重新进入 Integration RFC/Contract 评审。
## 14. 当前结论
源码层面可以确认:
1. Particle 当前通过 MapLibre 全局 `needsRepaint` 维持动画。
2. Particle 没有独立 layer-only render loop。
3. 一次 Particle repaint 会进入完整 `Renderer::render`。
4. camera 静止时仍会重复大量普通 source/layer update、prepare、render 工作。
5. 普通 fill/line/symbol/PBF draw 会在连续 frame 中再次提交。
6. 并非所有 style property 和 placement 都每帧完整重算,但不足以让复杂地图回到 idle。
7. 当前没有证据表明主要原因是 Particle 每帧修改全局 Style/Source/Camera revision。
8. STEP-085 已降低 Particle-local 的多余 repaint/copy/rebuild,但没有改变“合法 Particle frame = 完整 MapLibre frame”的架构。
9. Test Host Style 太简单,不能用于排除真实大型 Style 下的 full-scene amplification。
10. 下一步应先做 A0/A1/B0/B1 定量 profile,再决定 animation-only fast path 或 framebuffer composition。
11. 现阶段不需要修改 Particle 公共 API,也不需要修改 NavSea ↔ Particle C-001。
## 15. 建议的下一任务
建议下一任务只做 Renderer observability / performance profile,不先做架构重构。
目标是把完整 frame 拆成 Particle 与 non-Particle 阶段,定量证明:
- 每秒进入多少次完整 map update/render;
- 大型 Style 中额外 CPU 具体落在哪些 source/layer/placement/draw 阶段;
- 30fps 对完整 NavSea 的实际收益;
- 哪些阶段可以通过复用 RenderTree 或 animation-only fast path 安全跳过。
完成该 profile 后,再决定是否进入具体 Renderer 优化实现。

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,117 @@
# MapLibreParticleLab V3 通用 Scalar / Vector Field 运行时架构设计
状态:冻结(V3.0 Contract,尚未实现)
决策:新 canonical schema 为 `maplibre-particle-field/v2`;RN package 首个 V3 实现版至少 `13.4.1-particle.3`,Native AAR 独立升版,绝不覆盖 `.2`/`.1`。
## 边界与目标
V3 从 NavSea canonical Field submission 开始,是 provider-neutral、business-neutral 的二维时序 Scalar / Vector runtime。它不解析 NSFT/PMTiles,不做 HTTP、manifest、time_index、缓存、业务单位转换,也不以 WIND/WAVE/RAIN/TIDE 决定能力。V3.0 固定为 **0/1 scalar + 0/1 vector + 1 shared validity**,拒绝 neither;不设计 N-channel graph。
```text
WeatherV2 → NSFT → NavSea JS/TS(解码、拼接、单位/业务 canonical)
│ metadata + local scalar/vector file reference
▼
MapLibreParticleLab V3: validate → Snapshot → Source Timeline T0/T1/MIX
├─ Generic Field Renderer (config.valueSource)
└─ Particle Renderer (config.vectorSemantics)
```
当前实现为 `v1 vector(U/V)+valid → Timeline → 两个各自 RGBA U/V texture renderer`;V3 仍是一份 Source snapshot,两 renderer 独立显示、独立 GPU resource,而非两份 host field。
## 冻结数据 Contract
```text
FieldSnapshot
descriptor: fieldId, generation, validTime, grid, bounds, dataVersion, content hash
scalar?: Float32[width*height] // scalar plane
vector?: Float32[width*height*2], interleaved U/V // vector plane
valid: UInt8[width*height] // shared validity
vectorSemantics?: VELOCITY | DIRECTION // only when vector exists
```
- 所有 plane 必为 `float32-le`、NW-origin row-major;vector U=east、V=north。
- scalar-only、vector-only、scalar+vector 合法;neither、尺寸/byteLength/finite/validity 不一致均 reject。
- `valid` 是一个 snapshot-wide shared logical mask;V3.0 不加 channel mask。任何无效 cell 对全部现有 channel 无效。
- `validTime` 是实际时刻,不假设固定间隔;T0/T1 必须相同 grid/bounds/channel presence/vector semantics/encoding,且都递增。
- `fieldId` 是 opaque identity,删除 `fieldType` / `displayDomain` 对能力、adapter validation 和 compatibility 的强制依赖。领域/mask 选择若仍需要,作为 renderer config 的 business-neutral spatial policy,不能作为天气 enum。
### v2 JSON 与 binary plane
```json
{
"schema":"maplibre-particle-field/v2", "fieldId":"opaque-id", "generation":1,
"validTime":1760000000000, "dataVersion":1,
"grid":{"width":257,"height":257,"origin":"north-west","rowOrder":"north-to-south"},
"bounds":{"west":0,"south":0,"east":1,"north":1},
"validity":{"encoding":"uint8","byteLength":66049},
"scalar":{"encoding":"float32-le","byteLength":264196,"noData":-9999},
"vector":{"layout":"interleaved-uv","encoding":"float32-le","byteLength":528392,"noData":-9999,"semantics":"DIRECTION"}
}
```
提交 API 冻结为 `submitField(metadataJson, payloadRefs)`,其中 `payloadRefs={validityFileUri, scalarFileUri?, vectorFileUri?}`;每个 URI 独立 local file、独立限额、先验证存在性与总大小再同步读入。metadata 仍小,绝不采用 JS number[]、JSON array、Base64。v2 validity 采用显式 `uint8` plane,避免让任一 channel 的 noData 充当隐式共享 mask;Kotlin/JNI 最小修改为从单 `byte[]` 改为三个 nullable byte[](或等价 native struct),Core parser/adapter 接收 plane bundle。若实施时 JNI 参数数量/ABI 风险高,可在 Kotlin 读三文件后封装为有明确 offsets/lengths 的 **v2 binary container**;不能复用 v1 的单 payload 产生歧义。
## v1/v2 版本决策
选择 **v2**,而非 v1 additive:v1 单 `vector`、单 payload、`velocity` snapshot 和 WIND/GLOBAL validation 都是结构性约束;在同 schema 内重载 payload 会令 scalar-only 与 scalar+vector 的 bytes、validity、noData、compatibility ambiguous。
- v1 vector-only 继续由旧 parser/adapter 可读,作为 compatibility ingress;最薄兼容层将 v1 decode 规范化成 v2 内部 snapshot(vector=present、shared validity 从 v1 U/V noData 派生、semantics=VELOCITY)。
- Runtime 内只使用一个 V3 snapshot/timeline 实现;允许 v1 首帧与 v2 vector-only 共同进入同 Runtime,前提是 normalize 后 descriptor/semantics/channel presence 完全 compatible。否则 fail-closed,不维护两套 runtime。
- v1 不增加 scalar 字段;发布 `.3` 才公开 v2 `payloadRefs`,旧 `.2` ABI 可继续作为 legacy ingress,Native/AAR coordinate 独立推进。
## Renderer Contract(与数据分离)
`FieldSnapshot` 只回答“有什么”;renderer config 显式回答“怎样画”:
```text
FieldRendererConfig.valueSource = SCALAR | VECTOR_MAGNITUDE
ParticleRendererConfig.vectorSemantics = VELOCITY | DIRECTION
ParticleRendererConfig.advectionScale (finite, >0; display/config policy)
```
`SCALAR` 要求 scalar present;`VECTOR_MAGNITUDE` 要求 vector present;不满足即该 renderer unavailable/reject,绝不猜。`FieldColorStop.value` 保持 generic public semantics。Native 内部 `speedMps`、shader default 0..30m/s 和 speed-color 命名将在 V3-03 统一改为 generic `fieldValue`/`value`; legacy JNI ABI 可薄转换,不能改变 RN `.2`。
`SnapshotGpuResources` 为每一个 T0/T1 快照包含 `scalarTexture?`、`vectorTexture?`、`validityTexture` 与各 plane scale/format。rollover 把整个 bundle(而不是单 texture)从 OLD_T1 原子复用为 NEW_T0;只上传 NEW_T1 bundle。style reload/context loss 清空 renderer-owned resource,Source timeline 保留;下一帧从当前 immutable window 重建。Field ON/Particle OFF、Field OFF/Particle ON、双 ON 都继续合法;无 vector 时 Particle 不可用,不让 scalar 伪装 U/V。
## Vector semantics
`VELOCITY` 保持当前 Wind/Tide 行为:T0/T1 component-wise mix,m/s 地理推进,`advectionScale=1` 默认。
`DIRECTION` 用 unit U/V 表示方向,不代表速度。先 component-wise mix,再 normalize;若长度非 finite 或小于冻结阈值(建议 `1e-5`)则 cell invalid、粒子不移动/respawn,fail-closed。反向邻帧在 mix 约 0.5 归零亦遵此规则,不能任选方向。实际视觉推进量只由 particle renderer 的 `advectionScale`(及现有 visual clock)控制,必须明确由宿主/renderer 配置提供,不能从 Wave height/period 或业务名称推算。本任务未定义 Wave physics。
## WeatherV2→NavSea→V3 映射
|业务输入|NavSea canonical V3|Field renderer|Particle|
|---|---|---|---|
|Wind|vector U/V, `VELOCITY`|`VECTOR_MAGNITUDE`|`VELOCITY`|
|Tide/Current|vector U/V, `VELOCITY`|`VECTOR_MAGNITUDE`|`VELOCITY`|
|Rain|scalar rainRate + shared valid|`SCALAR`|unavailable|
|Wave|scalar height + unit direction-to U/V, `DIRECTION`|`SCALAR`|`DIRECTION`|
Wave period 留在 NavSea model/cache,不增加第二 scalar;Rain precipitation type 未写入 NSFT,V3 不推断或补造。NavSea 在拼接 257 tile 时保持 `(tiles*256)+1`,Core 不理解 tile。
## 受影响文件矩阵与风险
|区域|预计修改|
|---|---|
|Core|`field_data_adapter.*`、`local_particle_field_feed.cpp`、`raster_array_source.*`、fixtures/tests|
|shader/renderer|`render_weather_color_layer.cpp`、`render_particle_validation_layer.cpp`、`particle_gpu_validation.fragment.glsl`、UBO/manifest/generator 后端产物|
|Android|`WeatherRuntime.java`、`MapLibreMap.java`、`native_map_view.*`、JNI registration|
|RN|`types.ts`、`FieldRuntime.ts`、`NativeWeatherRuntimeBridge.ts`、Kotlin TurboModule/codegen、package version|
|Test Host|style fragment、v1 regression 与 Wind/Rain/Wave/synthetic fixtures;不引入业务逻辑到 package|
风险:plane 增加 GPU memory、3 URI read 后 Java/C++ copy 峰值、texture precision/scale、v1/v2 ABI 共存、direction opposite interpolation、style/context lifecycle 和 timeline compatibility。缓解是 bounded total bytes、明确 channel-presence compatibility、bundle-level reuse、fail-closed 和先 Linux/Core verification。
## 实施批次与门槛
|批次|修改范围 / 不修改范围|完成门槛 / 下一入口|
|---|---|---|
|V3-02 Core Contract + Scalar Adapter|v2 descriptor/snapshot/parser、v1 normalize;不改 GPU/JNI|单元/fixture覆盖三组合、invalid/compatibility;进入 V3-03|
|V3-03 GPU + Generic Field|scalar/vector/validity bundle、`FieldValueSource`;不改 particle semantics/JNI|OpenGL Core fixture:scalar 与 magnitude 不混读;进入 V3-04|
|V3-04 Vector Semantics|DIRECTION normalize、advectionScale;不改 Wave physics|VELOCITY Wind 回归、opposite/zero fail-closed;进入 V3-05|
|V3-05 Android/JNI/RN ingress|v2 plane refs、新 `.3`,保留 v1 thin ingress|payload limits/ownership/static checks;进入 V3-06|
|V3-06 fixtures|真实 Wind/Rain/Wave,Tide 回归、wave synthetic|T0/T1/MIX/reuse/style/visibility/gap coverage;进入 V3-07|
|V3-07 Linux freeze|Core/OpenGL 精确验证与文档|双 renderer/lifecycle/perf evidence;进入唯一 Android 周期|
|V3-08 Android 综合验收|一次 AAR/APK/device;不分散小周期|Wind/Rain/Wave/Tide、三 visibility 组合、seek/play/pause/rollover/style reload、hard-gate log 与 memory/perf evidence|
Wave synthetic fixture 固定:scalar west-low→east-high、vector 全场 north;色场必须只东西渐变,粒子必须只向北。所有 fixture还覆盖 T0/T1 scalar、T0/T1 vector、共同 MIX、OLD_T1→NEW_T0、only NEW_T1 upload、invalid/gap fail-closed。

View File

@@ -0,0 +1,68 @@
# MapLibreParticle QBIN V3 Decoder 与 Block Geometry 实施决策 v1
## 1. 结论
STEP-086 将 `QBV3` / version 3 作为新的压缩天气场容器格式接入 MapLibre Native。生产 Decoder 固定使用 **16×16 Block Geometry**,不在运行时尝试 16×16 / 32×32 双解码,也不通过启发式猜测块尺寸。
既有 `QBV2` 解码链继续保留。`EncodedFieldIngress` 依据 magic 显式分流:`QBV2` → `QbinV2Decoder`,`QBV3` → `QbinV3Decoder`;version 由对应 Decoder 严格校验。解码完成后继续统一进入 `FieldProfileValidator`,不改变既有 profile contract 分层。
## 2. Block Geometry 决策依据
使用仓库既有 Current / Rain / Wave / Wind 四类真实 Golden 数据,共 T0/T1 8 帧,按 v2.2 冻结稿规则实现确定性的 C++ benchmark encoder/decoder,对 16×16 与 32×32 做相同输入对比。
| 指标 | 16×16 | 32×32 | 结论 |
|---|---:|---:|---|
| 数值 payload 总字节 | 2,470,419 | 2,514,288 | 32×32 增加 43,869 字节,约 +1.7758% |
| 总 block 数 | 22,704 | 5,984 | 32×32 block 更少 |
| 非空 block 数 | 19,120 | 5,190 | 32×32 block 更少 |
| C++ encode 总时间 | 218.713 ms | 200.101 ms | 32×32 约快 8.5% |
| C++ decode 总时间 | 104.121 ms | 101.451 ms | 32×32 约快 2.6% |
8 帧合计 decode 差异约 2.67 ms,平均每帧约 0.33 ms;相比之下,32×32 对 payload 尺寸有稳定负收益,Rain 两帧的增幅尤其明显。QBIN V3 的核心目标之一是降低网络、缓存和跨 RN/Native 传输成本,因此选择 **16×16**。
此决定同时冻结:
- `QbinV3Decoder::BlockWidth = 16`;
- `QbinV3Decoder::BlockHeight = 16`;
- Golden Fixture manifest 中 blockWidth/blockHeight 均为 16;
- 后续生产 Writer 必须输出相同 ABI,不得自行改为 32×32。
## 3. Decoder 责任边界
`QbinV3Decoder` 只负责把已取得的 QBV3 二进制解码成现有 `DecodedFieldFrame`。它不负责 Weather provider、HTTP、下载、重试、缓存、manifest 获取、路线调度或 React 业务逻辑。
实现包含:
- 固定 Header / Descriptor 布局与 little-endian 读取;
- file size、plane count、cell count、decoded bytes 等硬上限;
- CRC32 校验;
- canonical descriptor 排序、唯一键、16-byte packing 与 padding 校验;
- VALIDITY 唯一性、长度、unused bits 校验;
- Numeric dtype 约束与 all-invalid 零长度编码;
- 16×16 block 的 Predictor + ZigZag + Rice 解码;
- Direction 的 canonical scale、环形预测与 half-circle tie 规则;
- 非法 Rice-K、截断、额外字节、非零 bit padding、残差越界等 fail-closed;
- 解码后构造既有 `DecodedFieldFrame`,继续使用既有 FieldProfileValidator。
## 4. Golden Fixtures
正式 QBIN V3 Golden Fixture 位于:
`integration/react-native/MapLibreParticleRNTestHost/assets/qbin-v3-golden/`
覆盖:
- current:T0 / T1,513×513;
- rain:T0 / T1,513×513;
- wave:T0 / T1,513×769;
- wind:T0 / T1,513×769。
`manifest.json` 固定每个文件的 bytes、SHA-256、width、height 以及 16×16 Block Geometry。校验脚本为:
`integration/react-native/MapLibreParticleRNTestHost/scripts/verify-qbin-v3-golden-fixtures.py`
8 个 V3 fixture 已与对应 V2 canonical raw 解码结果做逐 plane / validity byte-for-byte 对比,结果一致。
## 5. 兼容性与版本
本次不移除 QBV2,不改变 RN `submitEncodedField` 公共调用形态,不把 V3 压缩细节暴露给 JS/TS。Native / RN 发行版本统一升级为 `13.4.1-particle.9`。

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,53 @@
# STEP-081 iOS Metal Renderer 与真机 Smoke 入口设计 v1
状态:CLOSED / PASS。已在 Pleiades(iPad mini 5 / iPadOS 26.5.2)完成签名安装、Metal runtime、deterministic fixtures 与 QBIN V4 T0/MID/T1/Clear 真机验收。
## 目标和边界
Metal 路径消费既有 `RasterArraySource` V4 renderer projection、`WeatherColorLayer` 与 `RasterParticleLayer`。没有 iOS Field model、QBIN decoder 分支或公开 iOS SDK API。Smoke 代码仅在 `platform/ios/app/MBXViewController.mm`,RN iOS bridge 仍是明确非目标。
## OpenGL → Metal pass 对照
| `u_pass.x` | OpenGL 既有语义 | Metal `ParticleGpuValidationShader` |
| --- | --- | --- |
| `< 0.5` | State A/B update / seed / respawn | 同一 T0/T1 decode、timeMix、VELOCITY/DIRECTION advection 与 fail-closed state write |
| `< 1.5` | Trail fade | 同一 retention fade;legacy validation marker 保留 |
| `1.5–3.5` | Composite / FrozenComposite | 同一 trail/particle 色彩、mask、speed-color 和 legacy diagnostic 分支 |
| `> 3.5` | Trail particle line draw | State read/write displacement line,respawn alpha 抑制跨屏线 |
| `> 4.5` | WeatherColor | 同一 scalar/vector magnitude、stops/ramp 和 invalid discard |
| `> 5.5` | 无 | Metal-only current-endpoint 1 px quad coverage;只补 raster coverage,不改变 State、速度、时间步或 previous→current 物理长度 |
MSL 使用 `ParticleGpuValidationUBO` 的同字段 vec4/float4x4 布局;声明与 C++ 的 `alignas(16)` 相同,并在 MSL 中静态断言 `31 * 16` 字节。此 Step 未改动 C++ UBO、QBIN V4 contract 或 OpenGL source。
## Metal binding
| 类别 | Metal index | Stable ID |
| --- | ---: | --- |
| vertex attribute `short2 a_pos` | attribute 0 | `idParticleGpuValidationPosVertexAttribute` |
| UBO | buffer `drawableReservedUBOCount` | `idParticleGpuValidationUBO` |
| previous | texture/sampler 0 | `idParticleGpuValidationPreviousTexture` |
| mask | texture/sampler 1 | `idParticleGpuValidationMaskTexture` |
| field T0 | texture/sampler 2 | `idParticleGpuValidationFieldTexture` |
| field T1 | texture/sampler 3 | `idParticleGpuValidationNextFieldTexture` |
| state | texture/sampler 4 | `idParticleGpuValidationStateTexture` |
这些 index 来自 Metal `ShaderSource` 的 `attributes`/`textures` 表以及 existing shader IDs,而非 OpenGL location 推测。注册位于 `src/mbgl/mtl/renderer_backend.cpp`,CMake source/include list 位于 `cmake/metal.cmake`。
## Smoke 数据链
Test App Runtime Styling 菜单新增 `Particle / QBIN V4 Smoke (T0)`。点击后可选 T0、midpoint、T1 或 clear/release。它从 app bundle `ParticleSmokeFixtures/T0.qbin`、`T1.qbin` 读取现有 RN Wind Golden fixtures;`platform/ios/app/CMakeLists.txt` 在构建后从唯一 fixture 来源复制,不创建第二种数据格式。
private `MLNMapView` seam 提供 `mbglMap`。Smoke 直接创建 `RasterArraySource`,提交 `fixture.wind` T0/T1、Wind profile contract、ColorBinding 与 VectorComponents ParticleBinding,再添加 `WeatherColorLayer` 与 `RasterParticleLayer`。T0=1789239600000、midpoint=1789241400000、T1=1789243200000,全部通过 source-owned requested forecast time 选择窗口/mix。clear 先移除 layers 后移除 source,允许重复创建。Wind Golden fixture 实际范围为 112.5E..157.5E、0..55.776573N,Test App smoke 会把相机置于约 27.8883N / 135E、zoom 3,保证 FIELD_READY 后画面可观察;这只是 Test App 可视化入口,不改变 Core 数据范围。
## Metal 真机收口结论
真机调试确认两个 Metal 特有问题并形成正式修复:
1. `ShaderProgram::getRenderPipelineState()` 的缓存复用 key 原先只包含调用方 reuse hash,没有包含 color/depth/stencil attachment pixel format。粒子 State 使用 RGBA32Float、Trail 使用 RGBA8Unorm、主 renderable 常见 BGRA8Unorm,同一 shader 因此可能复用到不兼容的 pipeline。修复后 cache key 显式合并 color/depth/stencil format;物理 iPad 上 Trail RGBA8 写入从全零恢复为有效像素。
2. deterministic Vortex 实测真实 previous→current 线段平均约 0.065 px、最大约 0.24 px,全部小于 0.5 px。Metal `MTLPrimitiveTypeLine` 对该亚像素长度没有稳定可见覆盖。正式 previous→current line 保持不变,Metal 额外在真实 currentPosition 绘制 1 px quad coverage;不拉长轨迹、不改变 State、advection、时间步或物理速度。
同时确认 Metal vertex-stage texture/sampler binding、State A/B RGBA32Float、Trail A/B RGBA8、矩阵投影与 Composite 均可工作。
## 后续边界
STEP-081 已关闭。下一轮如需 iOS RN,必须另行设计桥接;不得把本 App 的 private Core seam 提升为 SDK 或 NavSea API。当前 Metal vertex+fragment 双阶段纹理绑定可在后续独立技术债中收窄为 stage-specific binding,但不得在 STEP-081 收尾中继续重构。

View File

@@ -0,0 +1,27 @@
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行设计 v1
状态:IMPLEMENTED / WAITING_MAC_XCFRAMEWORK_VALIDATION
## 公开边界
新增 `MLNParticleFieldRuntimeConfiguration`、`MLNParticleFieldRuntime` 和 `MLNMapView (ParticleFieldRuntime)`。公开头只依赖 Foundation 与已有 public Objective-C 前置类型;QBIN 以 `NSData` 传入,不暴露 `mbgl`、STL、Core 指针或 `MLNMapView_Private.h`。
Configuration 保存 Host-neutral 的 `sourceIdentifier`、`weatherColorLayerIdentifier`、`particleLayerIdentifier`。它不硬编码 RN ID;后续 RN 可继续传 `rn-weather-fields`、`rn-weather-color`、`rn-weather-particles`。
## 生命周期与 fail-closed
Runtime 被 `MLNMapView` 的 associated object 持有,Runtime 只弱引用 MapView。replace 先 dispose 并移除旧实例,attach 仅在当前 style 同时存在正确 Core 类型的 `RasterArraySource`、`WeatherColorLayer`、`RasterParticleLayer` 且两个 layer 都引用该 source 时成功。任何不满足条件的 style-ready/source/type/layer 错误都以 `NSError` 返回,不创建 source/layer,不替换 style。
每条命令重新从 current MapView/current style 查找并验证三对象;不缓存 `RasterArraySource*` 或任何跨 style 生命周期 Core pointer。因此 style reload 后旧 runtime 不会操作悬空对象,而是 fail-closed,Host 应在 style ready 后 replace/attach。
## V4 转发
`submitEncodedField` 先守住与 Android 相同的 UTF-8 ID 1..64、contract 1..256 KiB、QBIN 1..64 MiB、generation>0 边界,再调用 Core `parseResolvedFieldProfileContractJson(...)` 和 `RasterArraySource::submitEncodedField(...)`。十个 Core stable result 逐值映射为 ObjC enum;facade 生命周期错误不会伪装成 submit result。
Color binding 传递 fieldKey、semantic/group、SCALAR/VELOCITY_COMPONENT/DIRECTION_ANGLE 和 expected unit。Particle binding 保留 VECTOR_COMPONENTS 与 DIRECTION_ANGLE,inactive selector 严格为 `(0, 0)`。forecast time/play/pause/speed 和 clear 都只转发给 Core;T0/T1/mix、boundary reuse、QBIN decoder 与 renderer 不在 ObjC 实现重写。
## 导出与 distribution
`MLNParticleFieldRuntime.h` 列入 `MLN_IOS_SDK_HEADERS`,由现有 `public_hdrs` 进入 `MapLibre.dynamic`,并由 `Mapbox.h` 导入。`MLNMapView_Private.h` 继续仅列于 private list。
`release/maplibre-particle/ios/` 建立 module 名不变的 SwiftPM binary template。后续 STEP-083 的链路固定为:`reactTag -> React View Registry -> Fabric ComponentView -> contentView -> MLNMapView -> public MLNParticleFieldRuntime`。当前 peer dependency 是 MLRN 11.3.6;STEP-083 不建立第二个 Map registry、不把 MLRN private header 作为长期 API,并用 `$MLRN_SPM_SPEC` 令 MLRN 与 Bridge 消费同一 MapLibre binary。

View File

@@ -0,0 +1,45 @@
# STEP-085 V4 Renderer 性能修复设计 v1
状态:`IMPLEMENTED / WAITING_FOR_RDC_NATIVE_BUILD`
## 目标与根因
V4 的 `RasterArraySource` 通过独立的 field-keyed window 产出已经 materialize 的 T0/T1/MIX。旧 renderer 将 V4 误当作“没有 timeline”,从而产生两项错误:WeatherColor 的 `needsRepaint()` 永远为 true;Particle 也永远请求下一帧,并在 `prepare()` 将包含大 vector/scalar/valid 数组的 snapshot 按值复制。
本设计保持 V3 timeline、V4 Runtime Field Window、QBIN 和所有 RN/Host 公共接口不变,只在 Source 内部和共用 renderer seam 修复 cadence/lifetime。
## 共用 cadence 语义
新增 Source-owned `RasterArrayRendererCadence`。它只负责 visual simulation 的 deadline,不携带 field、不会变更 T0/T1/MIX,也不会调用 ForecastClock。
| targetFps | Core cadence | platform wake |
| --- | --- | --- |
| AUTO | 每个 MapLibre animation frame 可消费一次 | 沿用平台 animation-frame 语义 |
| 30 | 约 33.3ms 才允许一次 simulation | Android Handler / iOS NSTimer 约 34ms 请求一次 repaint |
| 60 | 约 16.7ms 才允许一次 simulation | Android Handler / iOS NSTimer 约 17ms 请求一次 repaint |
`RenderRasterParticleLayer::needsRepaint()` 仅对 active、visible、field-ready 的 V4 Particle 在 deadline 到期时返回 true。因 camera 而来的额外 frame 仍可 composite 当前 trail,但 `consumeIfDue()` 未成功时不建 State/Trail pass、不推进 ping-pong、不衰减 trail。Forecast PAUSE 不影响该 visual cadence。
Android/iOS runtime timer 仅是已有 facade 的“下一帧 wake”薄层;deadline 与是否推进由共用 C++ Source cadence 判定。hidden、clear、disable 和 dispose 都停止 V4 Particle wake。
## WeatherColor idle 与 drawable
V4 WeatherColor 在 PAUSE、field/window/stops/opacity/transform 不变时 `needsRepaint()==false`。它不再以 `sourceTimeline == nullptr` 作为“永远动画”的代理。
稳定状态下 WeatherColor drawable 复用既有 LayerGroup drawable。T0/T1 texture、mix、opacity、color stops 与 value source 由 Resources/Tweaker 在 render 时更新 UBO;仅 wrap 集合变化才 `clearDrawables()` 后重建。该方案保留 gfx abstraction,未引入 OpenGL/Metal 直接调用。
## V4 snapshot lifetime
materialize 仍发生在 Source 的 binding/window 真正变化时,但 `Materialized*Projection` 与 renderer `sourceField/sourceNextField` 全部改为 `shared_ptr<const ParticleFieldSnapshot>`。Source Impl 的不可变副本持有 projection,RenderLayer 只复制 shared handle;稳定 window 的 `prepare()` 不再深复制 scalar/vector/valid arrays。旧 V3 optional snapshot 路径仍在 Source 变化时构造独立 handle,未改变其 API。
## Debug/Validation observability
`include/mbgl/particle/renderer_debug_counters.hpp` 在非 release 建立低成本 relaxed atomic 累计器,release 编译为 no-op。覆盖:两 layer 的 needsRepaint/update、particle simulation advance、WeatherColor/Particle drawable rebuild、field texture upload、GPU resource create/release-reset、trail create/resize、V4 projection materialize 与 field-window revision。它不逐帧写普通日志,也不暴露给 NavSea。
## ForecastClock 审查
全仓确认 `RasterArraySource::advanceForecastClock(elapsedMs)` 只有定义,当前 Android WeatherRuntime、iOS `MLNParticleFieldRuntime` 与 RN bridge 均没有 production caller。故 V4 PLAY 自动推进尚无 owner;本 Step 不将 cadence timer 偷接成第二个 forecast clock。Host 显式 `setEncodedForecastTime` 继续触发 window refresh;自动 forecast playback 应单独设计 owner 后处理。
## 生命周期边界
本 Step 不改变 `detach()`、dispose、Host clear/destroy contract,也不对 retained allocation/Native Heap 归因。计数器和减少 repaint/copy/rebuild 仅为后续 RDC 采样提供证据。

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,532 @@
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 架构冻结 — Codex 任务 v1
## 0. 任务定位
本任务是 MapLibreParticleLab V3 的第一步:**只读调查当前真实实现,并冻结 V3 通用 Scalar / Vector Field Contract 与迁移方案。**
本任务不直接修改 C++、shader、JNI、Kotlin/Java、React Native Runtime 或 Android AAR。
V3 的目标不是“增加 Rain/Wave 两种天气类型”,而是把当前主要围绕 WIND + interleaved U/V 的运行时升级为:
> provider-neutral、business-neutral 的二维时序 Scalar / Vector Field Rendering Runtime。
当前已冻结的上游边界:
```text
WeatherV2 -> NSFT -> NavSea JS/TS -> Canonical Field -> MapLibreParticleLab V3
```
WeatherV2 统一向 NavSea 提供 NSFT;NavSea JS/TS 负责网络、缓存、NSFT 解码、tile 拼接、业务单位与 canonical 化;MapLibreParticleLab 从 canonical field 开始接管。
## 1. 仓库与执行基线
仓库:
```text
/mnt/sda1/MapLibreParticleLab
```
分支:`particle/main`
任务书生成时 HEAD:
```text
2e3ab26275a8d1873d764cf6927783a7b0406836
```
该 HEAD 已完成并冻结 STEP-064 RN 纯 Field Renderer `13.4.1-particle.2`。
执行开始时必须重新确认:
```bash
git branch --show-current
git rev-parse HEAD
git rev-parse origin/particle/main
git status --short
```
若远端已合法推进,先阅读新增提交后再继续;禁止 reset、checkout 覆盖或回退用户工作。
当前工作区已知存在与本任务无关的用户修改和历史未跟踪文件,特别包括 STEP-062 文档、Android/Expo/build 目录、旧任务书和 tgz。
必须遵守:
- 禁止 `git add .`、`git add -A`。
- 禁止删除、恢复、格式化、移动无关 modified/untracked 文件。
- 本任务只允许新增/修改 STEP-065 明确列出的文档产物和本任务书自身。
- 本任务不允许以“清理工作区”为理由处理历史文件。
## 2. 执行前必须阅读
至少完整阅读:
```text
AGENTS.md
PARTICLE_BASELINE.md
docs/00_项目规范/01_Codex协作规范_v1.md
docs/00_项目规范/02_构建与验证规范_v1.md
docs/00_项目规范/03_Step阶段记录规范_v1.md
docs/00_项目规范/05_粒子架构原则_v1.md
docs/40_任务/STEP-064_RN纯FieldRenderer接口V2_Codex任务_v1.md
docs/50_Step记录/STEP-064_RN纯FieldRenderer接口V2_v1.md
docs/报告/STEP-064_RN纯FieldRenderer接口V2_Codex完成报告_v1.md
```
并重点阅读当前真实实现:
```text
include/mbgl/particle/field/field_data_adapter.hpp
src/mbgl/particle/field/field_data_adapter.cpp
所有 Timeline / Runtime Field Window 实现
WeatherColorLayer 相关 C++ / shader / shader generator
RasterParticleLayer 相关 C++ / particle simulation shader
GPU field texture / upload / reuse / lifecycle 实现
Android Host WeatherRuntime / JNI submission
packages/maplibre-particle-rn/src/index.ts
packages/maplibre-particle-rn/src/types.ts
packages/maplibre-particle-rn/src/FieldRuntime.ts
packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts
integration/react-native 中当前正式 Consumer/Test Host
```
必须通过当前源码回答问题,不得根据历史任务书猜实现。
## 3. WeatherV2 外部输入事实与只读核对
WeatherV2 实际目录:
```text
/root/sourceserver/weatherV2
```
可只读核对以下文件,不得修改 WeatherV2:
```text
src/dev/core/nsft.py
src/dev/core/field_pmtiles.py
src/dev/layers/wind.py
src/dev/layers/tide.py
src/dev/layers/rain.py
src/dev/layers/waves.py
```
已调查结论必须作为 V3 设计输入:
- Wind NSFT:物理速度 U/V,单位 m/s,vector-only。
- Tide/Current NSFT:物理速度 U/V,单位 m/s,vector-only。
- Rain NSFT:rain rate scalar + valid mask;当前 NSFT 不保存 precipitation type。
- Wave NSFT:height scalar + period scalar + normalized direction-to U/V;direction U/V 不是 m/s。
- NSFT grid 为 `wm_grid_257`,NW origin,行 north->south,列 west->east。
- 同 zoom 相邻 257 tile 有共享边界,NavSea 拼接后 canonical grid 可保持 `(tiles*256)+1` 规则,无需 Core 理解 tile。
- Forecast target time 必须按实际 validTime/time_index 使用,Core 不假设固定 1h cadence。
这些事实如与当前 WeatherV2 代码不一致,报告差异并以当前代码为准。
## 4. V3 冻结边界
V3 Core/Native 不得负责:
- NSFT / PMTiles / WeatherV2 解析;
- HTTP、provider、manifest、time_index、cache、retry、prefetch;
- ECMWF / CMEMS 等业务来源;
- Wind / Rain / Wave / Tide 业务分支;
- kt、mm/h、m、m/s 等宿主业务单位转换;
- NavSea 当前 Layer、Forecast UI 或业务状态。
V3 从 Canonical Field Submission 开始负责:
- descriptor / payload validation;
- Snapshot;
- Timeline / target time / playback;
- T0/T1/MIX;
- Runtime Field Window;
- OLD_T1 -> NEW_T0 reuse;
- GPU channel resource 生命周期;
- Field Renderer;
- Particle Renderer;
- MapLibre style/viewport lifecycle recovery;
- fail-closed。
## 5. 必须冻结的最小 Canonical 模型
V3.0 只设计:
```text
FieldSnapshot
├─ common descriptor
├─ scalar channel? 0 or 1
├─ vector channel? 0 or 1 (U/V)
└─ shared validity
```
禁止第一版扩成 N scalar / N vector generic graph。
必须支持三种合法组合:
```text
vector-only
scalar-only
scalar + vector
```
Neither 必须 invalid / reject。
优先扩展当前 `ParticleFieldDescriptor / ParticleFieldSnapshot / ParticleFieldDataAdapter`,禁止并行创建 `GenericWeatherField`、`WindFieldV2` 等第二套重复模型。
当前 `fieldType == "WIND"`、`displayDomain == "GLOBAL"` 等硬编码必须被调查并给出 V3 最小迁移方案;Core 最终不得靠 WIND/WAVE/RAIN/TIDE 决定数据能力。
## 6. Renderer Contract 必须与数据 Contract 分离
必须调查并冻结以下原则:
`FieldSnapshot` 只描述“有什么数据”;Renderer config 描述“如何画”。
第一版 Field Renderer 的通用值来源建议为:
```text
SCALAR
VECTOR_MAGNITUDE
```
即:
- `SCALAR`:色场读取 explicit scalar channel;
- `VECTOR_MAGNITUDE`:色场读取 `sqrt(U^2 + V^2)`。
不得让 Renderer 根据 business type 或“有没有 scalar”偷偷猜来源。
必须调查当前 WeatherColorLayer/shader 是否硬依赖 `length(U,V)`,以及将其泛化所需最小接口、uniform/property、GPU resource 与 style property 改动。
`FieldColorStop.value` 的 RN 公共语义保持 generic;调查 Native/JNI/C++ 内 `speedMps` 命名实际范围,并给出兼容迁移路径。
## 7. Vector Semantics 必须业务无关
必须调查当前 Particle Integrator / shader 是否隐含:
```text
U/V = physical velocity (m/s)
```
V3 需要覆盖至少两类数学语义:
```text
VELOCITY
DIRECTION
```
目标映射:
- Wind/Tide -> VELOCITY;
- Wave direction-to U/V -> DIRECTION。
不得在 Core 增加 Wind/Wave/Tide enum 来解决。
对于 DIRECTION,设计必须说明:
- T0/T1 vector mix 后是否 normalize;
- 接近零向量/接近反向插值如何 fail-closed;
- particle movement 的独立 advection scale 放在哪一层;
- 如何保证 VELOCITY 当前 Wind 行为不被改变。
本任务只冻结方案,不实现 Wave physics。
## 8. Binary Submission / Channel Plane 设计
当前 RN 公共入口是:
```text
submitField(metadataJson, payloadFileUri)
```
当前 Core canonical payload 是:
```text
float32-le / interleaved-uv
```
必须调查 RN -> Android bridge -> JNI -> Core 的真实 ownership、文件读取、byte[]/buffer、size limit 与 decode 流程。
V3 优先评估独立 plane:
```text
metadata
vectorPayload? // float32-le interleaved U/V
scalarPayload? // float32-le scalar plane
```
不得使用巨大 JS number[]、JSON Array 或 Base64 作为正式大数据通路。
必须明确:scalar-only、vector-only、scalar+vector 三种提交如何表达;文件引用是一个 container 还是两个 file URI;JNI/Core 最小修改面是什么。
独立 plane 是首选方向,但最终冻结必须依据当前代码,不能为了迎合任务书而忽略更安全的现有接缝。
## 9. Timeline / Runtime Window 保护项
以下现有能力视为 V3 保护基线,不得在设计中推倒重来:
```text
Core-owned Timeline / Playback
T0 / T1 / MIX
正常连续播放 simulationRebuild=false
正常连续播放 trailCleared=false
Runtime Field Window
OLD_T1 -> NEW_T0 reuse
only NEW_T1 upload
Pause 语义
Invalid / Gap fail-closed
WeatherColorLayer 与 RasterParticleLayer 分离
```
V3 设计要说明如何把 Snapshot GPU resource 从单 vector texture 扩展成:
```text
SnapshotGpuResources
├─ scalar texture?
├─ vector texture?
└─ validity resource
```
并保持整个 Snapshot 在 rollover 时一起 reuse,Host 不参与 GPU reuse。
## 10. Canonical schema 版本决策
必须明确回答:
```text
maplibre-particle-field/v1 additive extension
还是
maplibre-particle-field/v2
```
当前已知 v1 descriptor 是单 `layout/encoding/byteLength/stride`,Snapshot 只有 `velocity`,RN `submitField()` 只有一个 payload file URI,且 adapter 严格接受 `interleaved-uv`。
因此本任务的默认判断倾向:**如果 Scalar + Vector 会导致字段重载、隐式兼容或 ambiguous payload,则应正式升 v2,而不是污染 v1。**
但最终结论必须根据完整 parser/bridge/fixture/consumer 调查给出。
如果选择 v2,设计必须说明:
- v1 Vector-only 是否继续可读;
- compatibility 应放在哪个最薄层;
- 是否允许 v1/v2 同时进入同一个 Runtime;
- 新 RN package 版本至少应升到 `13.4.1-particle.3`,不得覆盖已发布 `.2`;
- Native/AAR version 如何独立推进。
不得为了“兼容”长期维护两套 Runtime。
## 11. Validity 与业务映射冻结
V3.0 优先保持一个 Snapshot 一个共享 validity,不提前设计 channel-specific mask。
必须给出四类 WeatherV2 -> NavSea canonical -> V3 的映射表,至少明确:
```text
Wind : vector(U/V velocity) -> field=VECTOR_MAGNITUDE, particle=VELOCITY
Tide : vector(U/V velocity) -> field=VECTOR_MAGNITUDE, particle=VELOCITY
Rain : scalar(rainRate) -> field=SCALAR, particle=unavailable
Wave : scalar(height) + vector(direction-to U/V)
-> field=SCALAR, particle=DIRECTION
```
Wave `period` 当前保留在 NavSea Weather model/cache,V3.0 不因此增加第二 Scalar Channel。
Rain `precipitation_type` 当前未进入 NSFT,因此 V3 不推断、不补造该数据。
Field/Particle visibility 必须继续独立:
```text
Field ON / Particle OFF
Field OFF / Particle ON
Field ON / Particle ON
```
没有 vector 时 Particle unavailable / reject,不允许 scalar 伪装成 U/V。
## 12. 必须输出的调查结论 A-J
设计文档必须逐项回答:
A. 当前 Field Contract 实际结构与 Wind-only 硬编码点。
B. 当前 GPU Texture / upload / validity / shader 的实际结构。
C. 当前 Particle Integrator 对 vector 数值、单位、dt、scale 的实际假设。
D. 当前 RN -> Android -> JNI -> Core binary submission 路径与 ownership。
E. Scalar Channel 的最小扩展方案与受影响对象。
F. Wave direction-only 的最小、业务无关表达方案。
G. v1 additive extension 还是 canonical v2,并说明理由。
H. 预计受影响文件清单,按 Core / shader / Android / RN / Test Host 分类。
I. 向后兼容、性能、GPU memory、lifecycle、Timeline 风险。
J. 推荐的正式实施批次、验证门槛和 Android 最终一次综合验收范围。
## 13. 推荐实施路线必须被审查并冻结
设计结果至少应评估以下批次是否合理:
```text
V3-02 Core Contract + Scalar Snapshot/Adapter
V3-03 Scalar GPU Texture + Generic Field Renderer
V3-04 VectorSemantics VELOCITY/DIRECTION + Particle
V3-05 Android/JNI/RN generic binary submission
V3-06 Wind/Rain/Wave 三类 fixture + Tide 回归
V3-07 Linux/Core/OpenGL 精确验证与文档冻结
V3-08 用户一次 Android build/install/device 综合验收
```
可以根据当前代码合并或调整批次,但必须遵守:
- 尽量减少 Android 重复编译/安装周期;
- 在 Linux/Core 可验证的内容先集中完成;
- 不因单一小按钮/日志单独触发 Android 周期;
- 高风险接口分叉必须在 Android 实现前冻结。
最终路线要给每个批次明确“修改范围 / 不修改范围 / 完成门槛 / 下一批入口”。
## 14. Validation Fixture 规划
V3 后续至少规划三类数学能力 fixture:
```text
A. Vector-only : 真实 WeatherV2 Wind
B. Scalar-only : 真实 WeatherV2 Rain
C. Scalar+Vector: 真实 WeatherV2 Wave
```
Tide 作为第二套 VELOCITY vector 做回归,不单独创造业务代码路径。
Wave 必须规划一个独立性 synthetic fixture:
```text
scalar: west low -> east high
vector: 全场 north
```
预期:
```text
色场只呈东西渐变
粒子只统一向北
```
该 fixture 用于证明 Field Renderer 不偷读 vector magnitude,Particle Renderer 不偷读 scalar。
同时规划 T0/T1 scalar、T0/T1 vector、共同 MIX、window rollover、style reload、visibility、invalid/gap fail-closed 验证。
## 15. 本任务禁止事项
本任务不得:
- 修改任何生产代码;
- 修改 shader / generator;
- 修改 Android/JNI/RN API;
- 生成新的 AAR/tgz release;
- 执行 Android Gradle build、APK 安装或 ADB 验收;
- 把 NSFT decoder 放入 ParticleLab;
- 新增 Weather Provider / HTTP / cache;
- 新增 WIND/WAVE/RAIN/TIDE Core 分支;
- 实现 Wave physics;
- 设计 N-channel framework;
- 做 JSI/zero-copy 大重构;
- 加入或执行 Jest;
- 修改 NavSea 或 WeatherV2。
本任务是设计冻结任务,不是实现任务。
## 16. 必做文档输出
新增:
```text
docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md
docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md
docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md
docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md
```
并追加更新:
```text
粒子开发记录.md
```
所有文档必须使用中文,代码标识符、路径、schema/property 名保持实际英文。
设计文档必须包含至少:
- 当前真实架构图;
- V3 目标架构图;
- Scalar / Vector / validity 数据模型;
- Renderer config 与 Snapshot data 的边界;
- Binary plane 方案;
- Timeline/GPU reuse 迁移;
- API/schema 版本决策;
- WeatherV2 四层映射;
- 受影响文件矩阵;
- 风险与实施顺序。
## 17. 必做清单
执行结束前逐项对照,不得静默跳过:
- [ ] 读取全部项目规范与 STEP-064 冻结基线。
- [ ] 重新确认 branch / HEAD / origin / dirty worktree。
- [ ] 调查 Descriptor / Snapshot / Adapter 当前实际结构。
- [ ] 找全 WIND / GLOBAL / fieldType 等行为硬编码。
- [ ] 调查 Runtime Window / Timeline / MIX / GPU reuse。
- [ ] 调查 WeatherColorLayer value 获取和 `speedMps` 链路。
- [ ] 调查 Particle Integrator 的 velocity 假设。
- [ ] 调查 RN -> Android -> JNI -> Core binary submission。
- [ ] 只读核对 WeatherV2 NSFT 四层语义。
- [ ] 冻结 0/1 Scalar + 0/1 Vector + shared validity 模型。
- [ ] 冻结 FieldValueSource 与 VectorSemantics 方案。
- [ ] 冻结 binary payload / file-reference 方案。
- [ ] 明确 canonical v1 additive 或 v2。
- [ ] 给出 V3 分批实施路线与综合 Android 验收计划。
- [ ] 生成调查、设计、Step、完成报告。
- [ ] 追加粒子开发记录。
- [ ] 确认没有修改生产代码和无关用户文件。
## 18. Git 收尾要求
本任务会新增/修改项目文档,因此完成后必须 Git 收尾并 push 到公开远端 `particle/main`。
提交前必须再次执行 `git status --short`,只暂存本任务明确产物:
```text
docs/40_任务/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex任务_v1.md
docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md
docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md
docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md
docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md
粒子开发记录.md
```
禁止把任何既有 STEP-062 修改、历史 untracked、build/cache/android/tgz 一并提交。
完成后:
1. 创建 scoped commit;
2. push 到 `origin/particle/main`;
3. 确认远端 HEAD;
4. 在完成报告记录 commit hash、commit message、push 结果、最终远端分支/HEAD。
push 失败不得声称任务完成,必须明确报告原因。
## 19. 完成报告格式
完成报告必须按必做项逐项写:
```text
已完成 / 未完成 / 无法验证
```
并明确:
- 本任务是否修改生产代码:预期“否”;
- WeatherV2 是否被修改:预期“否”;
- Android Gradle / APK / ADB:预期“未执行,按任务边界”;
- Jest:预期“未加入、未执行”;
- 当前最终 V3 schema 推荐;
- 下一实施任务建议名称与范围;
- 所有未解决设计风险。
## 20. 最终完成标准
只有以下全部满足,STEP-065 才可标记 CLOSED / PASS:
1. 当前实际代码调查完整,并有文件/符号级依据;
2. V3 Scalar/Vector/validity Contract 已冻结;
3. Renderer data source 与 VectorSemantics 已冻结;
4. binary submission 与 schema version 路线已冻结;
5. Timeline/Runtime Window/GPU reuse 保护方案明确;
6. V3 分批实现和最终一次 Android 综合验收路线明确;
7. 四份正式文档 + 粒子开发记录完成;
8. 无生产代码、WeatherV2、NavSea 和无关工作区文件被修改;
9. scoped commit 已成功 push 到 `origin/particle/main`。

View File

@@ -0,0 +1,318 @@
# STEP-066 MapLibreParticleLab V3-02 Core Contract + Scalar Snapshot/Adapter — Codex任务 v1
## 0. 任务定位
本任务承接 STEP-065 已冻结的 MapLibreParticleLab V3 架构,只实施 **Core Contract / Snapshot / Adapter 层**。
目标:把当前 Wind-only、单 U/V payload 的 `maplibre-particle-field/v1` Core 输入模型,升级为 V3 内部统一的 generic snapshot,支持:
- vector-only
- scalar-only
- scalar + vector
- shared validity
- vector semantics: `VELOCITY | DIRECTION`
- v1 vector-only 作为最薄兼容 ingress normalize 到同一套 V3 snapshot
本任务不进入 GPU、shader、Android/JNI、RN、NavSea、WeatherV2,也不做 Android 构建。
## 1. 当前基线
仓库:`/mnt/sda1/MapLibreParticleLab`
分支:`particle/main`
任务书创建时 HEAD / origin:
`d23ae2079dcfef3ce48517f734fe487207e28d23`
执行时必须重新确认 branch、HEAD、origin 和 `git status --short`。若远端已合法推进,先阅读新增提交,不得 reset、checkout 覆盖或丢弃用户修改。
当前工作区已有 STEP-062 用户修改和大量历史 untracked/build 产物,全部视为无关用户内容。禁止 `git add .`、`git add -A`、clean/reset/stash 覆盖它们。
## 2. 开工前必须阅读
必须完整阅读:
- `AGENTS.md`
- `PARTICLE_BASELINE.md`
- `docs/00_项目规范/`
- `docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md`
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
- `docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md`
- `docs/报告/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_Codex完成报告_v1.md`
- 当前 `field_data_adapter.*`
- 当前 `local_particle_field_feed.*`
- 当前 `raster_array_source.*` 中 host field parser / Timeline / Runtime Window 相关实现
- 与 `ParticleFieldDescriptor`、`ParticleFieldSnapshot`、`ParticleFieldTimeWindow`、`sameFieldGpuContent`、`fieldIdentityChanged` 直接相关的所有调用点
- 现有相关 C++ tests/fixtures
不得根据 STEP-065 文档示例直接猜当前源码。
## 3. 冻结架构,禁止重新讨论
STEP-065 已冻结以下方向,本任务必须按此实现,不重新发明第二套模型:
1. 新 canonical schema:`maplibre-particle-field/v2`。
2. 一个 Snapshot:0/1 scalar + 0/1 vector + 1 shared validity。
3. scalar-only、vector-only、scalar+vector 合法;neither 非法。
4. vector 为 `float32-le / interleaved-uv`,U eastward、V northward。
5. scalar 为 `float32-le` 单 plane。
6. validity 为显式 shared logical validity;V3 Core 内统一为 `std::vector<uint8_t>` 或现有等价表示。
7. `VectorSemantics = VELOCITY | DIRECTION`,只在 vector present 时合法。
8. `fieldType` / `displayDomain` 不再作为 Core capability 或 validity 判断依据。
9. Renderer 的 `FieldValueSource` 属于后续 V3-03,不在本任务实现。
10. Timeline / T0-T1 / MIX / Pause / rollover ownership 不重构。
## 4. 本任务必须形成的 Core 模型
优先扩展现有 `ParticleFieldDescriptor` / `ParticleFieldSnapshot`,不要创建 `GenericWeatherField`、`RainField`、`WaveField` 等平行模型。
目标语义至少包含:
```text
ParticleFieldSnapshot
common identity/grid/time
scalar? : width*height float
vector? : width*height*2 float interleaved UV
valid : width*height uint8 shared validity
vectorSemantics? : VELOCITY | DIRECTION
```
字段命名可结合现有代码做最小调整,但必须 generic,不出现 Wind/Rain/Wave/Tide 业务枚举。
## 5. v2 Descriptor / Plane Bundle
本任务需要在 Core 层定义明确的 v2 descriptor 和 decode 输入形态。
推荐采用独立 plane bundle,而不是把三种数据重新塞回一个 ambiguous `std::vector<char>`:
```text
ParticleFieldPayloadBundle
scalar? bytes
vector? bytes
validity? bytes
```
具体 C++ 类型可根据当前接口选择 `std::optional<std::vector<char>>`、span/view 或等价简单结构,但不能引入大型新抽象。
必须保证:
- vector plane expected bytes = `cells * 2 * sizeof(float)`
- scalar plane expected bytes = `cells * sizeof(float)`
- validity plane expected bytes = `cells`(uint8)
- descriptor 声明的 channel presence 与实际 payload presence 一致
- 所有 float 必须 finite,除明确 noData 策略外不得接受 NaN/Inf
- shared validity 为 0 的 cell 可以允许 payload 内存在 finite placeholder/noData;validity=1 的 cell 对 present channel 必须是有效 finite 数据
- 不允许 neither
- DIRECTION 但无 vector 必须 reject
- vector present 但缺 vector semantics 必须 reject(除 v1 normalize)
## 6. v1 最薄兼容 ingress
现有 `maplibre-particle-field/v1` 必须继续可读,但不要维护第二套 Runtime。
v1 只支持现有 vector-only:
- `interleaved-uv`
- `float32-le`
- validity 从当前 U/V noData 规则派生
- normalize 后:`vector present`、`scalar absent`、`VectorSemantics::VELOCITY`
兼容层应该尽量放在 parser/adapter ingress,进入 Timeline 前就成为与 v2 相同的统一 snapshot。
不得给 v1 增加 scalar 字段、不得重新定义 v1 payload。
## 7. Identity / compatibility / hashing
必须重新审查并调整:
- `ParticleFieldSnapshot::isValid()`
- `isTimeInterpolationCompatibleWith()`
- `fieldIdentityChanged()`
- `sameFieldGpuContent()`
要求:
### isValid
不再硬编码 `fieldType == WIND` / `displayDomain == GLOBAL`。
必须验证至少:identity、generation、validTime、grid/bounds、channel presence、plane sizes、shared validity size、vector semantics 与 channel presence 的一致性。
### Timeline compatibility
T0/T1 必须至少保持:
- width/height 相同
- bounds 相同
- scalar presence 相同
- vector presence 相同
- vector semantics 相同
- plane encoding/layout 兼容
不得允许 scalar-only 与 vector-only 被当成同一 interpolation window。
### GPU content identity
虽然本任务不改 GPU,但 `sameFieldGpuContent()` 必须能够在未来正确区分 scalar/vector/validity 内容变化。
如果当前单 `fieldSha256` 已无法无歧义表示多 plane,应在 Core contract 中明确设计并实现最小 content identity,例如 per-plane hash 或一个明确的 aggregate hash。不得靠 fieldId/dataVersion 猜内容相同。
## 8. RasterArraySource host parser 范围
本任务只允许修改 host metadata/parser 到能够构造 v1/v2 Core descriptor/plane bundle 的程度。
若当前 `RasterArraySource::submitHostField(metadataText, payload)` 是唯一入口,允许在 C++ 层增加一个 **Core-only v2 overload / typed internal entry**,用于 tests/fixtures 直接提交 plane bundle。
但本任务禁止:
- 修改 Java/Kotlin API
- 修改 JNI signature
- 修改 RN API
- 修改已发布 `.2` package
因此 Android/RN 仍只能走现有 v1 入口是允许的;v2 正式跨 JNI ingress 留到 V3-05。
## 9. Local fixture / feed
若 `local_particle_field_feed` 或现有 C++ fixture 是最合适的验证入口,可做最小扩展以生成:
1. v1 vector-only fixture
2. v2 vector-only fixture
3. v2 scalar-only fixture
4. v2 scalar+vector fixture
5. invalid/neither fixture
6. channel mismatch fixture
Fixture 只用于 Core contract 验证,不做 GPU 可视化。
## 10. 必做验证
不加入 Jest。
必须使用现有 C++ 测试体系或最小精确 executable/test,至少验证:
- v1 Wind-style vector-only decode PASS,并 normalize 为 VELOCITY vector
- v2 vector-only PASS
- v2 scalar-only PASS
- v2 scalar+vector PASS
- neither REJECT
- scalar byteLength mismatch REJECT
- vector byteLength mismatch REJECT
- validity byteLength mismatch REJECT
- missing vector semantics REJECT
- DIRECTION without vector REJECT
- NaN/Inf in valid cell REJECT
- invalid cell 可按冻结策略安全接受 finite noData/placeholder
- T0/T1 compatible:same channel shape/semantics PASS
- scalar-only vs vector-only incompatible
- VELOCITY vs DIRECTION incompatible
- changed scalar/vector/validity content 不会被 `sameFieldGpuContent()` 误判为相同
执行规定的精确编译/测试,编译输出遵循全局 `$build-output-throttle`。
不得执行 Android Gradle、AAR、APK、ADB。
## 11. 回归保护线
本任务不得破坏:
- 当前 v1 canonical Wind fixture parser
- Source-owned Timeline
- Runtime Field Window
- T0/T1/MIX
- Pause semantics
- OLD_T1→NEW_T0 reuse 的现有决策逻辑
- normal rollover 时 `simulationRebuild=false / trailCleared=false` 的架构条件
- WeatherColorLayer / RasterParticleLayer 分离
本任务不要求 renderer 能消费 scalar;在 V3-03 前 scalar snapshot 进入 renderer 应保持 fail-closed/unavailable,而不是伪造成 U/V。
## 12. 禁止事项
不得:
- 修改 shader 或 shader generator
- 修改 `render_weather_color_layer.cpp` / `render_particle_validation_layer.cpp` 的渲染语义,除非纯编译适配且不改变行为;若确需适配必须在报告逐项列出
- 增加 FieldValueSource
- 实现 DIRECTION particle movement
- 增加 Wave physics
- 修改 Android/JNI/Java/Kotlin
- 修改 RN package/API/version
- 修改 Test Host JS/TS
- 修改 WeatherV2 / NavSea
- 解析 NSFT/PMTiles
- 新增 provider/network/cache
- 设计 N scalar/N vector channel framework
- 做 JSI/zero-copy 重构
- 执行 Jest
- 执行 Android build/install/ADB
## 13. 文档输出
必须新增/更新:
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
- `docs/报告/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_Codex完成报告_v1.md`
- 追加 `粒子开发记录.md`
若实施中对 STEP-065 冻结设计产生必要的小修正,必须更新 `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md` 并明确原因;禁止静默改变设计。
所有文档使用中文。
## 14. 必做清单
结束前逐项报告“已完成 / 未完成 / 无法验证”:
- [ ] 阅读规范、STEP-065 与当前实际源码
- [ ] 确认 branch/HEAD/origin/dirty worktree
- [ ] 定义 V3 Core generic snapshot/descriptor/payload bundle
- [ ] 去除 Core validity 中 WIND/GLOBAL capability hardcode
- [ ] 实现 v2 vector-only
- [ ] 实现 v2 scalar-only
- [ ] 实现 v2 scalar+vector
- [ ] 实现 shared validity
- [ ] 实现 VectorSemantics contract
- [ ] 实现 v1 vector-only thin normalize
- [ ] 调整 interpolation compatibility
- [ ] 调整 content identity / sameFieldGpuContent
- [ ] 保持 Timeline/Runtime Window ownership 不变
- [ ] 加入最小 Core fixtures/tests
- [ ] 完成精确 Linux/Core 编译/测试
- [ ] `git diff --check` PASS
- [ ] 确认无 Android/JNI/RN/WeatherV2/NavSea 修改
- [ ] 生成 Step、完成报告、追加开发记录
不得静默跳过。
## 15. Git 收尾
完成本任务规定修改与文档后,只提交本任务相关文件到当前 `particle/main` 并 push `origin/particle/main`。
禁止提交现有 STEP-062 用户修改、历史 untracked、build/cache/android/tgz。
必须使用显式文件列表 `git add -- <files...>`,禁止 `git add .` / `-A`。
完成报告必须记录:
- source/function commit hash 与 message
- docs handoff commit(如有)
- push 结果
- 最终 `HEAD`
- 最终 `origin/particle/main`
- 未完成/无法验证项
push 失败不得声称完成。
## 16. 完成门槛
STEP-066 只有同时满足以下条件才可 CLOSED / PASS:
1. v1 vector-only 继续可 decode,并 normalize 到统一 V3 snapshot;
2. v2 vector-only/scalar-only/scalar+vector 三种组合全部 Core 验证通过;
3. neither、尺寸/semantic/finite/validity 错误全部 fail-closed;
4. Timeline compatibility 已按 channel presence + vector semantics 收紧;
5. content identity 不会把 plane 内容变化误判为 GPU same-content;
6. 未改 GPU 渲染语义、Android/JNI/RN、WeatherV2/NavSea;
7. 精确 Linux/Core 编译/测试与 `git diff --check` 通过;
8. Step/报告/开发记录完成;
9. scoped commit 成功 push 到 `origin/particle/main`。
下一步仅在 STEP-066 CLOSED / PASS 后进入 V3-03:Scalar GPU Texture + Generic Field Renderer。

View File

@@ -0,0 +1,169 @@
# STEP-067 MapLibreParticleLab V3-03 Scalar GPU + Generic Field Renderer — Codex任务 v1
## 0. 任务定位
本任务承接已关闭的 STEP-066,只实施 **GPU field bundle / WeatherColor generic value source**。
目标:让 V3 scalar snapshot 首次可由 WeatherColorLayer 真正渲染,并保留 vector magnitude 色场;不实现 DIRECTION 粒子推进,不做 JNI/RN v2 ingress,不做 Android 编译。
## 1. 当前基线
仓库:`/mnt/sda1/MapLibreParticleLab`
分支:`particle/main`
任务书创建时:
`HEAD == origin/particle/main == 9dabb95e4425ffbcb863c520923f5d4b537a3feb`
执行时重新确认 HEAD、origin、`git status --short`,不得 reset/clean/stash。
STEP-062 用户修改、历史 untracked、build/cache/tgz 均视为无关内容,禁止 `git add .` / `git add -A`。
## 2. 开工前必须完整阅读
- `AGENTS.md`
- `PARTICLE_BASELINE.md`
- `docs/00_项目规范/`
- `docs/20_调查/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField现状调查_v1.md`
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
- `docs/报告/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_Codex完成报告_v1.md`
- 当前 `field_data_adapter.*`
- 当前 `render_weather_color_layer.*`
- 当前 `render_particle_validation_layer.*` 中 field texture / upload / reuse 路径
- `weather_color_layer.*`、`weather_color_stop.hpp`、相关 shader/UBO/generator 与 tests
不得按任务书猜源码;先以当前 HEAD 为准。
## 3. 冻结语义
1. `FieldValueSource = SCALAR | VECTOR_MAGNITUDE`,必须显式选择,不能根据 channel presence 猜。
2. `SCALAR` 要求 scalar present;`VECTOR_MAGNITUDE` 要求 vector present;不满足则 WeatherColor renderer fail-closed/unavailable。
3. Wind/Tide 使用 `VECTOR_MAGNITUDE`;Rain/Wave color 使用 `SCALAR`。
4. Particle renderer 本任务仍只接受 `VectorSemantics::Velocity`;DIRECTION 留 V3-04。
5. T0/T1/MIX、Pause、Timeline ownership、OLD_T1→NEW_T0 reuse、only NEW_T1 upload 的架构不变。
6. WeatherColorLayer 与 RasterParticleLayer 继续分离,但消费同一 Source-owned snapshots。
7. `FieldColorStop.value` 是 generic 值。C++ 内部 `speedMps` 命名必须在本 Step 收敛为 generic `value`/`fieldValue`;Android/JNI/RN 旧 ABI 本 Step 不修改。
## 4. 必做实现
### A. Generic FieldValueSource
在 C++ style/render 层新增最小 enum/config,推荐:`enum class FieldValueSource { Scalar, VectorMagnitude };`。
放在与 WeatherColorLayer 最贴近且不引入业务域的位置。WeatherColorLayer 必须持有明确 value source;默认值必须保持现有 Wind 行为,即 `VECTOR_MAGNITUDE`,保证旧路径兼容。
### B. WeatherColor scalar/vector GPU 输入
扩展现有 WeatherColor field texture 生成/上传,使:
- `VECTOR_MAGNITUDE`:继续从 vector U/V 得到 magnitude 语义;不得改变现有 Wind 可视结果。
- `SCALAR`:真正上传/编码 scalar 值,shader 根据 T0/T1 MIX 对 scalar 做时间插值后进入相同 generic color ramp。
- shared validity 必须进入 alpha/有效性判断;T0/T1 任一无效时按现有 fail-closed 策略不可显示错误值。
- 不允许 scalar 伪装成 U/V。
可继续复用现有 texture/UBO/shader 基础设施,但要让 field texture 的数据语义显式,不要复制第二套 WeatherColor renderer。
### C. Generic color ramp 命名
把 C++ Core/style/render/shader 中与 WeatherColor 色阶相关的 `speedMps` / 固定 speed 语义改成 generic value。至少:
- `WeatherColorStop.speedMps` → generic `value`
- 校验逻辑变成 finite、严格递增 generic value
- shader/UBO 注释和日志不再声称单位固定 m/s
- 删除/替换 `scale=0..30mps` 这类 Wind-only 日志
不得改变 RN `.2` 公共 API,也不得修改 Android/JNI/Kotlin/TS。
### D. Particle renderer 保护
本任务不得实现 DIRECTION advection。现有 RasterParticleLayer 必须显式要求:
- vector present
- `VectorSemantics::Velocity`
scalar-only 或 DIRECTION vector 必须 fail-closed,不创建/继续使用错误粒子资源。
### E. GPU reuse/content identity
复核 WeatherColor 的 T0/T1 resource reuse:
- normal boundary OLD_T1→NEW_T0 可复用相同 decoded plane bundle
- 新 T1 只上传新需要的 texture
- scalar/vector/validity 变化必须触发正确 content change
- 正常 playback 不允许因为 generic value source 改造而重建 Particle simulation 或 clear trail
如果当前 `sameFieldGpuContent()` 已满足,无需再改 Core Contract。
## 5. Shader 约束
允许修改 WeatherColor 实际使用的 shader/UBO/generator,只限完成 generic field value。必须保持:
- T0/T1 GPU 时间插值
- spatial sampling/bounds
- current vector particle shader physics 不变
- 不把 provider、WeatherV2、Rain/Wave/Tide 业务名写进 shader
- 不增加第二套专用 rain/wave shader
如果同一 shader 当前同时承载 particle/weather pass,可做最小 pass/value-source 分支,但不能借此重构整个粒子 shader 架构。
## 6. 最小验证
不加入 Jest。使用现有 C++/OpenGL 测试体系,至少覆盖:
- WeatherColor default `VECTOR_MAGNITUDE` 保持旧 Wind 行为
- scalar-only + `SCALAR` 可建立 field texture/render resources
- vector-only + `VECTOR_MAGNITUDE` 可建立资源
- scalar+vector 可由 value source 明确选择对应 channel
- scalar-only + `VECTOR_MAGNITUDE` fail-closed
- vector-only + `SCALAR` fail-closed
- T0/T1 channel/valueSource compatibility 错误不被渲染
- scalar T0/T1 MIX 数值路径正确
- vector magnitude T0/T1 MIX 不回归
- validity mask 对 scalar/vector 都生效
- DIRECTION vector 在 Particle renderer fail-closed
- existing v1 Wind fixture/source tests 回归通过
- `git diff --check` PASS
执行规定的 Linux OpenGL 精确编译/测试,遵循全局 `$build-output-throttle`。
不得执行 Android Gradle、AAR、APK、ADB。
## 7. 硬边界
本任务禁止修改 Android/JNI/Java/Kotlin、RN package/API/version、Test Host JS/TS、WeatherV2、NavSea;禁止实现 DIRECTION particle movement / wave physics;禁止解析 NSFT/PMTiles;禁止新增 provider/network/cache;禁止重构 Timeline/Playback/Runtime Window;禁止新建 RainRenderer/WaveRenderer/TideRenderer;禁止 Jest 与 Android build/install/ADB。
## 8. 文档输出
必须新增/更新:
- `docs/50_Step记录/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_v1.md`
- `docs/报告/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_Codex完成报告_v1.md`
- 追加 `粒子开发记录.md`
所有文档中文。报告逐项列出生产代码修改文件、Renderer/shader 语义变化、精确编译/测试结果、未执行 Android/Jest、未触碰无关工作区。
## 9. 必做清单
- [ ] 复读规范、STEP-065/066 与当前源码
- [ ] 引入显式 `FieldValueSource::Scalar / VectorMagnitude`
- [ ] 默认保持 VectorMagnitude 旧 Wind 行为
- [ ] WeatherColor scalar GPU path 可用
- [ ] WeatherColor vector magnitude path 回归
- [ ] shared validity 同时保护 scalar/vector
- [ ] C++ WeatherColor `speedMps` 语义改为 generic value
- [ ] Particle renderer 拒绝 scalar-only 和 DIRECTION
- [ ] T0/T1/MIX 与 resource reuse 不回归
- [ ] 最小精确 Linux/OpenGL tests PASS
- [ ] `git diff --check` PASS
- [ ] Step/报告/开发记录完成
- [ ] scoped Git commit + push `origin/particle/main`
## 10. Git 收尾
完成后只显式 `git add` 本任务文件与实际修改文件,严禁把 STEP-062 用户修改、历史 untracked、build/cache/tgz 带入提交。
创建合理 scoped commit;更新完成报告记录全部 commit hash/message、push 结果与最终远端分支。
必须 push 到 `origin/particle/main`,最终复核:
`HEAD == origin/particle/main`
push 失败不得声称完成。
完成后直接按必做清单报告“已完成 / 未完成 / 无法验证”,不要输出实施计划。

View File

@@ -0,0 +1,245 @@
# STEP-068 MapLibreParticleLab V3-04 DIRECTION 粒子推进 — Codex 任务 v1
执行状态(2026-09-11):IMPLEMENTED / WAITING_VALIDATION。C++/shader scoped 实现与 shader generator 已完成;本沙箱对最终含新增 source test 的精确编译连续在单次 30 秒终端上限前中止,未将其或全量 Ninja 写为 PASS。详见同编号 Step 记录与完成报告。
## 0. 任务定位
本任务承接已关闭的 STEP-065/066/067,在 MapLibreParticleLab V3 Core/GPU 中实现第二种通用 VectorSemantics:`DIRECTION`。
目标不是 Wave 业务实现,而是让任意 canonical direction-only vector field 可以驱动 RasterParticleLayer。Core/renderer 仍不得理解 WIND/WAVE/RAIN/TIDE、WeatherV2、NSFT 或 provider。
本任务只做到 C++/shader/Linux OpenGL 可验证闭环;不得进入 Android/JNI/RN v2 ingress,不执行 Android Gradle/AAR/APK/ADB,不加入或执行 Jest。
## 1. 仓库与基线
仓库:`/mnt/sda1/MapLibreParticleLab`
分支:`particle/main`
任务书编写时 HEAD:`89c72554c5fb5cd469bdd8ba06ff4b03a1a67ff9`
该 HEAD 已等于 `origin/particle/main`,STEP-067 为 `CLOSED / PASS`。
开工时必须重新确认 branch / HEAD / origin / `git status --short`。若远端已合法推进,先阅读新增提交,不得 reset。
当前工作区仍有 STEP-062 两处用户修改和历史 untracked/build/cache/tgz。全部保留;禁止 `git add .` / `git add -A`,禁止恢复、删除、覆盖或提交无关内容。
## 2. 必须阅读
至少阅读:
- `AGENTS.md`
- `PARTICLE_BASELINE.md`
- `docs/00_项目规范/*.md`
- `docs/30_设计/MapLibreParticleLab_V3通用ScalarVectorField运行时架构设计_v1.md`
- `docs/50_Step记录/STEP-065_MapLibreParticleLab_V3通用ScalarVectorField架构冻结_v1.md`
- `docs/50_Step记录/STEP-066_MapLibreParticleLab_V3-02_CoreContract与ScalarSnapshotAdapter_v1.md`
- `docs/50_Step记录/STEP-067_MapLibreParticleLab_V3-03_ScalarGPU与GenericFieldRenderer_v1.md`
- `include/mbgl/particle/field/field_data_adapter.hpp`
- `src/mbgl/particle/field/field_data_adapter.cpp`
- `src/mbgl/renderer/layers/render_particle_validation_layer.cpp`
- `include/mbgl/style/layers/raster_particle_layer.hpp`
- `src/mbgl/style/layers/raster_particle_layer.cpp`
- `src/mbgl/style/layers/raster_particle_layer_impl.hpp`
- `src/mbgl/style/layers/particle_render_layer_properties.hpp`
- `shaders/particle_gpu_validation.vertex.glsl`
- `shaders/particle_gpu_validation.fragment.glsl`
- `include/mbgl/shaders/particle_gpu_validation_ubo.hpp`
- 对应 generated OpenGL shader header 与现有 layer/source tests。
实现必须以实际源码为准,不得按任务书示例猜接口。
## 3. 冻结语义
V3 vector semantics 只有:
- `VELOCITY`:canonical U east / V north,物理速度,保持现有 m/s Web Mercator 推进路径。
- `DIRECTION`:canonical U east / V north,只表达方向;输入通常为单位向量,但 GPU 不可假设时间插值后仍为单位长度。
`DIRECTION` 的 GPU 规则必须是:
1. 分别解码 T0/T1 vector;
2. 按当前 `timeMix` 对 U/V component-wise 插值;
3. 检查 finite;
4. 对插值结果求长度;
5. 长度 <= epsilon 时 fail-closed;
6. 否则 normalize;
7. 乘独立的 renderer `advectionScale`;
8. 再进入现有 Web Mercator displacement 计算。
严禁:
- 先分别 normalize T0/T1 再做最终不归一化 mix;
- 使用 scalar height、period 或 vector 原 magnitude 决定推进速度;
- 将 DIRECTION 当 m/s;
- 在 Core 中出现 Wave 分支。
反向方向示例:T0=(1,0)、T1=(-1,0)、MIX=0.5,插值向量长度近零,必须 invalid/fail-closed,不得任意选择东或西。
## 4. VELOCITY 回归保护
VELOCITY 是已通过 Android 的旧正式路径。本任务必须保持默认行为:
- 不 normalize VELOCITY;
- T0/T1 仍 component-wise mix;
- 默认参数下 displacement 公式不变;
- U east / V north 与 world-y southward 的符号不变;
- latitude/cos(latitude)/Earth circumference 计算不变;
- lifecycle、respawn、Trail、T0/T1/MIX、pause、cadence、window rollover 不重写。
允许为共享 helper 重构 shader,但必须证明 VELOCITY 结果等价。
## 5. advectionScale
为 DIRECTION 增加 business-neutral renderer 控制 `advectionScale`。
优先以 RasterParticleLayer 的 C++ Core style/control property 实现,建议属性名:`raster-particle-advection-scale`,C++ API 为 `getAdvectionScale()/setAdvectionScale()`;最终命名需与现有 property 体系一致。
冻结要求:
- 默认值 `1.0`,确保不隐式引入产品级速度偏好;
- 必须 finite 且 > 0;需要上限时根据现有数值安全给出保守、文档化的界限,不得随意设为天气业务值;
- V3-04 中 `advectionScale` 只改变 DIRECTION 的推进 magnitude;VELOCITY 默认/现有运动数学不因该属性变化而改变;
- 不从 scalar、fieldId、fieldType、displayDomain 推导该值;
- RN 公共 API 暂不增加,留给 V3-05 一并接线。
如果当前 CoreOnly layer property 机制不适合新增 serialize property,Codex 可采用最小等价 C++ renderer config,但必须在报告解释原因;不得因此提前改 Android/JNI/RN。
## 6. Shader/UBO 一致性
当前 State fragment shader 和正式 particle line vertex shader都会独立读取 `u_field_texture/u_next_field_texture`。
两者必须使用相同 semantics:
`decode T0/T1 -> mix -> resolve VELOCITY or DIRECTION -> validity`
不得出现 State 按 Direction 推进、但 line vertex 仍把 raw mixed direction 当 velocity 的不一致。
建议增加明确的 UBO advection/semantics 参数,例如独立 vec4,避免挤占已有字段导致语义混乱。若修改 UBO:
- 同步 vertex GLSL;
- 同步 fragment GLSL;
- 同步 C++ UBO struct/static_assert;
- 运行官方 shader generator 更新 generated OpenGL header;
- 不手工只改 generated header。
helper 命名应从 `fieldVelocityAt` 收敛为 generic advection/vector 语义;不得继续用 velocity 名称表达 DIRECTION 分支。
## 7. Renderer acceptance
STEP-067 当前主动拒绝 `VectorSemantics::Direction`。本任务需改为:
- scalar-only:Particle unavailable/fail-closed;
- vector VELOCITY:允许;
- vector DIRECTION:允许;
- T0/T1 semantics 不一致:继续 fail-closed(正常应已被 Timeline compatibility 拦截,但 renderer 仍防御);
- invalid/NaN/near-zero mixed direction:该 sample invalid,按既有 lifecycle/respawn 安全处理;不得产生 NaN position。
切换 semantics/window 时不得错误复用与新 semantics 不兼容的 GPU resource。既有 `sameFieldGpuContent`/Timeline compatibility 的 semantics 保护必须保留。
## 8. Trail 与粒子线段
正式 particle line 必须与 State 实际位移一致:
- line segment 继续连接 previousState -> currentState;
- vertex shader 用于 `v_particle_velocity`/颜色判断的向量必须使用与 State 相同的 resolved advection vector;
- DIRECTION 下可把 `length(resolvedAdvection)` 作为纯视觉强度输入,但不得声称它是原始物理 speed;
- 不允许用 scalar 驱动 particle color;
- 本任务不重新设计 speedColorEnabled 公共能力,若命名仍带历史 speed,可记录技术债,避免扩大范围。
## 9. Timeline / GPU reuse 硬保护
不得破坏:
- Source-owned Timeline;
- T0/T1/MIX;
- PAUSE;
- 连续 MIX 时零 field upload;
- OLD_T1 -> NEW_T0 reuse;
- 正常 rollover 只上传 NEW_T1;
- window overlap 时 `simulationRebuild=false`、`trailCleared=false`;
- style/context loss 的既有重建规则;
- invalid/gap fail-closed。
改变 `advectionScale` 不应重新上传 field texture。是否清 trail/simulation 必须依据当前生命周期设计最小化,默认不允许为了纯 renderer 参数变化重新提交 field。
## 10. 必须补的验证
本任务需新增/调整最小测试,至少覆盖:
1. RasterParticleLayer advectionScale 默认值与 set/get/非法值策略;
2. VELOCITY 与 DIRECTION renderer eligibility;
3. scalar-only 仍 fail-closed;
4. T0/T1 semantics mismatch fail-closed/不绘制;
5. shader/UBO semantics 参数正确传递;
6. DIRECTION 数学:east、north、斜向;
7. T0 east + T1 north + MIX=0.5 -> normalize 后 northeast;
8. T0 east + T1 west + MIX=0.5 -> near-zero invalid;
9. advectionScale 改变 magnitude、不改变 direction;
10. VELOCITY 路径不 normalize,默认结果保持现状。
优先使用当前已有 C++ test/fixture 体系;不要为了测试引入新的大型 framework。
## 11. Linux 验证
必须执行:
- `node shaders/generate_shader_code.mjs`;
- 受影响 C++ 精确对象编译;
- 相关现有/新增过滤测试;
- 在可行时 `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`;
- `git diff --check`。
构建日志遵循项目全局 build-output-throttle。
如果全量 Ninja 再受单次终端时间限制,不得伪造 PASS;但应先确保受影响精确对象与过滤测试通过,并在报告明确缺口。不得要求用户现在编 Android。
## 12. 明确禁止
本任务不得:
- 修改 Android Java/Kotlin/JNI bridge;
- 修改 RN package API/version;
- 实现 v2 multi-plane file ingress;
- 修改 WeatherV2/NavSea;
- 解析 NSFT/PMTiles;
- 添加 Wind/Wave/Rain/Tide enum 或业务分支;
- 使用 scalar height/period 控制 DIRECTION 速度;
- 增加第二 scalar/N-channel;
- 重写 Timeline/Playback/Runtime Window;
- JSI/zero-copy 重构;
- 执行 Android Gradle/AAR/APK/ADB;
- 加入或执行 Jest。
## 13. 文档与状态
新增/更新:
- 本任务书 `docs/40_任务/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_Codex任务_v1.md`
- `docs/50_Step记录/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_v1.md`
- `docs/报告/STEP-068_MapLibreParticleLab_V3-04_DIRECTION粒子推进_Codex完成报告_v1.md`
- 追加 `粒子开发记录.md`
全部文档中文。完成报告逐项写“已完成 / 未完成 / 无法验证”,明确生产代码范围、验证结果、Android/Jest 未执行、风险和下一步。
若代码与 Linux 验证满足本任务门槛,可标记 `CLOSED / PASS`;若只有实现而关键精确编译/过滤测试未通过,则只能 `IMPLEMENTED / WAITING_VALIDATION`。
## 14. Git 收尾
本任务有生产代码修改,完成后必须 scoped commit + push 到 `origin/particle/main`。
提交前再次 `git status --short`。只暂存本任务实际修改文件与上述 STEP-068 文档;禁止纳入 STEP-062 两处用户修改和所有历史 untracked/build/cache/tgz。
允许按“实现代码”与“文档收尾”做 1-2 个 scoped commit。禁止 `git add .`、`git add -A`。
push 后必须确认:
- commit hash/message;
- push 成功;
- `HEAD == origin/particle/main`;
- 最终工作区剩余 dirty 仍只属于原有用户/历史内容。
push 失败则任务不得声称完全完成。
## 15. 下一阶段边界
本任务完成后下一阶段应为 V3-05:Android/JNI/RN generic v2 multi-plane ingress。
V3-05 之前不要求 Android build。最终仍按 V3 总规划,将 Android 编译/安装/设备综合验收集中到后续大阶段,避免每个小 Step 重复一次 Android 周期。

View File

@@ -0,0 +1,264 @@
# STEP-069 MapLibreParticleLab V3-05 Android/JNI/RN v2 多 Plane 入口 — Codex 任务 v1
## 1. 任务目标
基于当前 `particle/main` HEAD `9e9ea07bc74633714836aa4fff4e963109f4e3b9`,完成 `maplibre-particle-field/v2` 从 RN → Android Kotlin → Java Host facade → JNI → `RasterArraySource` → 现有 Core V3 Snapshot/Timeline 的明确多 plane 二进制入口。
本任务只做通用 Scalar/Vector Field runtime 的 Host ingress 与已经冻结的 renderer 配置桥,不引入 Wind/Wave/Rain/Tide provider/business 逻辑,不解析 NSFT/PMTiles,不改 Timeline/T0/T1/MIX/rollover/lifecycle 算法。
开始前必须完整阅读:`AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、STEP-065/066/067/068 的 Step/报告,以及当前实际源码。不得按记忆重建。
## 2. 当前已确认基线
- RN package:`packages/maplibre-particle-rn` 当前版本 `13.4.1-particle.2`。
- `.2` 公共 `FieldRuntime.submitField(reactTag, metadataJson, payloadFileUri)` 内部仍映射 `submitWindField` 单文件 ABI。
- Kotlin `WeatherRuntimeBridgeModule` 当前只读取一个本地 payload,单 payload 上限 64 MiB。
- Java `WeatherRuntime.submitWindField(metadataJson, byte[])` → `MapLibreMap.submitRasterArraySourceHostField(...)`。
- JNI `NativeMapView::submitRasterArraySourceHostField(...)` 当前只转一个 `byte[]` 为 `std::vector<char>`。
- `RasterArraySource::submitHostField(metadata, vector<char>)` 是 v1 legacy ingress。
- Core 已有 `ParticleFieldPayloadBundle { scalar?, vector?, validity? }` 与 `LocalParticleFieldFeed::load(metadata, bundle)`,但 `RasterArraySource` 尚无 v2 bundle overload。
- STEP-067 已有 `WeatherColorLayer::FieldValueSource { Scalar, VectorMagnitude }`,尚无 Android/RN setter。
- STEP-068 已有 `raster-particle-advection-scale`,尚无 Android/RN setter。
## 3. v2 RN 公共 API
将 package 版本递增到 **`13.4.1-particle.3`**,不得覆盖或继续宣称 `.2`。
新增并导出 provider-neutral 类型:
```ts
type FieldPayloadRefs = Readonly<
{ validityFileUri: string } & (
| { scalarFileUri: string; vectorFileUri?: string }
| { scalarFileUri?: string; vectorFileUri: string }
)
>;
type FieldValueSource = "SCALAR" | "VECTOR_MAGNITUDE";
```
`.3` 的主入口冻结为:
```ts
FieldRuntime.submitField(reactTag, metadataJson, payloadRefs)
FieldRuntime.clearFields(reactTag)
FieldRuntime.setFieldValueSource(reactTag, source)
FieldRuntime.setParticleAdvectionScale(reactTag, scale)
```
`submitField` 不得再接受 JS number[]、Base64 或巨大 JSON payload。
为了保留 v1 thin compatibility ingress,可保留现有 native/internal `submitWindField(metadata, singlePayload)`,并在 `.3` 公共层提供命名明确的 legacy 入口(例如 `submitLegacyVectorField`);不得让 v1/v2 共用一个模糊单 payload 形状。README/CHANGELOG 必须明确 `.2` 与 `.3` 差异。
## 4. payloadRefs 文件规则
v2 必须使用三个语义明确的 local file reference:
- `validityFileUri`:必填;
- `scalarFileUri`:可选;
- `vectorFileUri`:可选;
- scalar/vector 至少一个存在,否则在 Kotlin bridge fail-closed。
只允许 `file://` 或绝对本地路径,沿用现有 local-file 安全规则。
在实际读文件前先完成所有路径、可读性、文件长度检查,再读取;避免读到一半才发现 aggregate 超限。
冻结本桥接层 v3 初版内存上限:
- metadata UTF-8 ≤ 1 MiB(沿用现状);
- 每个存在的 plane 必须非空;
- **三个 plane 文件总字节数 ≤ 64 MiB**;
- 单文件自然也不得超过 64 MiB;
- 总长度计算必须防溢出;
- Core 仍负责按 metadata 声明验证每 plane exact byteLength/encoding/layout/validity。
不得把缺失 scalar/vector 伪造为零数据文件。validity 必须显式存在。
## 5. Android/Kotlin bridge
在 `WeatherRuntimeBridgeModule` 中新增 generic v2 submit 方法,命名可以继续受现有 TurboModule/codegen seam 限制,但语义必须明确为 Field v2,不能叫新的 Wind-only API。
推荐流程:
1. 校验 metadata;
2. 校验 refs presence;
3. resolve 三个 local file;
4. stat 全部存在 plane,计算 aggregate;
5. aggregate ≤ 64 MiB 后才逐个 bounded read;
6. 调 Java `WeatherRuntime.submitField(...)`;
7. Promise 返回现有 `FieldSubmitResult` name。
不得保存 reactTag/runtime/timeline 第二份状态,不改变现有 per-view lookup 与生命周期。
legacy `submitWindField` 可以保留作为 thin v1 compatibility,不得删除导致旧 `.2` native seam 无法理解。
## 6. Java Host facade
`WeatherRuntime` 新增 provider-neutral:
- `submitField(metadataJson, scalarBytes?, vectorBytes?, validityBytes)`;
- `clearFields()`(旧 `clearWindFields()` 可作为 deprecated/thin alias);
- `setFieldValueSource(...)`;
- `setParticleAdvectionScale(float)`。
不得在 Java 根据 `fieldId`、Wind/Wave/Rain/Tide 名称猜 renderer 行为。
`setFieldValueSource` 只接 `SCALAR` / `VECTOR_MAGNITUDE`;`setParticleAdvectionScale` 必须 finite 且 >0。二者只控制已有 renderer config,不改 Source timeline。
WeatherColor 内部历史 `speedMps` 命名若本任务不影响功能,可保留为兼容技术债,禁止借机大规模重命名。
## 7. NativeMap / JNI
新增明确的 v2 JNI 方法,不得重载旧单 payload 产生 ABI 歧义。
优先使用三 plane 参数:`scalar byte[]?`、`vector byte[]?`、`validity byte[]`。必须先检查当前 jni.hpp 对 nullable primitive array 的实际支持;若 nullable array 在当前 binding 容易产生注册/调用风险,可以改用**明确 v2 container 参数结构**,但必须:
- 不与 v1 单 payload 混淆;
- 明确记录 offsets/lengths;
- 不在 JS 层构造巨大 container;
- Kotlin/Java → JNI 后最终恢复成 `ParticleFieldPayloadBundle`。
不允许因为参数处理困难而用“空 scalar/vector 字节数组代表缺失”去绕过语义,除非在 ABI 层有明确 presence flags 且 Core bundle 恢复为 `std::nullopt`。
JNI 必须最终调用新的 `RasterArraySource::submitHostField(metadata, ParticleFieldPayloadBundle)`。
## 8. RasterArraySource 最薄 v2 overload
新增:
```cpp
submitHostField(const std::string&, const particle::ParticleFieldPayloadBundle&)
```
或等价明确 overload。
它必须调用现有 `LocalParticleFieldFeed::load(metadata, bundle)`;v1 overload 继续调用 legacy load/normalize。
两条 ingress decode 后必须进入**同一份** duplicate / monotonic / compatibility / pending initial T0 / initial window / next snapshot 提交流程。应提取最小公共 helper 避免复制 Timeline 状态机,但不得重写 Timeline。
日志改为 provider-neutral plane presence/bytes,不得假称所有 payload 为 `interleaved-uv`。legacy v1 日志可明确标注 legacy/vector-only。
## 9. FieldValueSource Android/JNI bridge
Core 已有 `WeatherColorLayer::setFieldValueSource()`,本任务只加最薄 Android/JNI Host setter,例如以稳定 int/enum code 表达:
- `0 = SCALAR`
- `1 = VECTOR_MAGNITUDE`
非法值 fail-closed。
不得新增天气业务 enum。该 setter 不重传 Field、不改变 Timeline。
## 10. advectionScale Android/RN bridge
利用 STEP-068 已有 `raster-particle-advection-scale` style property,Java Host 可通过现有 `setStyleLayerFloatProperty` 设置;如无需新 JNI 就不要新增 JNI。
RN 公开 `setParticleAdvectionScale()`;必须 finite、>0,最终只修改 renderer property,不重传 field、不清 Timeline/Trail/State。
## 11. 必须验证的行为
至少覆盖:
1. v2 scalar-only bundle 可 decode/submit;
2. v2 vector-only VELOCITY 可 decode/submit;
3. v2 scalar+vector DIRECTION 可 decode/submit;
4. 缺 validity 拒绝;
5. scalar/vector 都缺拒绝;
6. metadata 声明与 plane presence/长度不一致由 Core 拒绝;
7. v1 legacy vector-only ingress 仍通过 thin normalize;
8. v1 与 compatible v2 vector-only 可进入同一个 Runtime/Timeline 契约;
9. T0/T1 channel presence 或 vector semantics 不同继续拒绝;
10. aggregate local file size >64 MiB 在读入前拒绝;
11. 非 file URI / 相对路径拒绝;
12. FieldValueSource 非法值拒绝;
13. advectionScale <=0 / NaN / Inf 拒绝;
14. `.3` TypeScript public type 不允许 payloadRefs 同时缺 scalar/vector;
15. legacy API 明确命名,不与 v2 `submitField` 形状混淆。
## 12. 测试与静态验证
本任务**不执行 Android Gradle、AAR、APK、ADB,也不执行 Jest**。
允许并要求:
- Core/RasterArraySource 相关 C++ 精确对象编译;
- 相关 C++ 过滤测试,补最小测试覆盖 v2 Source submit 与 legacy regression;
- `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner` 仅当变更触发 Core 并且已有 build 可用;
- `git diff --check`;
- RN package TypeScript `tsc --noEmit` / 项目现有等价静态检查;
- 检查 TurboModule spec 与 Kotlin override 参数一致性;
- 可运行无需 Gradle 的 codegen/文本生成检查,但不得为了验证启动 Android build。
所有 noisy build 遵守 `$build-output-throttle`。
由于 Android 真正 Java/Kotlin/JNI 编译由用户后续 V3-08 手工统一执行,本 Step 不能声称 Android compile PASS。若静态审计仍存在签名不确定性,报告必须写明。
## 13. 明确禁止
不得:
- Android Gradle/AAR/APK/ADB;
- Jest;
- 修改 WeatherV2/NavSea;
- 解析 NSFT、PMTiles 或 provider manifest;
- 加 Wind/Wave/Rain/Tide enum/if 分支;
- 从 Wave height/period 推断粒子速度;
- 引入第二 scalar 或 N-channel;
- 改 Timeline/T0/T1/MIX/rollover/playback 算法;
- JSI/zero-copy 大重构;
- 删除 `.2` 已冻结 legacy native ABI;
- 修改 STEP-062 两处用户 dirty 文档;
- 清理历史 untracked/build/cache/tgz。
## 14. 必做文档
新增/更新:
- 本任务书 `docs/40_任务/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_Codex任务_v1.md`
- `docs/50_Step记录/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_v1.md`
- `docs/报告/STEP-069_MapLibreParticleLab_V3-05_Android_JNI_RN_v2多Plane入口_Codex完成报告_v1.md`
- `packages/maplibre-particle-rn/README.md`
- `packages/maplibre-particle-rn/CHANGELOG.md`
- 追加 `粒子开发记录.md`
全部文档中文说明为主,代码/API/命令保留英文。
任务完成后逐项报告“已完成 / 未完成 / 无法验证”,并明确 Android 编译未执行。
## 15. 状态判定
若 Core/Linux/TS 静态验证与 contract 检查满足要求,可标记:
`IMPLEMENTED / WAITING_ANDROID_VALIDATION`
**不得在本 Step 标记 Android CLOSED/PASS**,因为按照项目规则 Android 编译/实体机验收统一留到 V3-08。
该状态允许进入 V3-06 fixtures,只要不存在已知 ABI/类型阻塞。
## 16. Git 收尾
本任务修改代码,必须 scoped commit + push `origin/particle/main`。
提交前检查 `git status --short`,只暂存 STEP-069 实际文件。严禁 `git add .` / `git add -A`。
STEP-062 两处 tracked dirty、所有历史 untracked、`.codex-build-logs/`、旧 tgz/build 目录一律不得提交。
允许 1 个实现 commit + 1 个文档 handoff commit。
完成报告必须记录:
- implementation commit hash/message;
- handoff commit hash/message(若有);
- push result;
- remote branch;
- Final HEAD;
- `HEAD == origin/particle/main`;
- 未纳入的既有 dirty 内容摘要。
push 失败不得声称任务完成。
## 17. 执行回填(2026-09-11)
- 已按本任务实现 `.3` v2 refs、Kotlin/Java/JNI/Core bundle ingress、`FieldValueSource` 与 `advectionScale` 桥接;v1 ABI 保留为明确 legacy ingress。
- 未执行 Android Gradle、AAR、APK、ADB 或 Jest;Android 编译留给 V3-08。
- 结果状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。最终 commit/push 与允许验证结果见本 Step 记录和完成报告。

View File

@@ -0,0 +1,157 @@
# STEP-070 MapLibreParticleLab V3-06 四类场 Fixture 与 RN 通用回归 — Codex 任务 v1
## 1. 目标
基于 `particle/main` 当前 HEAD `3b012759838f1e1ecc44559faffe061d396b6cce`,完成 V3 通用 Scalar/Vector Field 的离线业务语义映射与回归资产:Wind、Rain、Wave、Tide/Current 四类场,外加固定 synthetic Wave independence fixture。
本 Step 的重点不是增加新的 Runtime 架构,而是证明 STEP-065~069 冻结的通用契约能够由真实/可追溯数据映射并通过 `.3` RN 公共 API 驱动。Android 编译/安装/ADB/设备视觉验证继续留到后续统一周期,不在本任务执行。
开始前完整阅读 `AGENTS.md`、`PARTICLE_BASELINE.md`、`docs/00_项目规范/`、STEP-065~069 Step/报告,以及当前实际源码。不得按记忆重建。
## 2. 必须保持的架构边界
- Core/Renderer 不得出现 Wind/Wave/Rain/Tide/provider/network/vendor 业务分支。
- Host/RN 负责业务字段到 canonical scalar/vector/validity 的映射。
- canonical schema 继续使用 `maplibre-particle-field/v2`。
- 一个 Snapshot:0/1 scalar + 0/1 vector + shared validity;neither 非法。
- Wind / Tide(Current):vector `VELOCITY`,Field=`VECTOR_MAGNITUDE`,Particle 使用 VELOCITY。
- Rain:scalar rain rate,Field=`SCALAR`,不启用 Particle。
- Wave:scalar=wave height;vector=direction-to unit vector;semantics=`DIRECTION`;Field=`SCALAR`;Particle=`DIRECTION`,推进视觉速度只来自 `advectionScale`。
- Wave period 不进入第二 scalar,不参与推进,不从 height/period 推导速度。
- Rain precipitation_type 暂不进入 canonical V3.0。
- T0/T1/MIX、Timeline、Playback、OLD_T1→NEW_T0 reuse 等 Core 逻辑不得修改。
## 3. 当前基线事实
- RN package 已是 `@navsea/maplibre-particle-rn@13.4.1-particle.3`。
- 正式 v2 API:`FieldRuntime.submitField(reactTag, metadataJson, payloadRefs)`。
- `payloadRefs` 为必填 validity + optional scalar/vector local file URI,scalar/vector 至少一个。
- `.3` 已提供 `setFieldValueSource()` 与 `setParticleAdvectionScale()`。
- Android/JNI/Core multi-plane ingress 已实现,但 Android 编译留待 V3-08。
- 现有 RN Test Host 的 REAL WEATHER 仍直接调用旧 `WeatherRuntimeBridge.submitWindField()`;本 Step 必须迁移验证入口到 package `.3` 的 `FieldRuntime`,不能继续用旧 bridge 证明新 API。
- 仓库已有真实 WeatherV2 Wind canonical 资产:`platform/android/MapLibreAndroidTestApp/src/main/assets/integration/particle-weatherv2/ecmwf-hres-wind-20260807/`。
- WeatherV2 源树只读位于 `/root/sourceserver/weatherV2`;其 NSFT v1 语义以 `src/dev/core/nsft.py` 和当前生产 pipeline 为准。不要修改 WeatherV2。
## 4. 数据来源与可追溯性
先调查 `/root/sourceserver/weatherV2` 当前实际输出,优先从已存在的 NSFT v1 `field_pmtiles`/`/fields` 等本地数值产物生成 fixture,不联网、不重新跑完整生产 pipeline。
至少为以下四类场确认一个可用 source/refTime/validTime/区域:
- Wind
- Rain
- Wave
- Tide/Current
优先选日本及周边、相邻两个实际 validTime,便于 T0/T1/MIX。若某层当前本地没有可用正式 NSFT 数值产物,不得伪称“真实 WeatherV2”;应在报告明确说明并使用最小可追溯 synthetic/decoder-derived fallback,同时保留该层的业务语义。Wind 已有真实 canonical 基线可直接复用/升级。
不得把旧 QBIN 当作正式未来 transport。若为数据核验读取旧 BIN,只能作为来源调查/对照,最终 fixture 必须输出 v2 canonical planes。
## 5. 新建通用 fixture/converter 工具
在 `tools/` 下新增 provider-neutral 或 WeatherV2-to-canonical 的离线工具,具体目录名由实际实现确定,但不得只为 Wind 特化。
工具至少应支持把已解码/NSFT 数值场规范化为:
- `metadata.json`
- `validity.u8`
- optional `scalar.f32`
- optional `vector.f32`(interleaved U/V)
- `conversion_summary.json`
约束:
- float32-le;validity uint8;NW-origin row-major;rows north→south;cols west→east。
- U eastward,V northward。
- 每个输出文件记录 byteLength、SHA-256、grid、bounds、validTime、generation、source provenance。
- vector DIRECTION 必须在 valid cell 上接近 unit length;允许原始量化误差,转换后可规范化,但近零/非 finite 必须 invalid/fail-closed,不得任造方向。
- shared validity 必须由源有效性与所有存在 channel 的有限性共同约束;invalid cell 的具体 payload 值可为 0,但 validity=0 才是权威。
- metadata 中不允许 Wind/Wave/Rain/Tide capability enum;`fieldId` 可包含可读 fixture 名称但 Core 不依赖它。
## 6. 四类 fixture 目录
新增仓库内固定测试资产,建议:
`integration/particle-v3-fields/`
至少:
- `wind-real/`:两个相邻 validTime,vector-only VELOCITY。
- `rain-real/`:两个相邻 validTime,scalar-only。
- `wave-real/`:两个相邻 validTime,scalar+vector DIRECTION。
- `tide-real/`:两个相邻 validTime,vector-only VELOCITY。
- `wave-synthetic-independence/`:两个兼容时次或至少足以验证 T0/T1 的 fixture。
若某 `*-real` 因本机数据缺失只能 fallback,目录名不得误用 real;改用 `*-decoder-fixture` 或等价明确命名,并在 dataset manifest/报告写明来源。
每组 fixture 提供 `dataset.json`,列出 frame 顺序、metadata/plane 路径、expected FieldValueSource、particle expected on/off、vector semantics、建议 advectionScale(Wave synthetic 可指定非 1 值用于验证 scale 独立性)。
## 7. Synthetic Wave Independence 固定语义
必须实现并静态验证:
- scalar(wave height)西侧低、东侧高,形成明显 E→W 或 W→E 的横向数值梯度;
- vector 在所有 valid cell 固定向北 `(u=0, v=1)`;
- `vectorSemantics=DIRECTION`;
- Field source=`SCALAR`;
- Particle 方向应只由 vector 决定为北向;scalar 改变不得改变粒子方向/推进 magnitude;
- 改 `advectionScale` 只改变 DIRECTION visual movement magnitude,不改变 field scalar。
增加无 Android 的数据 oracle/脚本验证该 fixture:scalar 的东西梯度与 vector 的北向独立,不允许只靠人工阅读 JSON。
## 8. RN Test Host 升级到 `.3` 正式 API
只修改 Test Host 的 JS/TS 与离线 assets/config,不执行 Gradle。
要求:
- REAL WEATHER/V3 验证入口从内部 `WeatherRuntimeBridge.submitWindField` 迁移到 `@navsea/maplibre-particle-rn` 的 `FieldRuntime`。
- 不从 package 内部路径 import bridge。
- 通过 app cache/local file URI 提交 metadata + plane refs;不得 JS number[] / base64 / giant JSON。
- 提供四类场选择/加载能力,至少能在 UI/fixture runner 中选择 Wind/Rain/Wave/Tide 和 synthetic Wave。
- 每次切换 dataset 前 `clearFields()`,设置正确的 `setFieldValueSource()`、Particle visibility、`setParticleAdvectionScale()`,再顺序提交 T0/T1。
- 对每个 dataset 明确断言:T0=`ACCEPTED_PENDING_INITIAL_WINDOW`,T1=`INITIAL_WINDOW_READY`,runtime state=`READY`。
- Rain 必须 Particle OFF;Wind/Tide Particle ON;Wave Particle ON 且 DIRECTION 数据不被改成 VELOCITY。
- Field/Particle visibility 仍独立。
- 不在 JS 保存 T0/T1/MIX 第二份 Runtime 状态;JS 只保存 fixture/选择 UI 与调用结果。
- 现有 GSI/base map 与已验证 UI 可保留,不做无关重构。
## 9. Core/Linux 回归
新增最小 C++/数据 contract 测试,重点不是复制完整 Android bridge,而是验证:
- 四类 canonical shape 能被 `LocalParticleFieldFeed` / `RasterArraySource` 接受。
- Wind/Tide vector-only VELOCITY compatible T0/T1。
- Rain scalar-only compatible T0/T1。
- Wave scalar+vector DIRECTION compatible T0/T1。
- channel presence/semantics mismatch 继续 fail-closed。
- synthetic Wave 的 scalar/vector independence oracle。
- normal T0→T1 / next snapshot 不修改 Runtime 算法。
如果现有 test 已覆盖底层形状,不重复堆测试;新增 fixture-specific contract test 即可。
## 10. 本 Step 不做
- 不执行 Android Gradle/AAR/APK/ADB/设备测试。
- 不执行 Jest。
- 不修改 NavSea。
- 不修改 WeatherV2 源项目。
- 不联网下载天气数据。
- 不引入 provider/network/cache/manifest runtime 到 C++ Core。
- 不改 v2 schema、Timeline/Playback、shader 数学、DIRECTION 算法。
- 不发布 npm/tgz/tag;`.3` 仅保持源码版本。
- 不修改 STEP-062 两处现有用户 dirty 文档,不清理历史 untracked/build/cache。
## 11. 必须验证
至少执行并记录:
1. fixture converter/validator 自检 PASS,含 plane byteLength/SHA/grid/bounds/time/finite/validity。
2. synthetic Wave independence oracle PASS。
3. package `.3` TypeScript `tsc --noEmit` PASS。
4. RN Test Host TypeScript/typecheck PASS(使用项目现有命令,不跑 Jest)。
5. RN TurboModule codegen schema 检查 PASS,确保 STEP-069 ABI 未被破坏。
6. 受影响 Core 精确对象编译 PASS。
7. 相关 C++ fixture/Source 过滤测试 PASS。
8. `git diff --check` PASS。
所有 noisy build 使用 `$build-output-throttle`。若完整 `mbgl-test/runner` 已经最新可直接使用;若因改 C++ test 触发重链,允许 Linux 重链。
## 12. 文档输出
必须生成/更新:
- 本任务书。
- `docs/50_Step记录/STEP-070_MapLibreParticleLab_V3-06_四类场Fixture与RN通用回归_v1.md`
- `docs/报告/STEP-070_MapLibreParticleLab_V3-06_四类场Fixture与RN通用回归_Codex完成报告_v1.md`
- 追加 `粒子开发记录.md`
- 新工具 README / fixture dataset manifest 必须中文说明为主。
状态上限:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。不得标记 Android CLOSED/PASS。
## 13. Git 收尾
所有本任务实际修改必须 scoped commit 并 push `origin/particle/main`。
- 开始/结束都核对 branch、HEAD、origin。
- 严禁 `git add .` / `git add -A`。
- 不提交 STEP-062 两处用户 dirty、历史任务 MD、build/cache/tgz。
- 可使用 1 个实现 commit + 1 个文档 handoff commit;必要时再用一个纯 Git 元数据收尾 commit。
- 完成报告记录 implementation/handoff commit、push result、remote branch、Final HEAD、`HEAD == origin/particle/main`。
- push 失败不得声称完成。

View File

@@ -0,0 +1,40 @@
# STEP-071A V3-07 Linux 综合冻结:测试 Fixture 修正
## 基线
- 仓库:`/mnt/sda1/MapLibreParticleLab`
- 分支:`particle/main`
- 基线 HEAD:`fcc533dc9518395d0c25794131f53f0fae20d51c`
- 只允许修改:`test/style/source.test.cpp`
## 已确认问题 1:Host duplicate 测试数据错误
`HostFieldSubmissionRejectsInvalidWithoutChangingLastValidField` 当前用同一份 `uniform-east` payload 提交 T0/T1。
Runtime 先按 GPU content identity 返回 `Duplicate` 是正确行为;测试预期 `InitialWindowReady` 已过时。
修正要求:
- T0 使用 `uniform-east/field.f32`。
- T1 使用兼容但内容不同的 `uniform-north/field.f32`。
- 后续 invalid/non-monotonic 检查使用不会与当前 lastValid 重复的 payload;尤其 non-monotonic 必须真正到达 non-monotonic 判定,而不是先命中 Duplicate。
- 不修改 `sameFieldGpuContent`、Duplicate 顺序或任何 Runtime 生产逻辑。
## 已确认问题 2:Timeline 测试时间重复
`RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 使用 east/north/vortex 三份旧 fixture;三份 metadata 的 `timeIndex` 均为 0。
当前 `OfflineFixtureParticleAdapter` 保留 `validTime = timeIndex + 1`,因此旧测试会形成重复时间,Timeline 正确 fail-closed,而测试无限等待。
修正要求:
- 不改共享 fixture 文件。
- 仅在该测试内部把 north/vortex metadata 的 `timeIndex` 调整为严格递增值,例如 east=0、north=1、vortex=2。
- 保持 grid/bounds/vector semantics/encoding 不变。
- 保持测试仍通过真实 `OfflineFixtureParticleAdapter` / Source preload / Timeline 路径验证,不改成绕过适配器的伪测试。
- 若需要小型局部 helper,只能放在 `test/style/source.test.cpp`,不得扩散生产 API。
## 禁止事项
- 不修改 `src/`、`include/`、shader、JNI、Android、RN、WeatherV2、NavSea。
- 不修改 STEP-062 用户 dirty 文件。
- 不执行编译、测试、代码审计、搜索、Android Gradle、ADB、Jest。
- 不执行 git add/commit/push。
- 不创建额外完成报告;验证与 Git 收尾由 ChatGPT 在 tmux `rdc` 中直接完成。
## 完成输出
修改完成后只报告:
1. 修改了 `test/style/source.test.cpp` 哪两个测试;
2. 每处如何避免 Duplicate / 重复 validTime;
3. 是否修改了任何生产代码(必须为“否”)。

View File

@@ -0,0 +1,27 @@
# STEP-071A V3-07 Linux 综合冻结:测试 Fixture 修正 v2
## 基线与现状
- 当前基线:`fcc533dc9518395d0c25794131f53f0fae20d51c` + 未提交的 v1 测试修正。
- v1 的 Host duplicate 修正已实测 PASS,不得回退。
- 只允许修改:`test/style/source.test.cpp`。
## 唯一问题
`RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 中 v1 使用:
`northMetadata.find("\"timeIndex\":0")`
实际共享 fixture metadata 格式是:
`"timeIndex": 0`
因此 `find()` 返回 `npos`。
## 必做修正
- 保留 v1 已完成的 east/north distinct payload 修正。
- 将 north/vortex 的测试内 `timeIndex` 替换逻辑改为匹配实际 JSON 文本格式。
- north 必须得到 `timeIndex = 1`,vortex 必须得到 `timeIndex = 2`。
- 不修改共享 fixture 文件。
- 不修改任何生产代码或 Runtime 行为。
## 禁止
- 不编译、不测试、不搜索/审计、不运行 Git。
- 不修改其他文件。
- 不运行 Android/ADB/Jest。
完成后只报告修改内容和是否修改生产代码。

View File

@@ -0,0 +1,50 @@
# STEP-072A V3-08 RN Test Host 移除 Legacy 双 Bridge
## 基线
- 仓库:`/mnt/sda1/MapLibreParticleLab`
- 分支:`particle/main`
- 已提交 HEAD:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
- 当前工作区已有 STEP-072 未提交修改,必须保留,不得回退或覆盖。
- 只处理 RN Test Host legacy bridge 重复 autolink 问题。
## 已确认事实
- App 已只从 `@navsea/maplibre-particle-rn` 使用 `FieldRuntime`。
- Expo autolinking 当前同时链接:
- `@maplibre-particle/weather-runtime-bridge`
- `@navsea/maplibre-particle-rn`
- 两者都注册 Native module `WeatherRuntimeBridge`,codegen library 都是 `WeatherRuntimeBridgeSpec`。
- Test Host 根 `react-native.config.js` 主动注册 `./modules/weather-runtime-bridge`。
- `tsconfig.json` 仍 include `modules/weather-runtime-bridge/src`。
- V3-08 必须只保留正式 package `.3` 的 bridge。
## 必做修改
1. 删除整个 tracked legacy 目录:
`integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/`
- 只删除其中 Git tracked 源码/配置;不要清理、提交或处理其 untracked `android/build/`。
2. 删除 Test Host 根:
`integration/react-native/MapLibreParticleRNTestHost/react-native.config.js`
- 该文件目前唯一用途是注册 legacy bridge;删除后不要新造替代配置。
3. 修改:
`integration/react-native/MapLibreParticleRNTestHost/tsconfig.json`
- include 收敛为 `App.tsx` 与 `src`,移除 legacy module src。
4. 更新 Test Host README 当前 V3-08 章节:
- 明确 legacy local `@maplibre-particle/weather-runtime-bridge` 已移除。
- `@navsea/maplibre-particle-rn@13.4.1-particle.3` 是唯一 `WeatherRuntimeBridge` 提供者。
- 历史 STEP-061/062 章节可保留为历史说明,不得改写历史事实。
## 禁止事项
- 不得运行 Android Gradle、AAR/APK build、publish、install 或 ADB。
- 不得运行或加入 Jest。
- 不得修改 `packages/maplibre-particle-rn/`、MapLibre Core/JNI/WeatherRuntime、fixture、NavSea 或 release manifest。
- 不得删除/清理任何 untracked/generated 文件;尤其不得处理 legacy 目录下现有 `android/build/`。
- 不得执行 `git add .`、`git add -A`、commit、push、reset、clean、checkout 或 stash。
- 不得修改 STEP-062 dirty 文档和其它历史 dirty/untracked 文件。
## 完成后必做报告
逐项报告“已完成 / 未完成 / 无法验证”,并说明原因:
1. tracked legacy bridge 源码/配置是否全部删除。
2. 根 `react-native.config.js` 是否删除。
3. `tsconfig.json` 是否只包含 `App.tsx` 与 `src`。
4. README 是否明确正式 package 为唯一 bridge provider。
5. 明确说明未执行 Android/ADB/Jest/Git。
不要自行做 Android 验证;完成文件修改后停止。

View File

@@ -0,0 +1,32 @@
# STEP-072B V3-08 RN Kotlin ReadableMap 编译修复 Codex 任务 v1
## 背景
V3-08 Win11 正式 npm 消费链已验证到 Android Kotlin 编译阶段。`@navsea/maplibre-particle-rn@13.4.1-particle.3` 的 `WeatherRuntimeBridgeModule.kt` 在 `setWeatherColorStops()` 中使用 `ReadableMap`,但源码 import 区缺少 `com.facebook.react.bridge.ReadableMap`。
Win11 错误:
- `Unresolved reference 'ReadableMap'`
- 后续多个 `getDouble` unresolved 均为级联错误。
## 唯一允许的代码修改
仅修改:
`packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
在 React Native bridge import 区加入:
`import com.facebook.react.bridge.ReadableMap`
除此之外不得修改任何生产代码、接口、行为或格式。
## 禁止项
- 不得运行 Android Gradle、APK/AAR build、ADB、Jest。
- 不得修改版本号、Maven 坐标、npm package metadata、TurboModule spec、README、CHANGELOG 或 release manifest。
- 不得修改 Win11 临时 worktree。
- 不得执行 Git add/commit/push/reset/clean。
- 不得触碰 STEP-062 dirty 文件和任何无关 untracked/build 目录。
## Codex 完成要求
1. 读取目标 Kotlin 文件并确认缺失 import。
2. 只增加 `ReadableMap` import。
3. 输出 `git diff -- <目标文件>` 供人工审计。
4. 不执行任何编译或测试。
## 后续由 ChatGPT 执行
ChatGPT 将直接完成 Linux 静态检查、重新 npm pack/publish、Win11 dependency/build 验证、文档与 Git 收尾。

View File

@@ -0,0 +1,81 @@
# STEP-072 V3-08 Android Native v2 与单次综合验收准备
## 基线
- 仓库:`/mnt/sda1/MapLibreParticleLab`
- 分支:`particle/main`
- 基线 HEAD:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
- 当前 RN 包:`13.4.1-particle.3`
- 当前旧 Native AAR:`13.4.1-particle.1`
## 已确认事实
- Maven Local 仅存在 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.1`。
- 该 AAR 的 `WeatherRuntime` 只有 v1 `submitWindField(String, byte[])`,不含 STEP-069 的 v2 multi-plane `submitField(...)`。
- 当前源码已包含 v2 scalar/vector/shared validity、`clearFields`、`setFieldValueSource`、`setParticleAdvectionScale`。
- 因此 V3-08 不得继续消费 Native `.1`,也不得覆盖 `.1`。
## 目标
准备新的 Android Native artifact 坐标 `13.4.1-particle.2`,并让 RN `.3` 与 RN Test Host 全部指向该新坐标;只做源码/配置/文档准备,不执行 Android 构建或发布。
## 必做修改
1. `platform/android/VERSION`
- 改为 `13.4.1-particle.2`。
2. `packages/maplibre-particle-rn/android/build.gradle`
- Native dependency 改为 `.2`。
3. `packages/maplibre-particle-rn/app.plugin.js`
- MLRN dependency substitution 改为 `.2`。
4. `integration/react-native/MapLibreParticleRNTestHost/modules/weather-runtime-bridge/android/build.gradle`
- Native dependency 改为 `.2`。
5. `integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js`
- dependency substitution 改为 `.2`。
6. 不修改或提交生成目录 `integration/react-native/MapLibreParticleRNTestHost/android/`。
## RN `.3` 文档关系
更新 `packages/maplibre-particle-rn/README.md` 与 `CHANGELOG.md`:
- RN/TS 为 `13.4.1-particle.3`。
- 对应目标 Native artifact 为 `13.4.1-particle.2`。
- `.2` Native 是 STEP-069 v2 multi-plane 所需的新 artifact,不能复用/覆盖 `.1`。
- 当前只完成坐标准备;Android AAR 尚未由用户手工构建/发布/验收,因此不得写成已发布或 PASS。
## Release manifest
更新 `release/maplibre-particle/release-manifest.json` 为当前 `.3` 开发/验收准备状态:
- release:`13.4.1-particle.3`
- releaseState:明确为等待 Android 手工构建/发布/验收,不得使用 `released`
- reactNativePackage.version:`13.4.1-particle.3`
- nativeArtifact.version:`13.4.1-particle.2`
- canonicalField.schema:`maplibre-particle-field/v2`
- transport 描述为 metadataJson + validity local ref + optional scalar/vector local refs
- 不伪造 AAR SHA、发布时间、release tag、Android PASS。
- 保留可追溯到 `.2` 已发布 RN 版本的 previousBaseline 信息;不要抹掉历史验证语义。
## Test Host README
更新 `integration/react-native/MapLibreParticleRNTestHost/README.md` 的当前版本/构建说明:
- 当前 V3-08 目标为 RN `.3` + Native `.2`。
- 用户必须先手工 build/publish `.2` Native AAR,再 prebuild/build Test Host。
- 历史 STEP-061/062 可保留为历史章节,但不能让顶部“当前版本”继续写旧 `.1`/step061。
- Metro 当前 binary asset extension 为 `.f32` + `.u8`。
## 新建 Android 单次验收文档
新建:`docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md`。
只写用户手工执行流程,不执行命令。必须使用 `--console=plain`,ADB 命令固定显式 `adb -s 192.168.1.146 ...`。
验收文档至少覆盖:
- Native `.2` Release OpenGL AAR build + Maven Local publish;核对 AAR SHA 与 Maven Local artifact 一致。
- `javap` 核对 `.2` 的 `WeatherRuntime.submitField`、`clearFields`、`setFieldValueSource`、`setParticleAdvectionScale` 存在。
- Test Host clean prebuild/build/install/start,一次 Android 周期完成。
- Wind real、Rain scalar-only、Wave scalar+DIRECTION、Tide VELOCITY、Wave synthetic independence 五组数据逐组加载。
- 每组要求 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`。
- PLAY/PAUSE、HALF/NORMAL/DOUBLE、seek T0/T1、AUTO/30/60 FPS、Field/Particle 独立显隐。
- Wave synthetic 检查 scalar 色场与 direction particle 独立;Rain 不显示粒子。
- logcat 观察 v2 submit / initial window ready / 无 fatal bridge/JNI 错误。
## 禁止事项
- 不修改 Core/Runtime/JNI/renderer 业务实现。
- 不执行 Gradle Android build、AAR/APK build、Maven publish、Expo Android build、ADB、安装或真机操作。
- 不执行 Jest。
- 不修改 STEP-062 用户 dirty 文件或历史 untracked 文件。
- 不执行 git add/commit/push。
- 不修改生成的 `integration/react-native/MapLibreParticleRNTestHost/android/`。
- 不创建或覆盖 Native `.1` artifact。
## 完成后报告
只报告修改文件、Native `.2` 坐标同步情况、验收文档路径,以及“Android build/publish/ADB 未执行”。

View File

@@ -0,0 +1,170 @@
# STEP-077 — QBIN2 V4 Android / JNI / RN 桥接只读调查 Codex 任务 v1
状态:完成
日期:2026-09-13
## 1. 任务性质
本任务是只读架构调查,不实现桥接,不修改 production code。
当前 `particle/main` / `origin/particle/main` 基线:
`0759a06104e1fb30f036db2efb6ef34ca5859759`
STEP-073~076 已完成 V4 Core、Decoder/Ingress、Binding/Projection Identity、`RasterArraySource` C++ Host API。
## 2. 调查目标
回答:V4 的以下能力应如何最小、稳定地穿过现有 Android/JNI/RN 链路:
```text
submitEncodedField
setColorBinding
setParticleBinding
setRequestedForecastTime
setForecastPlaybackState
setForecastPlaybackSpeed
clearEncodedField / clearAllEncodedFields
```
Host 仍不得控制 T0/T1/MIX。
## 3. 固定架构边界
必须保护:
```text
Test Host / NavSea JS/TS:下载、缓存、重试、gzip 解压、Manifest/Time Index
MapLibreParticle:只从 Host 已准备好的 raw QBIN bytes 开始
Core:选择 per-field T0/T1/MIX
Renderer:不解析 QBIN,不知道 provider/network/profile 业务
```
真实服务器 QBIN 不在本调查中下载或使用。
## 4. 必查实际链路
必须基于当前真实代码逐层记录:
```text
packages/maplibre-particle-rn/src/FieldRuntime.ts
packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts
packages/maplibre-particle-rn/src/types.ts
WeatherRuntimeBridgeModule.kt
WeatherRuntime.java
MapLibreMap.java
NativeMapView.java
native_map_view.hpp / native_map_view.cpp
RasterArraySource V4 Host API
```
不得只引用旧 STEP 文档替代源码调查。
## 5. QBIN 传输形态调查
重点比较并给出推荐:
```text
A. RN 传 qbinFileUri,本地 Android 层读取后交给 Java/JNI
B. RN 直接传 byte[] / number[]
C. 其他现有可复用的本地 binary seam
```
必须结合当前 TurboModule/codegen、Kotlin 文件读取、Java byte[]、JNI array copy 与 64 MiB 限制分析。
默认倾向不得由 JS 构造大 number array / Base64;但最终结论必须以现有代码和成本为依据。
## 6. RuntimeMetadata / ProfileContract / Binding ABI 调查
分别给出跨层最小 ABI 推荐:
```text
EncodedFieldRuntimeMetadata
ResolvedFieldProfileContract + requiredPlanes[]
ColorBinding
ParticleBinding
Forecast playback state/speed/time
```
比较:typed primitive parameters、primitive arrays、JSON string、ReadableMap/ReadableArray 等可行形态。
结论必须说明每一层在哪里解析、在哪里只做边界校验、如何避免 Server/SDK/Host/Native 多处重复业务规则。
## 7. 兼容性与命名调查
必须明确:
```text
现有 V3 FieldRuntime.submitField 是否保留
V4 是否新增独立 submitEncodedField,而不是替换旧 submitField
WeatherRuntime 现有 setForecastTime / setPlaybackState / setPlaybackSpeed 是否可复用或必须新增 V4 语义 API
clearFields 与 clearEncodedFields 是否必须分离
旧 Renderer/V3 timeline 是否可能被 V4 命令误操作
```
不得为了表面统一而让 V4 clear/time/playback 复用会修改 V3 timeline 的旧实现。
## 8. 生命周期与线程模型
必须调查并说明:
```text
WeatherRuntime 与 MapLibreMap / MapView 的所有权
Fabric reactTag → MLRNMapView → MapLibreMap 的现有解析路径
Java 主线程约束
NativeMapView / Style Source 查找与生命周期
style reload / map detach / runtime dispose 后 V4 状态如何失效
```
不得引入第二套 Map registry 或长期保存 reactTag/map pointer 的新机制。
## 9. 错误模型调查
必须把 C++ `RasterArrayEncodedFieldSubmitResult` 与 RN 可见结果的推荐映射列全:
```text
AcceptedInserted
AcceptedReplacedRevision
Duplicate
RejectedDecode
RejectedContract
RejectedInvalidFieldKey
RejectedIdentityConflict
RejectedStaleRevision
RejectedIncompatible
RejectedCapacity
```
同时说明文件不可读、参数非法、runtime disposed、source missing/style not ready 等桥接层错误与 Core submit result 如何分层,不得混成一个 UNKNOWN。
## 10. 验证能力调查
不执行 Android 编译、APK、ADB 或实体机操作。
调查当前可在 Linux 完成的静态验证入口,包括:
```text
TurboModule codegen schema
TypeScript typecheck
JNI C++ 精确编译或 Linux 可编译部分
Java/Kotlin 是否有无需 Android Gradle 的现成静态验证入口
现有 WeatherRuntime JVM tests 的边界
```
给出后续实现 Step 的分层验收清单,不在本任务实际执行大规模 build。
## 11. 输出文档
必须生成:
```text
docs/20_调查/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查报告_v1.md
docs/报告/STEP-077_QBIN2_V4_Android_JNI_RN桥接调查_Codex完成报告_v1.md
```
并追加 `粒子开发记录.md`。
调查报告必须包含当前链路图、推荐 V4 API 表、每层参数形态、错误映射、生命周期、兼容策略、实施文件清单、风险与后续实现拆分建议。
## 12. 禁止项
不得修改 production code、不得新增桥接 API、不得修改 package version、不得修改 Test Host、不得进入真实 QBIN 联调、不得修改 Renderer/GPU。
任务开始前已有 dirty/untracked 内容必须保持原样,不得清理、覆盖、暂存或提交。
## 13. Git 收尾
本任务会生成调查文档,因此完成后只提交本任务新增/修改的文档,commit 后 push `origin/particle/main`。
完成报告必须记录 commit hash、commit message、push result、remote branch、Final HEAD;push 失败不得声称任务完成。
完成后立即停止,不进入 STEP-077 实现。

View File

@@ -0,0 +1,254 @@
# STEP-081 — iOS Metal Renderer 与真机 Smoke 入口 Codex 任务 v1
状态:计划
日期:2026-09-17
## 1. 任务性质与执行机器
本任务在 `Mac-mini-tei.lan` 的 `/Users/tei/dev/MapLibreParticleLab` 执行,是 Android 粒子系统完成后的第一张 iOS/Metal 实现任务。
当前代码事实:
- branch:`particle/main`
- 起点 HEAD:`f23f4143b9642de0e8d6661dd567e791494a0a0b`
- `origin/particle/main`:同一 commit
- 当前 Android/RN 代码版本:`13.4.1-particle.5`
- iOS 官方基线:`ios-v6.28.0`,与 Android 共用 core `18545ca10d296704b2cba675b182ff2c8da24588`
- iOS 当前 renderer:Metal
- `ParticleGpuValidationShader` 目前只有 OpenGL ShaderSource,Metal 尚未实现/注册。
Codex 是本任务的大规模代码实现者;**Codex 禁止执行任何 iOS/Xcode/CMake 编译、链接、安装、Simulator 或真机操作**。Codex 完成后停止,由 RDC 把结果交给 GPT 审查;审查通过后,实际编译和真机验证由 GPT + RDC 完成。
## 2. 前置必读
开始修改前必须读取并遵守:
1. `AGENTS.md`
2. `PARTICLE_BASELINE.md`
3. `docs/00_项目规范/` 全部当前文件
4. `docs/00_项目总则/README.md` 及其列出的当前有效总则
5. `docs/00_项目总则/当前基线与开发路线_v2.md`
6. `packages/maplibre-particle-rn/CHANGELOG.md` 与 `README.md`
7. 当前 `RasterParticleLayer`、`WeatherColorLayer`、QBIN V4 Projection/Materialization 代码
8. Metal backend、Metal shader group/program、iOS app CMake 与 `MLNMapView` private Core seam
若旧总则中出现“Simulator 验收”或“Codex 负责构建”的描述,以本任务和用户最新规则为准:**iOS 不做 Simulator 测试;Codex 不编译;RDC 编译;运行验收只用真机。**
## 3. 本 Step 目标
实现第一条可被 iOS 真机触发的 Metal 粒子渲染路径:
```text
QBIN V4 / canonical field
→ RasterArraySource V4 renderer projection
→ WeatherColorLayer + RasterParticleLayer
→ ParticleGpuValidationShader (Metal)
→ MapLibre Metal gfx backend
→ iOS 原生 Test App 真机 Smoke 入口
```
本 Step 不接 React Native iOS bridge。
## 4. 固定架构边界
必须保持 Android 已冻结的核心语义不变:
- 不改变 QBIN V4 文件格式、Decoder、FieldProfile/Binding 语义。
- 不改变 `RasterArraySource` 对 T0/T1/MIX、Forecast Clock、boundary reuse 的所有权。
- 不改变 `WeatherColorLayer` 与 `RasterParticleLayer` 分离原则。
- 不新增 iOS 专用 Field 数据模型,不复制一套 Particle Runtime。
- 不把 provider、HTTP、manifest、下载、缓存或 NavSea 业务逻辑放进 Native/iOS。
- 粒子颜色继续独立于 WeatherColor ramp;不得为了 Metal 顺手改变视觉产品语义。
- iOS/Metal 应消费与 Android/OpenGL 相同的 C++ RenderLayer、UBO、field snapshot 和 renderer-facing V4 projection。
后端差异只允许落在 Metal Shader/资源绑定/必要的 backend thin-hook 与 iOS Test App smoke seam。
## 5. 必做项 A — Metal Particle Shader
新增 `ParticleGpuValidationShader` 的 Metal `ShaderSource`,语义必须与当前 OpenGL 版本逐 pass 对齐,至少覆盖:
- State A/B update
- Trail fade
- Trail particle line draw
- Composite / FrozenComposite
- WeatherColor pass(当前 `u_pass.x > 4.5`)
- T0/T1 `timeMix`
- field bounds / respawn bounds
- VELOCITY 与 DIRECTION 两种 advection semantics
- particleColor / trailColor / speed-color optional path
- WeatherColor stops/ramp
- invalid alpha / invalid sample 的 fail-closed 行为
- world/map matrix projection
Metal 实现必须复用现有 `ParticleGpuValidationUBO`,不得另建 iOS 专用 UBO。必须核对 Metal `alignas(16)`/buffer layout 与当前 `sizeof(ParticleGpuValidationUBO) == 31 * 16` 契约。
必须从当前 shader IDs / texture IDs / attribute IDs 推导 Metal binding,不得凭 OpenGL location 猜测。
建议文件形态遵循现有 Metal shader 规范,例如:
```text
include/mbgl/shaders/mtl/particle_gpu_validation.hpp
src/mbgl/shaders/mtl/particle_gpu_validation.cpp
```
具体文件名可按仓库现有命名契约调整,但不得把 MSL 字符串塞进 renderer layer 源文件。
## 6. 必做项 B — Metal backend 注册与构建图接线
必须完成:
- 在 Metal renderer backend include 新 shader。
- 将 `BuiltIn::ParticleGpuValidationShader` 注册进 Metal shader registry。
- 补齐 Metal shader `attributes`、`textures` 与 UBO/buffer binding。
- 如当前 CMake/Bazel/source list 不是自动 glob,补齐最小构建图接线。
- 不修改 OpenGL shader 语义来“迁就”Metal;若共享 ID 定义确有缺口,只做 backend-neutral 最小修正并在报告中单列原因。
必须静态检查当前 `RenderRasterParticleLayer` / `RenderWeatherColorLayer` 使用的 gfx API 是否存在明显 OpenGL-only 假设。能通过现有 gfx 抽象工作的代码保持不动;只有明确阻断 Metal 的点才允许最小修改。
## 7. 必做项 C — iOS 原生真机 Smoke 入口
在现有 `platform/ios/app` 内增加一个最小、隔离、可明确触发的 Particle/QBIN V4 Smoke 场景,用于后续 RDC 编译安装后在真实 iPhone/iPad 上触发 Metal shader runtime compilation。
要求:
- 使用现有 iOS Test App,不新建第二个产品工程。
- 场景应能从 App 内明确进入,名称应能识别为 Particle / QBIN V4 Smoke。
- 使用仓库现有 Wind Golden QBIN T0/T1 作为本地 fixture;不得联网下载。
- fixture 可通过 CMake resource copy 或其他现有 iOS app 资源机制进入 bundle;不得复制出新的数据格式。
- 通过 iOS Objective-C++ / private test seam 取得当前 `mbgl::Map` / Style,并调用现有 C++ Host API;不得新增另一套 runtime。
- 建立 `raster-array` source、`weather-color` layer、`raster-particle` layer,并提交 Wind T0/T1。
- Smoke 默认应同时显示 WeatherColor 与 Particle,便于真机确认 Metal 两种 pass 都被实际触发。
- 至少提供 T0 / midpoint / T1 三个固定时间状态或等价的可操作入口,使后续真机能验证 `mix`。
- 提供 clear/reload 最小入口或可重复进入场景的方式,以便观察资源释放/重建。
Smoke 代码只能存在于测试 App/测试 seam,不得进入公开 iOS SDK API,也不得成为 NavSea 正式接口。
## 8. 非目标
本 Step 明确不做:
- React Native iOS bridge / TurboModule
- Expo plugin iOS 接线
- NavSea iOS 接入
- npm/iOS artifact 发布
- Android AAR/RN package 版本升级
- Vulkan 实现
- 新粒子算法、视觉重设计、性能调优
## 9. 允许修改范围
优先限制在:
```text
include/mbgl/shaders/mtl/
src/mbgl/shaders/mtl/
src/mbgl/mtl/renderer_backend.cpp
必要的 CMake/Bazel/source list
src/mbgl/renderer/layers/(仅 Metal 阻断的 backend-neutral 最小修正)
platform/ios/app/
integration/react-native/MapLibreParticleRNTestHost/assets/qbin-golden/wind/(只读 fixture 来源)
docs/30_设计/
docs/50_Step记录/
docs/报告/
粒子开发记录.md
```
若必须超出以上 production 范围,先判断是否仍属于明确的 Metal thin-hook;若需要改 QBIN/Core contract、RasterArraySource API 或 Android bridge,立即停止并在报告标为 BLOCKED,不得自行扩大任务。
## 10. 禁止修改范围
禁止:
- `docs/00_项目总则/`
- `docs/00_项目规范/`
- QBIN V4 格式/Decoder/Profile/Binding contract
- Android JNI/Kotlin/RN bridge
- Android 版本号与公开 artifact coordinate
- `packages/maplibre-particle-rn` 公共 API
- upstream remote/tag/history
- 与本 Step 无关的已有文件/未跟踪文件
## 11. Codex 验证规则 — 严禁编译
Codex 本任务只允许静态验证,例如:
```text
git diff --check
git status --short
git grep / rg / find
必要的文本/结构一致性检查
```
**禁止 Codex 执行任何可能进入 iOS/Native 构建、链接、shader runtime 编译、安装或运行的命令**,包括但不限于:
```text
xcodebuild
cmake / ninja / make / bazel build
clang / clang++ / metal / metallib 的编译调用
pod install / swift build
simctl
任何 iOS Simulator 命令
任何真机安装/启动命令
```
也不要用 Android/Linux build 代替 iOS 验证。本 Step 的编译状态在 Codex 报告中必须写为 `NOT_RUN_BY_DESIGN`。
不加入 Jest,不执行 Jest。
## 12. GPT/RDC 后续验收接口
Codex 必须在完成报告中明确列出,供 GPT/RDC 后续逐项执行:
1. iPhoneOS device build 所需的推荐 CMake/Xcode 入口与 target 名称。
2. Smoke 场景在 iOS Test App 中的进入方法。
3. 真机预期出现的关键日志事件,至少覆盖 shader/layer creation、field ready、particle resource create。
4. T0 / midpoint / T1 与 clear/reload 的人工操作步骤。
5. 若 Metal shader runtime compile 失败,应优先检查的源文件/函数/绑定表。
## 13. 必做输出
Codex 必须实际生成/更新:
```text
docs/30_设计/STEP-081_iOS_Metal_Renderer与真机Smoke入口设计_v1.md
docs/50_Step记录/STEP-081_iOS_Metal_Renderer与真机Smoke入口_v1.md
docs/报告/STEP-081_iOS_Metal_Renderer与真机Smoke入口_Codex完成报告_v1.md
粒子开发记录.md
```
设计文档必须记录 OpenGL → Metal pass 对照、Metal UBO/attribute/texture binding 表、Metal 注册位置、Smoke 场景数据链和后续 RN iOS bridge 的明确边界。
Step 状态在 Codex 结束时只能根据事实写:代码实现完成但未编译时,写“进行中 / WAITING_RDC_BUILD_REVIEW”或等价明确表述;**不得写 iOS PASS、Metal PASS 或真机 PASS**。
完成报告必须至少包含:
- 执行结论:PASS/PARTIAL/BLOCKED/FAIL(仅针对 Codex 代码任务本身)
- branch、Start HEAD、End HEAD、工作树状态
- 实际修改/新增文件
- Metal shader/pass/binding 实现摘要
- iOS Smoke 入口说明
- 与任务书差异
- 构建记录:明确 `NOT_RUN_BY_DESIGN — Codex prohibited from building`
- 静态检查记录
- 已知风险/未完成项
- 给 GPT/RDC 的编译与真机验收清单
- commit hash、commit message、push result、remote branch、Final HEAD
## 14. Git 收尾
本任务会修改项目文件,因此 Codex 必须:
1. 只提交本任务相关文件,不得夹带既有无关 dirty/untracked。
2. 使用 scoped commit message,例如:`feat: bring up particle renderer on ios metal`。
3. commit 后 push 到 `origin/particle/main`。
4. push 失败不得声称完成。
5. 完成后不得继续下一 Step,不得自行开始 RN iOS bridge。
## 15. 停止条件
出现以下任一情况必须停止扩大修改并在报告中写明 BLOCKED/PARTIAL:
- Metal 实现需要改变 QBIN V4/Core contract 才能继续。
- 需要建立 iOS 独立 Particle Runtime 或复制 Android Runtime 逻辑。
- 必须修改 Android bridge/API 才能让 iOS smoke 工作。
- 发现现有 gfx RenderTarget/Texture/Lines 在 Metal 根本不具备所需能力,且修复将变成大规模 gfx backend 重构。
- 需要升级 Xcode、MapLibre baseline、CMake、SDK 或第三方依赖。
- 无法以现有 iOS Test App/private test seam 触发 Core Host API,且需要新增公开 SDK API。
完成本任务后立即停止,等待 RDC 将 Codex 结果交给 GPT 审查。GPT 审查通过后,才由 RDC 执行 iPhoneOS device build,并只在真实 iPhone/iPad 上做运行验收。

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,198 @@
# STEP-087 QBIN V3 解码与 13.4.1-particle.9 升级 Codex 任务 v1
## 1. 任务目标
在 Mac 专用工作树 `/Users/tei/dev/MapLibreParticleLab-qbv3-p9` 上完成 QBIN V3(Magic=`QBV3`, version=3)正式解码支持,并将统一 Native / RN 源码版本升级到 `13.4.1-particle.9`。
本任务必须以以下设计稿为权威依据:
- `docs/30_设计/WeatherV2_QBIN_V3通用天气场二进制格式与React独立Render_Binding设计_v2.2_最终冻结稿.md`
该设计稿是用户提供原文,必须保留,不得改写、覆盖或删减。实现中如发现规范真实矛盾,只能在完成报告中记录并停止相关 ABI 决策,不得自行修改规范来迁就代码。
## 2. 当前基线
执行前必须核对:
- 工作树分支:`particle/qbv3-p9`
- 基线必须包含 `release/particle-8` 的两笔发布提交,至少到 `77744dd9f`
- `platform/android/VERSION` 与 `packages/maplibre-particle-rn/package.json` 当前均应为 `13.4.1-particle.8`
- 不得修改其他 Mac 工作树
- 主线目标仍为 `origin/particle/main`
先阅读项目根目录 AGENTS.md(如存在)、docs/00_项目总则、docs/00_项目规范,以及 STEP-073~085 中与 QBIN ingress、FieldProfile、发布和 iOS device-arm64 相关的最新记录。## 3. Phase 0:只读现状调查
在写代码前完成一次只读调查并在报告中记录结论,至少确认:
- `QbinV2Decoder`、`EncodedFieldIngress::decodeAndValidate()`、`DecodedFieldFrame`、`FieldProfileValidator` 的真实接口。
- QBV2 source/test 在 Bazel/CMake/平台构建中的注册位置。
- RN / Android / iOS 当前 encoded-field 入口是否只是透传 bytes,确认 QBV3 不需要新增 JS/TS 公共业务 API。
- `.8` 版本号实际出现在哪些“当前版本”文件中;禁止全仓替换历史文档中的旧版本号。
- 现有 Wind / Rain / Wave / Current QBIN Golden Fixture 的来源、尺寸与可复用性。
结论应坚持:
- V2 与 V3 只在 Encoded Field Ingress 前分流。
- Decoder 后继续输出同一 `DecodedFieldFrame / Plane Registry`。
- Runtime / Binding / Renderer 不感知 QBV2/QBV3。
- 不把 Provider、Weather Service、HTTP、Manifest、缓存、React 业务逻辑放进 Native Decoder。
## 4. Block Geometry 冻结
v2.2 规范只剩 `BLOCK_WIDTH / BLOCK_HEIGHT` 未冻结,本任务必须在正式 QBV3 Decoder 固化前完成选择。
使用现有四类真实 Golden 数据:
- `integration/react-native/MapLibreParticleRNTestHost/assets/qbin-golden/wind/`
- `rain/`
- `wave/`
- `current/`
优先复用 QBV2 Decoder 得到 canonical quantized integer grid + validity,再用测试/benchmark 专用 deterministic QBV3 encoder/prototype 比较 `16×16` 与 `32×32`。该 encoder 仅用于测试、fixture 和 benchmark,不得演变成 Weather Provider/生产 Writer。比较必须使用同一 predictor / ZigZag / Rice 规则,至少记录:
- encoded bytes
- block count / non-empty block count
- encode time
- entropy decode time
- total decode time
- CRC time(若可独立测)
- peak temporary bytes(若现有工具不能可靠测量,报告中明确说明,不得伪造)
选择原则:
1. 首先保证规范一致和 bit-exact round-trip。
2. 压缩率差异很小时,优先更低 decode/encode 开销、更少 block、实现更简单的一项。
3. 最终必须只留下一个 production Block Geometry。
4. production Decoder 不得同时“猜测”16×16/32×32,也不得根据解码是否成功试两遍。
5. 测试代码可以保留两个候选参数用于 benchmark,但正式 QBV3 ABI 常量必须唯一。
把选择依据写入:
- `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`
该文档是粒子侧实施补充,不改写用户提供的 WeatherV2 v2.2 原文。
## 5. 正式 QBV3 Decoder
新增清晰独立的 QBV3 decoder 文件,优先采用:
- `include/mbgl/particle/ingress/qbin_v3_decoder.hpp`
- `src/mbgl/particle/ingress/qbin_v3_decoder.cpp`
如现有构建结构要求轻微调整可据实处理,但禁止把 V3 逻辑塞成 `qbin_v2_decoder.cpp` 内的大量 version 分支。实现必须逐项遵守 v2.2 normative 主体,重点包括:
- 112-byte Header、32-byte Descriptor、Little Endian。
- Magic=`QBV3`、version=3;FRAME 固定 layout/projection/sampleOrder/gridRegistration/tile 字段。
- Version 3 limits:planeCount 32、cellCount 2,097,152、file 64 MiB、decoded frame 32 MiB。
- Header bounds finite、Web Mercator 范围、binary64 数值零禁止 -0.0。
- Numeric dtype 只允许 INT16/UINT16/UINT8/INT8;FLOAT32 与 Numeric BITSET reject。
- `semantic==3 ⇔ valueClass==VALIDITY ⇔ dtype==BITSET`,且只允许一个 Shared Validity Plane。
- Validity LSB-first;tail unused high bits 必须为 0。
- unknown fieldType(包括 0)只作为 opaque hint,不得单独 reject。
- Numeric `scale > 0` finite,offset finite;数值为 0 时只接受 binary32 +0.0。
- Parser 顺序保持:结构与 budget → CRC → padding/Validity → Rice decode。
- CRC-32/ISO-HDLC,CRC field 76..79 计算时视为 0。
- all-invalid 合法:Validity 全 0、Numeric byteLength=0、decoded raw 全 0。
- zero-length Plane 可以共享 canonical byteOffset;只对真实非零 range 做 overlap 判断。
- Numeric Plane byteLength 是各 non-empty block encoded bytes 之和,不含 align16 padding。
Rice / predictor 必须严格实现:
- Predictor 在全 Grid 连续,不在 block 边界 reset。
- invalid cell 不消费 Rice symbol,materialize raw=0。
- ZigZag 使用无 UB 的 widened fixed-width arithmetic。
- Rice bitstream 与 remainder 都按 byte 内 LSB-first。
- riceK 允许 0..31;Decoder permissive,不因非最优 k 单独 reject。
- unary quotient、MAX_Q、residual、raw reconstruction 全部 checked;非法立即 fail-closed。
- block padding high unused bits 必须为 0,symbolCount 必须精确。
- Direction 使用 canonical float32(360/N)、N=floor(360/scale+0.5)、raw∈[0,N)。
- Direction predictor 必须 normalize;half-circle tie 固定 `-N/2`。## 6. Ingress 与兼容性
`EncodedFieldIngress::decodeAndValidate()` 必须按 bytes 中的 magic/version 做显式 dispatch:
- `QBV2 / version=2 → QbinV2Decoder`
- `QBV3 / version=3 → QbinV3Decoder`
- 其他组合 fail-closed
要求:
- QBV2 frozen behavior 不得改变。
- V3 decode 成功后继续走现有 `FieldProfileValidator`。
- Binary-valid 与 Profile-valid 继续分层;V3 Decoder 不得根据 wind/wave/rain/current 名称要求业务 Plane。
- V2/V3 decoded Plane 的 ContentIdentity 必须继续使用项目既有 canonical identity 语义,避免 Runtime 因容器版本产生无意义 identity 漂移。
- 如需要抽取共享 helper,可做小范围重构,但不得把公共 API 扩大到 Renderer/Binding。
## 7. 测试与 Fixture
新增 C++ 定向测试,不新增或运行 Jest。
至少覆盖:
- Wind / Rain / Wave / Current integer + validity bit-exact round-trip。
- INT16 / UINT16 / INT8 / UINT8。
- all-valid、部分 invalid、all-invalid。
- zero-length shared offset。
- bad magic/version/endian、reserved、bounds、negative zero、descriptor order/duplicate、packing、CRC。
- malformed Rice、unterminated unary、riceK>31、symbolCount 少/多、nonzero block padding、MAX_Q/residual/raw overflow。
- non-optimal but otherwise-valid riceK 必须 accept。
- fieldType=0 与 unknown fieldType 必须 accept。
- Direction 359↔0、N-1↔0、half-circle tie、predictor wrap。
- WEB_MERCATOR + NODE sample 公式的定位 Golden Test。
最终冻结 Geometry 后生成唯一正式 QBV3 Golden Fixture 集,至少覆盖四类场;实验 16/32 双候选 fixture 不得混入 production fixture 目录。## 8. 构建与验证
本任务在 Mac 上执行 Native/C++ 开发和精确编译验证。
必须:
- 运行与 QBV2/QBV3 decoder 直接相关的 C++ 定向测试。
- 运行必要的 Mac 原生精确编译,确认新增 source/header 被真实构建目标消费。
- 对 iOS 只验证 iPhone/iPad device arm64 路径;不得恢复 simulator slice。
- 如执行 noisy compile/test,遵循当前 Codex 全局输出节流规则。
- `git diff --check` 必须 PASS。
禁止:
- Android Gradle build
- AAR/APK 构建或安装
- ADB
- 启动 Android App / 清理数据
- Android 实机/虚拟机冒烟
Android 最终构建安装仍由用户手工执行。本任务应在报告中给出最少的用户手工验证入口,但不要代替用户执行。
## 9. 升级到 13.4.1-particle.9
以已纳入的 `.8` 为基线,将当前有效版本升级为:
`13.4.1-particle.9`
按 `.8` 的真实发布结构更新所有“当前版本”源文件/配置,包括 Native VERSION、RN package、插件/Android 依赖引用、CHANGELOG/README 中的当前安装示例,以及 release pipeline 需要的当前版本元数据。
禁止:
- 全仓替换历史 STEP / 报告中的 `.1~.8`。
- 覆盖既有 `.8` artifact、tag 或发布目录。
- 未经用户明确要求发布外部 Maven/npm/CDN artifact。
- 未经用户明确要求创建正式 release tag。## 10. 文档输出
除本任务书和用户提供设计稿外,任务必须生成/更新:
- `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`
- `docs/50_Step记录/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_v1.md`
- `docs/报告/STEP-087_QBIN_V3解码与13.4.1-particle.9升级_Codex完成报告_v1.md`
完成报告必须写清:
- 起始 HEAD / 最终 HEAD
- 选定的 Block Geometry 与 benchmark 证据
- 新增/修改的主要代码文件
- QBV2 回归结果
- QBV3 测试结果
- Mac 原生精确编译结果
- 未执行的 Android 手工门槛
- 版本号变更位置
- 仍存在的风险或未完成项
如同主题报告已存在,按 v2/v3 递增,不覆盖旧报告。
## 11. Git 收尾
完成规定代码、测试、版本和文档后:
1. 只提交本任务相关变更。
2. 不提交无关 untracked 文件或其他工作树内容。
3. commit message 使用清晰英文,例如:`feat: add QBIN V3 decoder for particle.9`。
4. 将最终结果 push 到公开远端 `origin/particle/main`。
5. push 失败不得声称完成。
6. 完成报告必须记录最终 commit hash、commit message、push result、remote branch、Final HEAD。
7. 最终再次确认本地 HEAD 与 `origin/particle/main` 一致。
## 12. 停止条件
如果实际 benchmark 无法用现有真实 fixture 对 16×16/32×32 做可信选择,或 v2.2 存在导致合法文件无法唯一解释的真实矛盾:
- 不得猜测 production Geometry。
- 完成可独立验证的代码/调查部分后停止。
- 在 `docs/报告/` 生成接口/规范阻塞说明,明确缺什么数据或决定。
- 不得把支持两种 production Geometry 的“兼容 decoder”当成完成方案。
除此之外,应完成实现,不要因为普通工程细节反复向用户确认。

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,156 @@
# MapLibreParticleLab 粒子连续渲染静态底图缓存与动画快路径优化规划 v1
## 1. 目标
STEP-086 已确认:Particle / WeatherColor 的 needsRepaint 会通过全局 repaint 让完整 MapLibre scene 连续出帧。React 不会每帧重新提交 PBF,但 Native Renderer 会重复进入 source/layer orchestration,并重新提交静态 PBF/fill/line/symbol draw。
本规划目标:
- 地图真实变化时仍走 Full Frame。
- Camera、Style、Tile 稳定且只有 WeatherColor/Particle 动画时,复用 Static Underlay。
- NavSea / React 不参与 Renderer cache 管理。
- 不改变 NavSea ↔ Particle C-001 公共边界。
## 2. 当前关键代码
必须以当前 origin/particle/main 实际代码为准,重点包括:
- src/mbgl/renderer/layers/render_particle_validation_layer.cpp
- src/mbgl/renderer/layers/render_weather_color_layer.cpp
- src/mbgl/renderer/render_orchestrator.cpp/.hpp
- src/mbgl/renderer/renderer.cpp
- src/mbgl/renderer/renderer_impl.cpp/.hpp
- src/mbgl/map/map_impl.cpp
- 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
## 3. 核心结构
不增加永久 public static/dynamic layer flag。采用 Renderer 内部 retained dynamic boundary:
- WeatherColor 是 boundary candidate。
- RasterParticle 是 boundary candidate。
- 同时存在时取最小 layerIndex。
- boundary 之前连续 LayerGroup 前缀作为 Static Underlay。
- boundary 及之后继续实时绘制。
- 没有 boundary 时完全保持原 Renderer。
Layer 顺序、parameters.currentLayer、depthRangeSize、opaque/translucent 原始 ordinal 必须保持,不允许对子集从 0 重新编号而改变透明/depth 语义。
## 4. Static Underlay Cache
建议 Renderer-owned helper:
- src/mbgl/renderer/retained_underlay_cache.hpp
- src/mbgl/renderer/retained_underlay_cache.cpp
职责:
- offscreen renderable / texture
- viewport size
- boundary
- valid/invalid
- invalidation reason
- build/reuse/fallback 计数
- compositor resources
不改变 Heatmap/Hillshade RenderTarget 现有行为;复用现有 gfx::OffscreenTexture / RenderTarget / Drawable / shader abstraction。
## 5. Texture Composite
使用独立语义的 RetainedUnderlayCompositeShader:
- viewport/fullscreen quad
- sample cached RGBA texture
- 正确 premultiplied alpha
- 不做 map transform / color ramp / Particle 业务
不要长期复用 LocationIndicatorTexturedShader。
## 6. STEP-088
先只消除静态地图重复 GPU draw,仍保留:
- Map::Impl::onUpdate
- UpdateParameters
- createRenderTree
- source update / TilePyramid update
- source/layer prepare
- updateLayers
- LayerGroup tweaker/upload
Render 阶段:
1. cache invalid:boundary 前 LayerGroups render 到 offscreen。
2. cache valid:跳过 boundary 前静态 draw。
3. main framebuffer clear。
4. composite cached underlay。
5. boundary 及之后 WeatherColor/Particle/动态 overlay 正常绘制。
6. present。
## 7. STEP-089
在 STEP-088 实机通过后再做 production invalidation:
- Camera center/zoom/bearing/pitch
- viewport/pixelRatio
- style/layer add/remove/reorder/change
- source/tile/bucket/drawable 变化
- sprite/image/glyph
- symbol placement/fade
- context/surface recreation
- 3D/terrain/custom/debug fallback
- OpenGL/Metal 一致生命周期
## 8. STEP-090 条件步骤
只有 STEP-089 后真实 NavSea profile 仍证明主要 CPU 落在:
- Map::Impl::onUpdate
- createRenderTree
- Vector/PBF source update
- TilePyramid update
- source/layer prepare
才进入 Animation-only fast path。
如果 STEP-089 已满足性能目标,不执行 STEP-090。
## 9. STEP-088 安全边界
PoC reuse 至少要求:
- 2D
- Camera fingerprint 连续一致
- viewport 不变
- style loaded
- boundary 不变
- context/resource 正常
以下至少 fallback full render:
- has3D
- Debug Overdraw
- cache texture/compositor 创建失败
- viewport resize
- boundary change
- context 不可用
- legacy RenderItem ordering 无法证明安全
- 其他不能证明 cache 正确的状态
原则:不确定就 full render,不用 stale cache 换性能。
## 10. 观测
复用 include/mbgl/particle/renderer_debug_counters.hpp,建议增加:
- FullRendererFrame
- RetainedUnderlayBuild
- RetainedUnderlayReuse
- RetainedUnderlayFallback
- RetainedUnderlayInvalidation
- RetainedUnderlayStaticGroupDraw
- RetainedDynamicGroupDraw
固定 Camera + Particle 约 30 FPS 时,理想:
- RetainedUnderlayBuild ≈ 1
- RetainedUnderlayReuse 持续增长
- ParticleSimulationAdvance 持续增长
- StaticGroupDraw 建 cache 后不随 Particle FPS 增长
- DynamicGroupDraw 持续增长
## 11. 不允许
- React RAF 驱动
- Host renderFastFrame/cacheMapNow/invalidateBasemap API
- NavSea 手工管理 renderer cache
- 修改 C-001
- 永久 public static/dynamic flag
- 改变 Style layer ordering
- STEP-088 就跳过 source/update/prepare
- Android/iOS 不同 renderer semantics
- stale cache
## 12. 执行顺序
1. STEP-088:PoC + observability
2. 用户 Android/NavSea 真机验证
3. STEP-089:production invalidation/fallback
4. 用户再次 profile
5. 只有 CPU orchestration 仍是瓶颈时才 STEP-090

View File

@@ -1,3 +1,11 @@
# Step 记录目录
保存阶段权威状态,初始化脚本生成 STEP-000。
保存各阶段已发生事实和阶段权威状态。历史 Step 不因后续版本升级而改写。
## 当前 Android / RN 操作入口
当前 QBIN V4 / Android `.3` 的构建、pbf 公网发布、Win11 Test Host、Metro/ADB 与人工验收流程:
`STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md`
`STEP-080 ... v1` 及 STEP-060~072 中的 MavenLocal、`.1/.2`、旧 Win11 路径/端口均保留为历史证据,不作为当前命令来源。

View File

@@ -1,6 +1,6 @@
# STEP-064 RN 纯 Field Renderer 接口 V2
状态:完成(Consumer 验证通过,待正式发布冻结)
状态:冻结(13.4.1-particle.2 正式发布完成)
## 目标
@@ -40,3 +40,12 @@
- 修正后 `FieldRuntime attached; style ready`;T0–T3 依次返回 Pending / InitialWindowReady / NextSnapshot / NextSnapshot,Runtime READY。
- `Field OFF + Particle ON` 与 `Field ON + Particle OFF` 均实测通过,并恢复两者 ON。
- hard gate 未见 WEATHER_OBJECTS_NOT_READY、FATAL、SIGSEGV、JNI DETECTED ERROR、NoSuchMethodError、UnsatisfiedLinkError 或非法 layer property。
## 正式发布关闭
- Release source commit:`e43b58707cfd0f87965424477ed3f95625b3f724`。
- Release tag:`maplibre-particle/rn/13.4.1-particle.2`,已 push。
- 正式 HTTPS:`https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.2.tgz`。
- 正式 tgz:10,660 bytes;SHA256 `ace9e0401824839d18a93cc4201c1f5d4cccbff0636570cfde82fd80b60779b5`;公网 HTTP 200 且下载字节完全一致。
- Win11 Consumer 已从正式 HTTPS URL 安装 `.2`,TypeScript 通过,MLRN 11.3.6 单份 dedupe。
- STEP-064 正式冻结;后续修改必须升 `13.4.1-particle.3`,不得覆盖 `.2` tgz 或移动 release tag。

View File

@@ -0,0 +1,25 @@
# STEP-065 MapLibreParticleLab V3 通用 Scalar / Vector Field 架构冻结
状态:冻结
## 目标与基线
在 `2e3ab262` 对当前真实实现做只读调查,冻结 V3.0 generic Scalar/Vector Contract 和迁移路线;不实现。开始与结束均为 `particle/main`,开始时 `HEAD == origin/particle/main == 2e3ab262`。
## 必做项结果
- [x] 读取全部规范、STEP-064 基线,复核 branch/HEAD/origin/dirty worktree。
- [x] 调查 descriptor/snapshot/adapter、WIND/GLOBAL、Timeline/MIX/window reuse、WeatherColor `speedMps`、Particle velocity m/s、RN→Android→JNI→Core。
- [x] 只读核对 WeatherV2 Wind/Tide/Rain/Wave NSFT 语义。
- [x] 冻结 0/1 scalar + 0/1 vector + shared validity、`FieldValueSource`、`VectorSemantics`、独立 plane file refs。
- [x] 冻结 `maplibre-particle-field/v2`、v1 thin normalize compatibility、分批路线和单次 Android 综合验收。
- [x] 生成调查、设计、Step、报告并追加开发记录。
- [x] 确认未修改生产代码、WeatherV2、NavSea、STEP-062 或历史无关 untracked 文件。
## 允许/禁止与验证
仅新增/更新任务指定文档;不改 C++、shader、JNI、Android、RN API 或 fixture,不生成 AAR/tgz。未执行 Android Gradle、APK、ADB、Jest,原因是任务明确禁止。执行只读代码/文本审计与 `git diff --check`/精确 staged 文件审计;不宣称构建或设备 PASS。
## 当前结果与下一步
详见调查及设计文档。下一任务建议:`STEP-066 V3-02 Core Contract + Scalar Snapshot/Adapter`,只实现 v2/v1 normalize 与 Core fixture/验证,先不进入 GPU 或 Android。未解决风险:v2 具体 Kotlin/JNI ABI 形态、plane 总内存预算、direction 视觉 scale 默认值须在实施前用目标设备约束量化。

View File

@@ -0,0 +1,35 @@
# STEP-066 MapLibreParticleLab V3-02 Core Contract 与 Scalar Snapshot Adapter
状态:完成
## 目标与基线
在 `particle/main` 的 STEP-065 冻结设计上,仅实现 Core Contract、Snapshot、Adapter 与最小 Core 测试。开工时 `HEAD == origin/particle/main == d23ae2079dcfef3ce48517f734fe487207e28d23`;工作区已有 STEP-062 两处用户修改及历史 untracked/build 内容,均未纳入本 Step。
## 完成内容
- [x] 阅读规范、STEP-065、任务书与当前实际源码。
- [x] 定义通用 `ParticleFieldPayloadBundle`、v2 plane descriptor 与 `VectorSemantics::Velocity|Direction`。
- [x] Snapshot 升级为 optional scalar、optional interleaved U/V vector、shared `valid`;移除 Core 对 `WIND/GLOBAL` 的 capability hardcode。
- [x] 实现 v2 vector-only、scalar-only、scalar+vector;neither、plane presence/长度/encoding、validity、semantics 与 finite/noData 错误 fail-closed。
- [x] 保留 v1 单 payload parser;在 ingress 派生 shared validity,并归一为 vector-only + `VELOCITY`。
- [x] 收紧 T0/T1 compatibility 至 grid/bounds、scalar/vector presence 与 vector semantics;不改变 Source-owned Timeline、T0/T1/MIX、pause 或 rollover ownership。
- [x] `sameFieldGpuContent()` 逐值比较 decoded scalar/vector/validity plane,不依赖 fieldId/dataVersion/legacy hash 猜测内容。
- [x] 最小 renderer 适配:速度纹理读取改为 generic vector 成员;scalar-only 输入清空/不创建 drawables,V3-03 前 fail-closed;未改 shader、纹理编码、GPU 计算或粒子运动语义。
- [x] `source.test.cpp` 覆盖 v1 normalize、三种 v2 合法组合、neither、三种长度错误、missing semantics、DIRECTION without vector、NaN/Inf、invalid finite placeholder、compatibility 与三 plane identity。
## 验证
- `ninja -C build-linux-opengl -j3 mbgl-test`:PASS。
- `ninja -C build-linux-opengl -j3 test/CMakeFiles/mbgl-test.dir/style/source.test.cpp.o`:PASS。
- `ninja -C build-linux-opengl -j3 mbgl-test-runner`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter=Source.ParticleFieldV3*`:PASS(退出码 0)。
- `git diff --check`:PASS。
全部 build/test 输出经 `build-output-throttle` 限流。未执行 Android Gradle、AAR/APK、ADB 或 Jest。
## 范围确认、阻塞与下一步
没有 Android/JNI/RN、WeatherV2、NavSea、shader 或 Test Host JS/TS 修改;STEP-062 用户修改和历史 untracked 内容未暂存。Scalar GPU texture、generic `FieldValueSource`、DIRECTION advection、v2 JNI/RN ingress 均未实现,按冻结路线留给 V3-03/V3-04/V3-05;scalar snapshot 在现有 renderer 为 unavailable/fail-closed。
下一步:STEP-067(V3-03 Scalar GPU Texture + Generic Field Renderer)。

View File

@@ -0,0 +1,37 @@
# STEP-067 MapLibreParticleLab V3-03 Scalar GPU 与 Generic Field Renderer
状态:完成(CLOSED / PASS)
## 目标与基线
在 `particle/main` 的 `9dabb95e4425ffbcb863c520923f5d4b537a3feb` 上实现 V3 scalar GPU field bundle 的 WeatherColor 消费,保持 vector magnitude 的 Wind 色场和既有粒子 Velocity 语义。
## 完成内容
- [x] `WeatherColorLayer` 新增显式 `FieldValueSource::Scalar|VectorMagnitude`,默认 `VectorMagnitude`。
- [x] WeatherColor 单一 GPU renderer 根据明确 source 编码 scalar 或 U/V,shared validity 始终写入 alpha;T0/T1 均无效或缺少所选 channel 时清理 drawable 并 fail-closed。
- [x] shader 对 scalar 或 vector magnitude 分别先作 T0/T1 时间插值,再进入同一 generic color ramp;没有 scalar 伪装为 U/V。
- [x] `WeatherColorStop.speedMps` 收敛为 generic `value`,校验为 finite 且严格递增;日志、UBO 注释和 shader 去除固定 m/s 语义。
- [x] Particle renderer 明确仅接受 vector present 且 `VectorSemantics::Velocity`;scalar-only 与 `Direction` 立即清理其 GPU resource 并 fail-closed。
- [x] WeatherColor rollover 仍以 decoded bundle content identity 复用 OLD_T1→NEW_T0;切换 value source 时不误复用不同 GPU 编码,只有新内容上传。
## 允许修改
WeatherColor style/render、Particle renderer guard、实际 GLSL/生成的 OpenGL shader header、对应 UBO 注释和 C++ layer test。
## 禁止修改确认
未修改 Android/JNI/Java/Kotlin、RN package/API/version、Test Host、WeatherV2、NavSea、Timeline/Playback/Runtime Window;未实现 DIRECTION advection、Wave physics、v2 JNI/RN ingress;未运行 Gradle、AAR、APK、ADB 或 Jest。
## 验证
- `node shaders/generate_shader_code.mjs`:PASS(经 build-output-throttle)。
- 精确受影响对象:`render_weather_color_layer.cpp.o`、`render_particle_validation_layer.cpp.o`、`style_layer.test.cpp.o`:PASS(经 build-output-throttle;ccache 临时目录使用 `/tmp/maplibre-particle-ccache-tmp`)。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldV3*:Layer.WeatherColorUsesIndependentFixedOpacity'`:PASS,退出码 0。
- `git diff --check`:PASS。
- 用户于当前 `HEAD`(`06fcbef9ccb820e595f06acbd356950de7e3a73f`)手工执行 `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`,输出为 `ninja: no work to do.`。确认 `mbgl-test` 与 `mbgl-test-runner` 两个完整目标及其依赖均已是最新,没有待处理的构建或重链接工作。
- 上述结果仅确认目标构建/重链接状态;本次没有重新执行全量测试用例,不能将其表述为全量测试用例重跑 PASS。
## 阻塞与下一步
无功能阻塞。本 Step 的 Linux 全量目标重链收尾已确认;Android 验收按 V3-08 的单独周期执行,且不属于本 Step 的关闭条件。

View File

@@ -0,0 +1,61 @@
# STEP-068 MapLibreParticleLab V3-04 DIRECTION 粒子推进
状态:完成(CLOSED / PASS)
## 目标与基线
基线为 `particle/main` / `89c72554c5fb5cd469bdd8ba06ff4b03a1a67ff9`,与 `origin/particle/main` 一致。实现通用 `DIRECTION` 向量推进及独立 renderer `advectionScale`,不引入任何 Wave、WeatherV2 或 provider 分支。
## 已完成
- [x] RasterParticleLayer 新增 `raster-particle-advection-scale` 与 C++ `getAdvectionScale()/setAdvectionScale()`;默认 `1.0`,非 finite 或非正值 fail-closed。
- [x] State fragment 与正式 line vertex 共享同一语义:分别解码 T0/T1、分量 MIX、检查有限性;DIRECTION 对混合结果归一化并乘 scale,长度 `<= 1e-5` 无效;VELOCITY 保留原始分量 MIX 与 magnitude。
- [x] UBO 新增明确 `advection` vec4(Direction flag、scale、epsilon),C++ struct/static_assert、两份 GLSL 和 generated OpenGL header 同步。
- [x] renderer 接受 vector VELOCITY 与 DIRECTION;scalar-only、缺 semantics 或 T0/T1 semantics 不同仍清理并 fail-closed。
- [x] `advectionScale` 仅更新 renderer uniform,不上传 field texture、不重置 Timeline/State/Trail。
- [x] C++ oracle 测试覆盖 east/north/diagonal、east/west MIX 归零、scale 改变 magnitude,以及 VELOCITY 不归一化且不消费 scale;Layer 测试覆盖默认、set/get 与非法值。
## 禁止范围确认
未修改 Android、JNI、Java/Kotlin、RN、WeatherV2、NavSea、Timeline/Playback/Runtime Window、STEP-062 用户修改或历史无关文件;未执行 Gradle、AAR/APK/ADB 或 Jest。
## 验证
- [x] `node shaders/generate_shader_code.mjs`:PASS(输出节流)。
- [x] 第一轮受影响对象 `render_particle_validation_layer.cpp.o`、`raster_particle_layer.cpp.o`、`style_layer.test.cpp.o`:PASS(输出节流;发生在新增 CPU oracle 后的最终重编译前)。
- [x] 既有过滤测试 `Layer.RasterParticle*:Source.ParticleFieldV3*`:PASS(输出节流;发生在新增 CPU oracle 后的最终重链前)。
- [x] 最终受影响精确对象:`render_particle_validation_layer.cpp.o`、`render_weather_color_layer.cpp.o`、`raster_particle_layer.cpp.o`、`source.test.cpp.o`、`style_layer.test.cpp.o`:PASS。
- [x] `ninja -C build-linux-opengl -j3 mbgl-test mbgl-test-runner`:PASS,完整目标重编译/重链完成(本轮约 899.5 秒)。
- [x] STEP-068 相关过滤测试 `Source.ParticleFieldV3*:Layer.RasterParticle*`:8/8 PASS;其中包含 DIRECTION east/north/diagonal、MIX northeast、opposite fail-closed、VELOCITY 不归一化及 advectionScale 属性。
- [x] `git diff --check`:PASS。
- [x] shader generator:此前本 Step 已 PASS;WeatherColor UBO 最小修复未修改 shader/generator 输入,无需再次生成。
## 2026-09-11 全量 Linux 重编译发现的 UBO 初始化遗漏
全量 Linux 重编译在 `src/mbgl/renderer/layers/render_weather_color_layer.cpp:108` 以
`-Wmissing-field-initializers` 发现 `ParticleGpuValidationUBO::advection` 新增后
WeatherColor 的 designated aggregate initializer 未同步。已仅在该 color-only pass
显式补入安全中性值 `{Direction flag=0, scale=1, epsilon=1e-5, reserved=0}`;WeatherColor
不执行 advection,色场取样和颜色路径不变。
搜索确认 C++ 中仅有两个 `ParticleGpuValidationUBO` 聚合初始化器:Particle renderer
已初始化 `advection`,WeatherColor 为本次唯一遗漏。此项修复后的 shader generator
不需要重跑(未修改 shader/generator 输入)。修复后的 WeatherColor 精确对象编译与
`git diff --check` 均已 PASS;编译日志按输出节流保存。随后外部终端完成完整
`mbgl-test` / `mbgl-test-runner` 重编译与重链并 PASS。
## 独立既有测试问题与下一步
全量目标构建 PASS 后,额外扩大过滤范围时发现当前 `HEAD` 已存在的 `Layer.WeatherColorUsesIndependentFixedOpacity` 断言错误:测试先成功调用 `setColorStops({{-2,...},{3,...}})` 改变状态,最后却仍断言等于更早的 `{0,10,20}`。`git show HEAD:test/style/style_layer.test.cpp` 已确认该错误在 STEP-068 开工前即存在,且本 Step 对该测试的 diff 只有新增 RasterParticle 测试。为保持任务隔离,本 Step 不修改该既有 WeatherColor 测试,也不把它作为 DIRECTION 关闭门槛。
STEP-068 相关实现、精确对象、完整 Linux 目标重链、8 个相关过滤测试与 `git diff --check` 均已通过,因此本 Step 正式 `CLOSED / PASS`。Android Gradle/AAR/APK/ADB 与 Jest 仍按任务要求未执行。下一步进入 V3-05:Android/JNI/RN generic v2 multi-plane ingress。
## Git 收尾
- 实现提交:`f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- commit message:`feat: add direction particle advection`
- push:成功
- 远端分支:`origin/particle/main`
- 实现提交 push 后:`HEAD == origin/particle/main == f3c33c67275b24f61b80924abbb9d10d68b2cb7c`
- STEP-062 两处用户修改及历史 untracked/build/cache/tgz 均未暂存、未提交。

View File

@@ -0,0 +1,23 @@
# STEP-069 MapLibreParticleLab V3-05 Android/JNI/RN v2 多 Plane 入口
状态:完成(IMPLEMENTED / WAITING_ANDROID_VALIDATION)
## 已完成
- [x] RN 包升为 `13.4.1-particle.3`,导出 `FieldPayloadRefs`、`FieldValueSource`;v2 主入口只接受 refs。
- [x] 保留 v1 `submitWindField` ABI,并提供明确的 `submitLegacyVectorField` 公共兼容入口。
- [x] Native TurboModule Spec 改为 codegen 支持的三个明确 string plane 参数;公共 `FieldRuntime.submitField(..., FieldPayloadRefs)` 保持不变,空 scalar/vector string 仅为 Native ABI 的 optional marker。Kotlin 在读取前校验 validity、local file、非空和三 plane aggregate 64 MiB;Java/JNI 用 presence flags 和长度恢复 optional bundle。
- [x] Java `WeatherRuntime.submitField` 给 nullable scalar/vector 标注 `@Nullable`,并独立拒绝 total 超过 64 MiB,避免绕过 Kotlin 后分配 oversized packed array。
- [x] `RasterArraySource` v1/v2 decode 后共用同一 duplicate、monotonic、compatibility、initial-window 与 Timeline 路径。
- [x] 桥接 `SCALAR|VECTOR_MAGNITUDE` 与 positive finite `advectionScale`,只更新既有 renderer config。
- [x] Core source 测试覆盖三种合法 v2 bundle、缺 validity/no channel 与 semantics incompatibility,以及 v1 legacy 与 compatible v2 vector-only 进入同一 `RasterArraySource` Timeline。
## 范围与验证
未改 WeatherV2、NavSea、Timeline/T0/T1/MIX/rollover/lifecycle,也未改 STEP-062 dirty 或无关 untracked 内容。以下均经 build-output-throttle:外部复核指定的 `combine-js-to-schema-cli.js /tmp/step069-schema.json packages/maplibre-particle-rn/src/NativeWeatherRuntimeBridge.ts` 实际 PASS;`./node_modules/.bin/tsc --noEmit -p packages/maplibre-particle-rn/tsconfig.json` PASS;`source.test.cpp.o` 精确编译 PASS;`ninja -C build-linux-opengl -j3 mbgl-test` 重链 PASS;`Source.HostFieldV2BundleUsesTheSameTimelineIngress:Source.HostFieldLegacyAndCompatibleV2VectorOnlyShareOneTimeline` 2/2 PASS。`git diff --check` PASS。Android Gradle/AAR/APK/ADB、Jest 均未执行;Android compile 留给 V3-08,不能标记 Android PASS。
## Git 收尾
- implementation commit:`148bf80fe41ba566de6c1d4b439cfe372a8a453c`(`feat: add v2 multi-plane field ingress`)。
- 本 Step 仅显式暂存实现文件;STEP-062 两处用户 dirty、历史 untracked、`.codex-build-logs/`、旧 tgz/build/cache 均未纳入。
- 文档 handoff commit 与 push/Final HEAD 在完成报告最终提交后回填。

View File

@@ -0,0 +1,38 @@
# STEP-070 MapLibreParticleLab V3-06 四类场 Fixture 与 RN 通用回归
状态:IMPLEMENTED / WAITING_ANDROID_VALIDATION
## 目标与边界
在 `particle/main` `3b012759` 上增加离线 V3 fixture、转换/校验工具、RN Test Host `.3` 公共 API 接线和最小 Core contract test。不修改 Timeline/Playback/T0/T1/MIX/rollover、schema、shader、WeatherV2、NavSea 或 STEP-062 两份用户 dirty 文档;不运行 Android Gradle/AAR/APK/ADB/Jest。
## 完成内容
- [x] `tools/particle_v3_fixture` 生成并校验 v2 metadata、shared validity、可选 scalar/vector、长度与 SHA-256。validator 对每一个 validity=1 的 cell 检查所有存在 plane:finite 且不等于 noData。
- [x] `wind-real` 从本地 canonical v1 `field.f32` 重新封装;shared validity 严格派生为 `finite(U) && finite(V) && U != -9999 && V != -9999`。
- [x] `wave-real` 由 WeatherV2 `ecmwf-wam/waves/2026090912/wave_bin` 的正式 `2026090918/2026090919` BIN 和 header sidecar 解码:scalar=`height_m`,vector=规范化 `unit_to_u/unit_to_v`,语义 `DIRECTION`;`period_s` 和原始 degree 不进入 canonical。
- [x] `tide-real` 由 WeatherV2 `cmems/tide/2026090900/current_bin` 的正式 `2026090901/2026090902` BIN 和 header sidecar 解码为 vector-only `VELOCITY`。
- [x] Wave/Tide 每帧 summary 记录正式源 BIN 与 sidecar SHA-256、target_time、referenceTime、bounds、grid、projection、included/excluded channels 和 validity 派生规则;两者 generation 为小且严格递增的 `1/2`。
- [x] Rain 保持 `rain-decoder-fixture`,manifest/provenance 明确为非真实 field values,因为本 Step 未实现 PMTiles reader;fallback generation 为严格递增的 `1/2`。
- [x] Test Host 的 `fieldDatasets` 已指向 `wave-real` 和 `tide-real`,继续仅经 package `FieldRuntime` 公共 API 提交 local file refs。
- [x] 保留并验证 Wave synthetic scalar/vector independence;新增 C++ provider-neutral 四类 shape contract test。
## 验证
- [x] `python3 tools/particle_v3_fixture/generate_fixtures.py --write-reference-fixtures --validate`:PASS。
- [x] package `.3` `tsc --noEmit`:PASS。
- [x] RN Test Host `npm run typecheck`:PASS(不含 Jest)。
- [x] RN TurboModule `combine-js-to-schema-cli.js`:PASS。
- [x] 使用 `CCACHE_DIR=/tmp/step070-ccache`,重新编译 `source.test.cpp.o`:PASS。
- [x] `mbgl-test` / `mbgl-test-runner` 完整 Linux OpenGL 重编译/重链:459/459 完成;`Source.ParticleFieldV3*` 过滤测试 6/6 PASS;验证链退出码 0。
- [x] `git diff --check`:PASS(提交前复核)。
- [ ] Android Gradle/AAR/APK/ADB/Jest:按任务范围未运行。
## 当前结果、阻塞与下一步
fixture 数据契约、TypeScript/codegen、Linux 完整重链与 V3 Source 过滤测试均已完成。Android/JNI 真实编译和设备视觉验收仍由后续统一周期完成。Rain 要升级为真实数值场,需要另行实现只读 PMTiles reader;不得把现有 fallback 改称 real。
## Git 收尾
- implementation:`8588e6fcb4f3f5d874159c2e42619c5227cb7921`(`feat: add V3 field fixtures and RN regression host`),已 push 至 `origin/particle/main`。
- docs-only Git handoff 在 `git add` 创建 `.git/index.lock` 时因只读文件系统被阻断,未提交、未推送;STEP-062 两份用户 dirty 文档与历史 untracked/build/cache/tgz 均未暂存。

View File

@@ -0,0 +1,37 @@
# STEP-071 — MapLibreParticleLab V3-07 Linux 综合冻结
状态:CLOSED / PASS(Linux)
日期:2026-09-11
基线:`fcc533dc9518395d0c25794131f53f0fae20d51c`
## 目标
在进入 V3-08 Android 统一验收前,对 V3-02~V3-06 的 canonical field、Host ingress、Timeline、RasterParticle 与 RN 公共桥接做一次 Linux 综合冻结。
## 冻结链结果
- [x] canonical fixture validator:PASS。
- [x] `packages/maplibre-particle-rn` TypeScript:PASS。
- [x] RN Test Host `npm run typecheck`:PASS。
- [x] RN TurboModule codegen:PASS。
- [x] `mbgl-test` / `mbgl-test-runner` Linux OpenGL target:PASS / up-to-date。
- [x] V3 综合过滤测试:21/21 PASS。
- [x] `git diff --check`:PASS。
- [ ] Android Gradle/AAR/APK/ADB/Jest:本 Step 不运行。
## 综合测试覆盖
过滤范围:`Source.ParticleField*`、`Source.HostField*`、`Source.RasterArrayTimeline*`、`Source.RasterArraySharesCanonicalSnapshotsWithWeatherColorLayer`、`Layer.RasterParticle*`。
最终 18 个 Source + 3 个 Layer,共 21 项全部 PASS。
## 冻结中发现并修正的测试问题
1. `HostFieldSubmissionRejectsInvalidWithoutChangingLastValidField` 旧测试用同一 `uniform-east` payload 构造 T0/T1,正确 Runtime 会先返回 Duplicate。测试改为 east→north 两份兼容但不同内容。
2. `RasterArrayTimelineCandidatesReachReadyThroughFieldAdapter` 使用的 east/north/vortex 旧 fixture 均为 `timeIndex=0`;当前 Adapter 保留 `validTime=timeIndex+1`,导致 Timeline 正确 fail-closed。测试仅在局部 metadata 副本中改为 0/1/2,未修改共享 fixture。
3. 两处均为 test fixture 陈旧,不修改 Runtime、Duplicate 判定顺序、Timeline fail-closed 或生产 API。
对应 Codex 任务:
- `docs/40_任务/STEP-071A_V3-07_Linux综合冻结_测试Fixture修正_Codex任务_v1.md`
- `docs/40_任务/STEP-071A_V3-07_Linux综合冻结_测试Fixture修正_Codex任务_v2.md`
## 边界
- 未修改 `src/`、`include/`、shader、JNI、Android、RN runtime、WeatherV2 或 NavSea。
- 已知既有 `Layer.WeatherColorUsesIndependentFixedOpacity` 断言债不属于本 Step,不修、不作为 V3-07 gate。
- STEP-062 用户 dirty 与历史 untracked/build/cache/tgz 均保持隔离。
- 下一步:V3-08,一次 Android 编译/安装/实体机综合验收。

View File

@@ -0,0 +1,57 @@
# STEP-072 — V3-08 Android Native v2 与单次综合验收准备
状态:IMPLEMENTED / WAITING_ANDROID_VALIDATION
日期:2026-09-11
基线:`dd356a277db05ce0075bee43f431ddeb2aba19cc`
## 目标
在不执行 Android 编译/安装的前提下,完成 V3-08 前的 Native/RN 版本坐标准备、Test Host 单 Bridge 收口与单次实体机验收流程冻结。
## 已完成
- [x] Native Android `VERSION`:`13.4.1-particle.1 → 13.4.1-particle.2`。
- [x] RN package `.3` 的 Android dependency 与 Expo plugin 统一指向 Native `.2`。
- [x] RN Test Host plugin 统一指向 Native `.2`。
- [x] release manifest 升为 RN `.3` + Native `.2`,Android 状态保持 `not_run`,并保留 `.2` 已发布历史追溯。
- [x] 删除 Test Host legacy local `@maplibre-particle/weather-runtime-bridge` tracked 源码/配置。
- [x] 删除 Test Host 根 `react-native.config.js`,`tsconfig.json` 只保留 `App.tsx` 与 `src`。
- [x] Expo autolinking 复核:只剩正式 `@navsea/maplibre-particle-rn` 提供 `WeatherRuntimeBridge`。
- [x] 建立 `docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md`。
## 静态验证
- [x] RN Test Host `npm run typecheck`:PASS。
- [x] package TypeScript:PASS。
- [x] RN TurboModule codegen:仅生成 `NativeWeatherRuntimeBridge`,PASS。
- [x] canonical fixture validator:PASS。
- [x] Expo autolinking:3 dependencies;包含 MLRN、正式 particle package、Expo;不再包含 legacy bridge。
- [x] 两个 Expo plugin `node --check`:PASS。
- [x] release manifest JSON:PASS。
- [x] `git diff --check`:PASS。
## 关键发现与修正
准备阶段发现 Test Host 会同时 autolink legacy local bridge 与正式 package bridge;二者 Native module name 都是 `WeatherRuntimeBridge`、codegen library 都是 `WeatherRuntimeBridgeSpec`。STEP-072A 删除了旧本地 bridge 注册与源码,使 V3-08 clean prebuild 前只剩正式 package bridge。
验收文档同时修正了 AAR `javap` 方法、Makefile `MLN_ANDROID_GRADLE` 的命令行覆盖方式、Linux→Win11 Maven Local handoff,以及 Win11 `gradlew.bat`/Metro/固定 ADB 设备流程。
## 未执行
- Android Gradle / AAR build / Maven publish / APK build / ADB / 实体机验收:未执行。
- Jest:未执行。
- 因此本 Step 不声明 Android PASS,也不创建 `.3` release tag。
## 下一步
用户按 `docs/60_测试/STEP-072_V3-08_Android单次综合验收_v1.md` 只执行一次 Android build/install 周期,覆盖 Wind、Rain、Wave、Tide 与 synthetic Wave 五组场数据。
## Git 实现基线
- implementation/preparation commit:`11cf58d344e64e6aff77deceb2c34c8eda045f1b`(`chore: prepare V3-08 android validation`)。
- 已成功 push 到 `origin/particle/main`;push 后实现基线 HEAD 与远端一致。
## 2026-09-11 V3-08 最终关闭
状态:**CLOSED / PASS**。
Win11/CARBON7i 使用 Gradle 8.14.3、AGP 8.12.0、Kotlin 2.1.20、JDK 17.0.18、NDK 27.1.12297006、compileSdk/targetSdk 36、minSdk 24 完成正式 npm 消费链与 Debug APK 构建。dependencyInsight 最终解析到 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.2`。
实体机 `192.168.1.146` 上五组数据均满足 T0=`ACCEPTED_PENDING_INITIAL_WINDOW`、T1=`INITIAL_WINDOW_READY`、最终 Runtime=`READY`:Wind real、Rain scalar-only、Wave real、Tide/Current、Wave synthetic independence。
控制面 PLAY/PAUSE、HALF/NORMAL/DOUBLE、FPS AUTO/30/60、seek T0/T1、Weather/Particle 独立显隐、Field stops、Particle color、Trail color、Apply visual 均返回 `ACCEPTED`;最终 FATAL/JNI/UnsatisfiedLinkError 扫描为空。
测试期间发现 fixture 位于 Metro project root 外,临时 Win11 `D:\V308` 验收 worktree 通过 `watchFolders` 纳入 `integration/react-native/particle-v3-fields` 后 bundle 成功。该临时 Metro 修改不进入正式仓库,不属于 Native/RN Runtime 缺陷。
最终产物:Native `.2` AAR SHA-256 `f890f834692d3cacb8a281b2d0f82cf4a7f8ba7756132092be800505b961e9a2`;RN `.3` tgz SHA-256 `16e5dab86aac9832f7a48d8c35dd9821f80f52e7e53ff4e0e6c98724781eda3a`;APK SHA-256 `49824062CAB72771282294114B5A818D2067F9B786B8697D2D2A983BF0B743E2`。

View File

@@ -0,0 +1,39 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator
状态:完成
日期:2026-09-13
## 目标
建立不接入 Decoder、Renderer、Android/JNI/RN 或 V3 生产路径的 QBIN2 V4.4 provider-neutral Core primitive,并以数据驱动 Contract Schema v1 验证 `DecodedFieldFrame`。
## 完成项
- [x] 建立 `ResolvedFieldProfileContract`、`DecodedFieldFrame`、`FieldSeries`、`EncodedFieldStore` 与独立 `ForecastClock`。
- [x] 以独立 Validity descriptor 加共享只读 payload 表达单一 Frame validity domain;descriptor 与 payload 使用同一 `shared_ptr`,不复制位图。
- [x] 实现纯数据驱动 `FieldProfileValidator`:schema、REQUIRED 双向一致、额外 plane、shared-validity 能力、时间关系及 Core 单位/速度组规则均返回结构化结果。
- [x] 保持普通 Scalar 的 semanticID/unitID opaque;无 profileID 产品分支。
- [x] 保持同 validTime 的 Duplicate、identity conflict、revision replacement 与相邻插值兼容性语义;fieldKey 独立 cadence/window。
- [x] 增加 C++ 自动化测试,并通过 Linux OpenGL 构建与 V3 Field/Timeline 定向回归。
## 允许修改
`include/mbgl/particle/field/`、`src/mbgl/particle/field/`、根/测试 CMake、`test/particle/`、本 Step、开发记录与完成报告。
## 禁止修改确认
未修改 Renderer、shader、`platform/android/`、RN/integration、Style JSON/source schema;未新增 QBIN decoder、JNI、Java/Kotlin 或 React API;旧 V3 Timeline/`submitField(...)` 路径未切换。
## 验证
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
以上构建/测试均通过输出节流脚本执行。未执行 Android Gradle、APK、ADB 或真机操作。
## 阻塞与下一步
无本 STEP 阻塞。下一步必须等待独立审计后再决定是否进入 QBIN2 Encoded Ingress / Decoder;本 STEP 未自行开展该工作。

View File

@@ -0,0 +1,40 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 审计修正 v2
状态:完成
日期:2026-09-13
## 目标
仅修正 STEP-073 v1 审计确认的 QBIN2 V4 Core/通用 Validator 三项问题:DEGREE Unit ID、Plane `(semanticID, groupID)` 唯一性与 ForecastClock HALF tick 舍入漂移。
## 完成项
- [x] `FieldUnitDegrees` 改为冻结 QBIN V2 registry 的 numeric ID `4`;`NONE=0`、`MPS=1` 未变。Direction `unitID=4` 可接受,`unitID=2` 返回 `InvalidCoreUnit`。
- [x] 实际 Frame 与 Contract `requiredPlanes[]` 均以 `(semanticID, groupID)` 判重;component 改为 descriptor 属性。semantic/group 已定位但 component 不同返回 `PlaneDescriptorMismatch`。
- [x] ForecastClock 以整数 half-millisecond remainder 累积,HALF 不再逐 tick 向上取整;seek 清除 remainder,溢出 fail-closed。
- [x] U/V 继续使用各自 semanticID,并按既有通用 U_EAST/V_NORTH velocity group 规则通过。
- [x] 新增对应 C++ 自动化覆盖并完成 Linux 定向验证与 V3 回归。
## 允许修改
仅修改 `include/mbgl/particle/field/`、`src/mbgl/particle/field/`、`test/particle/field_runtime.test.cpp`、本 Step、开发记录及完成报告。
## 禁止修改确认
未修改 Renderer、shader、`platform/android/`、RN/package/integration、现有 V3 ingress/timeline 生产路径;未实现 QBIN2 Decoder、Binding/Projection、JNI 或 Android/RN 接入。
## 验证
所有构建/测试均经 `build-output-throttle` 的 `run_limited.py` 执行:
- `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- `git diff --check`:PASS。
未执行 Android Gradle、APK、ADB、真机操作或 Jest;未执行 iOS/Metal 验证。
## 阻塞与下一步
本审计修正无阻塞。后续工作须另行批准;本 STEP 在此停止,不进入 QBIN2 Decoder 或任一接入阶段。

View File

@@ -0,0 +1,40 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 审计修正 v3
状态:完成
日期:2026-09-13
## 目标
仅关闭独立审计发现的 Generic Field Profile Validator 协议遗漏:Native 已知 Core semantic 不得被 Contract 与 Frame 联合伪装,且 Direction Circular Quantization 必须有完整的 provider-neutral 结构约束。
## 完成项
- [x] 在 Validator 内建立静态、数据表驱动的 Native Core semantic contract:`1=VELOCITY_COMPONENT/MPS/U_EAST`、`2=VELOCITY_COMPONENT/MPS/V_NORTH`、`3=VALIDITY/NONE/NONE/group0`、`10/11=DIRECTION_ANGLE/DEGREE/NONE/group0`。表不依赖 profile/product 名称。
- [x] Contract 与实际 Frame 都在原有 descriptor 比较前核对该表;冲突返回结构化 `SemanticContractMismatch`。
- [x] Unknown `SCALAR` semanticID 和 unitID 仍为 opaque,继续经过 Contract/Frame descriptor 一致性检查而不加入 Native unit 白名单。
- [x] 所有 `DIRECTION_ANGLE` 强制 `DEGREE(4)`、`NONE`、`groupID=0`、`UINT16`、`offset=0`、`scale=360/65536`(允许极小 float32 表示误差);违反返回 `InvalidCoreUnit` 或已知 semantic 的 `SemanticContractMismatch`。
- [x] 覆盖已知 semantic 伪装、正确 Direction、Direction component/group/dtype/scale/offset/unit 拒绝、opaque Scalar、既有 plane identity、DEGREE registry 与 ForecastClock HALF 行为。
## 允许修改
仅修改 `include/mbgl/particle/field/field_profile_validator.hpp`、`src/mbgl/particle/field/field_profile_validator.cpp`、`test/particle/field_runtime.test.cpp`、本 Step、完成报告与开发记录。
## 禁止修改确认
未修改 QBIN2 Decoder、Binding/Projection、Renderer、shader、JNI、`platform/android/**`、`packages/maplibre-particle-rn/**`、`integration/react-native/**` 或现有 V3 生产入口;未实现 parser/header/CRC/packing。
## 验证
所有 noisy 命令均经 `build-output-throttle` 的 `run_limited.py` 节流执行:
- [x] `cmake --build build-linux-opengl --target mbgl-test -j3`:PASS。
- [x] `build-linux-opengl/mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:PASS。
- [x] `build-linux-opengl/mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:PASS。
- [x] `git diff --check`:PASS。
未执行 Android Gradle、APK、ADB、真机、Jest 或 iOS/Metal。
## 当前结果、阻塞与下一步
本轮所有范围内项完成,无阻塞。本 STEP 在此停止,等待 ChatGPT 独立审计;不进入 STEP-074 或任何 QBIN2 ingress/Decoder 工作。

View File

@@ -0,0 +1,45 @@
# STEP-073 — QBIN2 V4 Core 与通用 Field Profile Validator 最终关闭修正 v4
状态:完成
日期:2026-09-13
## 基线
- 开始 HEAD:`0b66a5cd30e91423cbc864a327a53bed06cae91c`。
- 开始工作区含 STEP-062 文档修改及多项历史 untracked 文件;本 Step 未修改、暂存或提交它们。
## 目标
关闭独立审计在实际重链 `mbgl-test-runner` 后发现的三项问题:Frame 的唯一共享 validity domain、实际已知 Core semantic 的错误优先级,以及 ForecastClock 测试的 pause/resume 语义。
## 完成项
- [x] `DecodedFieldFrame` 允许空 numeric `planes[]`,完整 designated validity 仍结构合法;所缺 required numeric plane 由 Validator 返回 `MissingRequiredPlane`。
- [x] designated validity 强制 `Validity/Bitset/NONE/group0/None/REQUIRED`、cell count 与 bit payload 长度一致,并与 `validityBits` 使用同一 `shared_ptr`;任一违反均为 `InvalidFrame`。
- [x] 禁止 `planes[]` 出现第二张 Validity plane,确保一帧只有唯一共享 validity domain。
- [x] 保留静态数据表驱动的 Core semantic contract,并在 required-plane descriptor 匹配前检查实际 plane;semantic 1 Scalar 伪装、semantic 10 component/group 冲突均返回 `SemanticContractMismatch`。
- [x] unknown Scalar 及 opaque unit 的可扩展行为保持;Direction circular quantization v3 规则未变。
- [x] ForecastClock 测试在 `pause()` 后显式 `play()`,未改变生产 `pause()` 语义。
## 允许修改
仅修改 Field Runtime Core、通用 Validator、对应 C++ 测试和本 Step 文档/报告/开发记录。
## 禁止修改确认
未修改 QBIN2 Decoder、Binding/Projection、Renderer、shader、JNI、`platform/android/**`、`packages/maplibre-particle-rn/**`、`integration/react-native/**`、Style JSON/source schema 或 V3 `submitField(...)` 路径;未执行 Android Gradle、APK、ADB、真机、Jest 或 iOS/Metal。
## 验证
构建/测试完整输出均落入 `/tmp` 限流日志(仓库未提供 build-output-throttle 指定的 helper script,仅在失败时读取有限末尾窗口)。
- [x] `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:实际重编译并重链 PASS。
- [x] `mbgl-test-runner --gtest_list_tests`:确认包含 `ParticleFieldProfileValidator.` 与 `ParticleFieldRuntime.`。
- [x] `mbgl-test-runner --gtest_filter='ParticleFieldRuntime.*:ParticleFieldProfileValidator.*'`:实际运行 11 tests,11 PASS,0 failed;绝非 0 tests。
- [x] `mbgl-test-runner --gtest_filter='Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*'`:实际运行 11 tests,11 PASS,0 failed。
- [x] `git diff --check`:PASS。
## 当前结果、阻塞与下一步
STEP-073 v4 的关闭条件已满足,无范围内阻塞。停止于此,等待 ChatGPT 独立复验;不进入 STEP-074。

View File

@@ -0,0 +1,9 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v1
状态:完成(Core 范围)。开始 HEAD:`b8a629b7b65067f7f561db473312484bd8f0451d`。开始工作区已有 STEP-062 文档修改及历史 untracked 文件,未清理、暂存或提交。
新增独立 `mbgl::particle::ingress::QbinV2Decoder` 和无状态 `EncodedFieldIngress`。其顺序为 QBIN structural decode、构造 `DecodedFieldFrame`、再调用既有 generic `FieldProfileValidator`;未接入 Store、Renderer、Binding、Host 或平台 bridge。
Decoder fail-closed 校验 QBV2 LE header/descriptor、保留字段、FRAME/WebMercator/node 限制、时间/边界/容量、精确 canonical packing 与零 padding、ISO-HDLC CRC(CRC 字段虚拟清零)、single shared validity、invalid-cell raw-zero,以及 per-plane local content identity。numeric payload 保持原始 bytes。
真实 CMake runner 已加入 `ParticleQbinV2Decoder`、`ParticleEncodedFieldIngress`;fixture 仅在 test 范围,涵盖 2×2 与 513×769 构造。

View File

@@ -0,0 +1,36 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v2
状态:完成
日期:2026-09-13
## 基线
- 开始 HEAD:`83a84452f23d019cbb143630eac04631552fef89`。
- 开始前已有 STEP-062 文档修改及历史 untracked 文件;未清理、覆盖、暂存或提交。
## 完成项
- [x] per-plane `contentIdentity` 改为 raw bytes 加 descriptor 的明确 Little Endian canonical byte stream;不含 `byteOffset`。
- [x] 64 MiB 为整份 QBIN 上限,32 MiB 为各 Plane raw `byteLength` 总和上限;canonical padding 不计入 decoded data。
- [x] BITSET 与 `VALIDITY` 双向强制;构造后以 `DecodedFieldFrame::isStructurallyValid()` 作最终 fail-closed 自检。
- [x] test-only fixture writer 对整数、float32、float64 明确写 Little Endian。
- [x] decoder/ingress runner 扩为实际 18 tests,包含 header、descriptor、limits、packing、padding、CRC、validity、invalid-cell、identity、513×769 与 Contract 矩阵。
- [x] unknown Scalar opaque unit、unknown field semantic 与 `validTimeUtc < referenceTimeUtc` 的 decoder 容器行为均通过。
## 验证
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(实际重编译/重链)。
- `mbgl-test-runner --gtest_list_tests`:PASS,确认 suites 已注册。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:18/18 PASS。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:STEP-073 11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:既有 V3 11/11 PASS。
- `git diff --check`:PASS。
## 范围确认
只修改 QBIN decoder/header 与 C++ test,未进入 Store submission、Binding/Projection、Renderer/shader、JNI、Android、RN、Test Host、URL/HTTP/download/gzip、iOS/Metal 或 Jest。
## 阻塞与下一步
无范围内阻塞。本 Step 到此关闭;提交并 push 后停止,不进入下一 Step。

View File

@@ -0,0 +1,49 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v3
状态:完成
日期:2026-09-13
## 目标
在不扩展 STEP-074 生产范围的前提下,收口 Decoder 入口错误、Little Endian fail-closed 与 32 MiB raw decoded-data 上限的最终测试覆盖。
## 基线
- 开始 HEAD:`56b8dab90f914438e6fcaec6278146c96c8df638`。
- 开始前已有 STEP-062 文档修改及历史 untracked 文件;均未清理、覆盖、暂存或提交。
## 必做项
- [x] 空 span、1 byte、111 bytes 都经 production `QbinV2Decoder::decode` 返回 `InputTooSmall`。
- [x] 112-byte 非 `QBV2` 输入返回 `BadMagic`;空 `fieldID`、零 `generation` 返回 `InvalidRuntimeMetadata`。
- [x] 明确验证 `dataVersion=0` 的合法 frame 仍 Accepted。
- [x] Header width 写成 big-endian `00 00 00 02` 并重算 CRC 后以 `LimitExceeded` 结构化拒绝。
- [x] Descriptor count 写成 mixed-endian `00 00 00 04` 并重算 CRC 后以 `InvalidDescriptor` 结构化拒绝。
- [x] raw sum 为 31,712,633 bytes、canonical payload 为 31,712,648 bytes 的 1447×1449 FRAME Accepted;15 bytes padding 未计入 32 MiB decoded limit。
## 允许修改
- `test/particle/qbin_v2_decoder.test.cpp`。
- 本 Step 记录、完成报告与开发记录。
## 禁止修改
- Host、网络、JNI、Android/RN、Binding/Projection、Renderer、Store submission 与 production Decoder。
## 构建与测试
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(经输出限流器,实际重编译/重链)。
- `mbgl-test-runner --gtest_list_tests`:PASS,Decoder/Ingress suite 已注册。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24 PASS。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:STEP-073 11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:既有 V3 11/11 PASS。
- `git diff --check`:PASS。
## 当前结果
仅补 6 个 production decode 覆盖测试,未发现生产 Decoder 缺陷,故未修改生产实现。STEP-074 由 v2 的 18 tests 扩至实际 24 tests,全部通过。
## 阻塞与下一步
无范围内阻塞。按任务要求完成 scoped commit 与 `origin/particle/main` push 尝试后停止,不进入 STEP-075。

View File

@@ -0,0 +1,46 @@
# STEP-074 — QBIN2 V4 Encoded Ingress 与 Decoder v4
状态:完成
日期:2026-09-13
## 目标
仅修正 v3 `AcceptsRawLimitWithCanonicalPaddingExcluded` 的边界 fixture,使它严格证明 decoded raw 数据不超过 32 MiB、canonical packed payload 超过 32 MiB、整个 QBIN 文件不超过 64 MiB 时 production Decoder 仍接受。
## 基线
- 开始 HEAD:`f4a495ff38b77f586f97853493dda02ab067b26d`。
- 开始前已有 STEP-062 文档修改及历史 untracked/build 文件;均未清理、覆盖、暂存或纳入本 Step。
## 必做项
- [x] `AcceptsRawLimitWithCanonicalPaddingExcluded` 改为 1101×1890(2,080,890 cells)、17 plane:1 个 single shared validity BITSET 与 16 个 UINT8 numeric plane;descriptor 依 semantic 3、4–19 canonical 排序。
- [x] 测试直接计算并断言 `rawSum=33,554,352 <= 33,554,432`、`payloadLength=33,554,448 > 33,554,432`、`payloadLength > rawSum`、`totalQbinBytes=33,555,104 <= 67,108,864`。
- [x] fixture 经 production `QbinV2Decoder::decode(...)` 返回 Accepted。
- [x] cells、planes、canonical packing、single shared validity、descriptor 与 64 MiB 文件限制均在构造及断言内满足。
## 允许修改
- `test/particle/qbin_v2_decoder.test.cpp`。
- 本 Step 记录、完成报告与粒子开发记录。
## 禁止修改
- Production Decoder、Host、网络、JNI、RN、Binding/Projection、Renderer、Store submission、Android/iOS 平台路径。
## 构建测试
- `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(输出节流器;实际目标已重新检查并确认最新)。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:实际 Running 24,24/24 PASS(XML:Decoder 22、Ingress 2)。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:实际 Running 11,11/11 PASS。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:实际 Running 11,11/11 PASS。
- `git diff --check`:PASS。
## 当前结果
旧 fixture 的 payload 仍低于 32 MiB,不能捕获把 packed payload 误当 decoded raw limit 的实现错误。新 fixture 以 16 个每个 6-byte canonical padding 的 numeric plane 将 payload 推至 limit 以上 16 bytes,同时 raw sum 比 limit 小 80 bytes;production Decoder 接受,未发现生产缺陷,故未修改 production 代码。
## 阻塞与下一步
无范围内阻塞。未执行 Android Gradle/APK/ADB/Jest;按任务要求停止,不进入下一 Step。

View File

@@ -0,0 +1,36 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Content Identity v1
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`31783f25a8153f48000f19ac7cc2f2113cf47818`。
- 仅实现 provider-neutral Core Binding/Projection;没有接入 Renderer、GPU、JNI、Android、RN、NavSea 或网络。
## 必做项
- [x] 新增独立 `ColorBinding`、`ParticleBinding`、`ColorProjectionContentIdentity`、`ParticleProjectionContentIdentity` 与 `BindingProjectionResolver`。
- [x] Color 可选择 Scalar/Velocity/Direction numeric plane;unit 只作 equality assertion。
- [x] Particle 支持同 fieldKey/window 的 U/V vector 与 DirectionAngle 两种模式;没有定义速度大小或 Renderer 行为。
- [x] 每次 resolve 以 binding 的 fieldKey 独立调用 `EncodedFieldStore::resolve`,保留 Core-owned requested time 与每 field window。
- [x] SampleState 仅保存 requestedForecastTimeMs/mix;content identity 不纳入 mix、播放状态或视觉参数,且保留只读 frame/storage shared_ptr。
- [x] 结构化 fail-closed 返回 InvalidBinding、FieldSeriesMissing、WindowUnavailable、PlaneMissing、Class/Unit/Component/Group mismatch 与 InterpolationIncompatible。
- [x] 测试 wind `00/01/02` 与 wave `00/03/06` 在 `01:30` 使用各自窗口;MIX `0/.25/.5/.99` 保持 identity/storage;Scalar-only revision 仅改变 Color identity;覆盖 Direction 成功及 unit/selector failure。
## 验证
- Linux OpenGL `mbgl-test-runner` 真实重建:PASS(输出节流)。
- 规定过滤组 XML:50 tests、0 failures、0 errors:Binding 4,Runtime/Profile 11,QBIN Decoder/Ingress 24,V3 Source 11。
- `git diff --check`:PASS。
## 边界与未执行
- FieldSeries 在提交阶段已拒绝不可插值相邻 frame;Resolver 仍保留 `InterpolationIncompatible` 结构化保护,正常 Store 无法形成该窗口。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal 或任何 Host/Renderer 验证;它们不属于本 Step,不能视为 PASS。
- 任务开始前既有 STEP-062 dirty、历史 untracked、build logs 与 RN 输出均未清理、暂存或提交。
## 下一步
STEP-075 关闭;按任务要求停止,不进入 STEP-076。

View File

@@ -0,0 +1,46 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Identity v2
状态:完成
日期:2026-09-13
## 目标与基线
- 基于 STEP-075 v1(`3616daec30079395847cc6a538fbd963641b7e12`)完成独立审计收口。
- 仅修改 provider-neutral Core Binding/Projection、其离线单元测试与本 Step 文档;没有进入 STEP-076。
## 必做项
- [x] 新增 `ProjectionFrameCoordinate`,只以 `fieldID + validTimeMs` 表达 T0/T1 window coordinate;完整 `FrameContentIdentity` 仍保留在 resolved frame 中作 revision/runtime 诊断。
- [x] Color 和 Particle identity 均统一比较 binding、T0/T1 coordinate、各自 selected plane content identity 及 T0/T1 shared validity content identity。
- [x] 移除 Resolver 公共 `InterpolationIncompatible` 错误码;`FieldSeries::submit` 已拒绝不兼容相邻 frame,`resolve` 只返回已验证 window。
- [x] 通过 Store 的 same-valid-time `ReplacedRevision` 测试 Scalar-only、U/V-only、validity-only revision:分别为 Color-only change、Particle-only change、两者均 change。
- [x] 保持各 binding 独立 fieldKey/cadence、MIX 不进入 identity、in-window storage stable,以及 DirectionAngle success/failure coverage。
## 允许修改
- `include/mbgl/particle/field/field_binding.hpp`
- `src/mbgl/particle/field/field_binding.cpp`
- `test/particle/field_binding.test.cpp`
- 本 Step v2、完成报告 v2 与 `粒子开发记录.md`
## 禁止修改
- 未修改 Decoder、FieldSeries、Profile Validator、Renderer、Host/JNI/RN、Android/iOS 或网络数据路径。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal、Renderer/GPU 或真实 QBIN Golden 数据。
## 验证与当前结果
- Linux OpenGL `cmake --build build-linux-opengl --target mbgl-test-runner -j3` 真实重建 PASS(输出节流)。
- 规定 GTest XML `/tmp/step075-gtest.xml`:50 tests、0 failures、0 errors。Binding 4;Runtime/Profile 11;QBIN Decoder/Ingress 24;V3 Source 11。
- `git diff --check` PASS。
## Git
- scoped commit:`90d05f596a42c157502f9a2a5f20425c999cebab`,`fix: close STEP-075 projection identity audit`。
- 已成功 push:`3616daec3..90d05f596 particle/main -> particle/main`。
## 阻塞与下一步
- 无实现阻塞。Android/Jest/iOS/Metal 等禁止范围没有验证,不能视为 PASS。
- STEP-075 已关闭;按任务书停止,不进入 STEP-076。

View File

@@ -0,0 +1,49 @@
# STEP-075 — QBIN2 V4 Binding 与 Projection Identity v3
状态:完成
日期:2026-09-13
## 目标与基线
- 基于 `653e6f2f1598b7817b7912a39877690f248d6f25`,仅关闭 inactive selector 污染 `ParticleBinding` identity 的审计问题。
- 不进入 STEP-076;测试仅使用离线 C++ fixture。
## 必做项
- [x] `ParticleBinding::operator==` 改为 mode-aware:VectorComponents 只比较 U/V selector;DirectionAngle 只比较 direction selector;两者均继续比较 mode、fieldKey 与 expectedUnitID。
- [x] selector identity 继续仅为 `(semanticID, groupID)`;component 保持为 resolved Plane descriptor assertion,不进入 identity。
- [x] VectorComponents 覆盖:仅变更 inactive `directionPlane` 时 binding equality 与 resolved `ParticleProjectionContentIdentity` 均相同;变更 active U selector 则 binding 不同且 `PlaneMissing` fail-closed。
- [x] DirectionAngle 覆盖:仅变更 inactive U/V selectors 时 binding equality 与 resolved identity 均相同;变更 active direction selector 则 binding 不同且 `PlaneMissing` fail-closed。
## 允许修改
- `include/mbgl/particle/field/field_binding.hpp`
- `src/mbgl/particle/field/field_binding.cpp`
- `test/particle/field_binding.test.cpp`
- 本 Step、完成报告 v3 与 `粒子开发记录.md`
## 禁止修改
- 未修改 Decoder、Runtime、Renderer、Host/JNI/RN/Android,未进入 STEP-076。
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal,未使用真实服务器 QBIN。
## 验证与当前结果
- Linux OpenGL:`cmake --build build-linux-opengl --target mbgl-test-runner -j3`,真实重建 PASS(全局 build-output-throttle)。
- `ParticleFieldBinding.*`:6/6,0 failure。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:11/11,0 failure。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24,0 failure。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:11/11,0 failure。
- `git diff --check` PASS。
## Git
- scoped commit:`2645bd725f63c4da12ab48a0cc11ff034c9d9878`,`fix: close STEP-075 binding selector identity audit`。
- push result:`653e6f2f1..2645bd725 particle/main -> particle/main`;remote branch:`origin/particle/main`。
- Final HEAD(scoped implementation delivery):`2645bd725f63c4da12ab48a0cc11ff034c9d9878`。
## 阻塞与下一步
- 无实现阻塞。
- STEP-075 v3 已关闭;按任务书停止,不进入 STEP-076。

View File

@@ -0,0 +1,34 @@
# STEP-076 — QBIN2 V4 Core Host API 与 RasterArraySource 接线 v1
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`b148fb6a0c6711182194f6778d80c8f8a60eb037`。
- 任务前 STEP-062 两处 dirty、历史 untracked 和 build 输出保持原样,未暂存、覆盖或提交。
- 本 Step 仅在 C++ Core / style source 接入 QBIN V4;V3 timeline、renderer、JNI/Java/RN/Test Host 均不变。
## 完成项
- [x] `RasterArraySource` 新增隔离 V4 owner 状态:`EncodedFieldStore`、`ForecastClock`、可选 Color/Particle binding;与 V3 `RasterArrayTimelineRuntime` 生命周期和数据完全分离。
- [x] 新增 `submitEncodedField`,严格按 fieldKey/runtime metadata → `QbinV2Decoder::decode` → `FieldProfileValidator::validate` → `EncodedFieldStore::submit` 顺序执行;失败在 Store 提交前返回,结果稳定区分 insert/replace/duplicate/decode/contract/key/identity/stale/incompatible/capacity。
- [x] 新增 binding、requested forecast time、playback state/speed、clock advance、独立 color/particle resolve、单 field / all field clear API;没有 Host 可写 T0/T1/MIX。
- [x] binding 只保存 Host intent;resolve 使用既有 `BindingProjectionResolver` fail-closed。clear 保留 binding intent,且绝不调用 V3 `clearHostFields()`。
- [x] 新增离线 QBIN deterministic suite:submission reject 后旧 window 可继续 resolve;wind `00/01/02` 与 wave `00/03/06` 各自解析 window/mix,clock in-window advance 只改变 mix 不改变 content identity,pause 和 HALF/NORMAL/DOUBLE 语义覆盖;clear/re-submit 生命周期覆盖。
## 验证
- 先通过全局 `build-output-throttle` 配置,再真实执行 `cmake --build build-linux-opengl --target mbgl-test-runner -j3`。构建环境的默认 ccache 位于只读目录,故仅将本次 ccache 临时目录置于 `/tmp`;重建成功。
- `Source.RasterArrayV4HostApi*`:3/3,0 failure。
- `ParticleFieldBinding.*`:6/6,0 failure。
- `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:11/11,0 failure。
- `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:24/24,0 failure。
- `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:11/11,0 failure。
- `git diff --check`:PASS。
## 边界、阻塞与下一步
- 未执行 Android Gradle/APK/ADB/Jest、iOS/Metal;未修改 Android/JNI/RN、Renderer、shader、Test Host;未使用真实服务器 QBIN,也未新增网络/缓存/gzip 逻辑。
- 无实现阻塞。本 Step 到 C++ Host API 为止,按任务书停止,不进入 Android/JNI/RN bridge。

View File

@@ -0,0 +1,39 @@
# STEP-076 — QBIN2 V4 Core Host API 与 RasterArraySource 接线 v2
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`ae03d8bfd2813eb5e65074db16018e37c7cff1c4`。
- 任务开始前检测到 STEP-062 两处已有 dirty,以及历史 untracked/build 输出;均未修改、暂存、提交或清理。
- v1 production 接线保持不变;本 v2 只补 V4 Host API public-path 测试证明与任务文档。
## 完成项
- [x] 针对 `RejectedDecode`、`RejectedContract`、`RejectedInvalidFieldKey`、`RejectedStaleRevision`,先由公开 `RasterArraySource` API 建立可 resolve 的 V4 Particle projection;每次 reject 后重新 resolve,并验证 content identity、T0/T1 coordinate、U/V selected-plane storage 与 shared validity storage 仍为同一内容身份。
- [x] 保留 v1 三个 Source V4 测试,并新增 `RasterArrayV4HostApiRejectsKeepPublicProjectionStable`;该 suite 已为 4 个测试。
- [x] clear 测试先通过公开 V3 `submitInitialRuntimeFieldWindow(...)` 和既有离线 fixture 建立 V3 `field/nextField`。`clearEncodedField`、`clearAllEncodedFields` 后均核验 V3 双端 identity/generation/validTime 不变;V4 resolve 按 fail-closed 返回,重新提交 V4 后既有 binding intent 再次 resolve。
- [x] 未调用 `clearHostFields()`;原子性证明未访问 V4 `encodedFieldStore` 私有成员或私有 helper。
## 修改范围
- `test/source/raster_array_v4_host_api.test.cpp`
- 本 Step、完成报告与 `粒子开发记录.md`
- 未修改 production API、Renderer/shader、JNI、Android/RN/Test Host、QBIN decoder 或 Field Runtime/Binding production 代码。
## 验证
- [x] 真实执行 `cmake --build build-linux-opengl --target mbgl-test-runner -j3`,通过全局 `build-output-throttle`,PASS。
- [x] `Source.RasterArrayV4HostApi*`:Running 4,0 failure。
- [x] `ParticleFieldBinding.*`:Running 6,0 failure。
- [x] `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`:Running 11,0 failure。
- [x] `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`:Running 24,0 failure。
- [x] `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`:Running 11,0 failure。
- [x] `git diff --check`:PASS。
## 边界、阻塞与停止条件
- 未执行 Android 编译、Gradle/APK、ADB、实体机、Jest 或 iOS/Metal。
- 无阻塞;仅完成本任务书的审计收口,停止,不进入下一 Step。

View File

@@ -0,0 +1,29 @@
# STEP-077 QBIN2 V4 Android / JNI / RN 桥接只读调查 v1
状态:完成
日期:2026-09-13
## 目标与结果
- [x] 基于实际 RN、Kotlin、Java、JNI、`RasterArraySource` 源码完成 V4 桥接调查。
- [x] 给出 file URI 传输、small control-plane ABI、V3/V4 timeline/clear 隔离、结果和 lifecycle 映射建议。
- [x] 生成调查报告、完成报告并追加开发记录。
## 范围
只读调查;未修改 production code、package version、Test Host、Renderer/GPU,未下载真实 QBIN,未执行 Android 编译、APK、ADB 或实体机操作。
## 核心结论
V4 必须新增独立 `submitEncodedField` / binding / forecast / clear 命名空间。V3 `submitField`、`clearFields`、forecast/playback API 当前会进入 V3 Host field / timeline,不能复用。RN 传 `qbinFileUri`,Kotlin 以现有 64 MiB local-file guard 读取,再走 Java `byte[]` 和 JNI copy;不要使用 JS number array 或 Base64。
## 验证
- [x] 对要求的全部实际源码路径、JNI registration、Core public V4 API、package codegen/typecheck config 和 `WeatherRuntimeStateTest` 做静态审计。
- [x] `git diff --check`(仅本任务文档)将在提交前执行。
- [ ] Android/Gradle/APK/ADB/实体机:任务明确不执行。
## 阻塞与下一步
无调查阻塞。后续只能按报告中拆分实现 Step,先做 Spec/ABI 与 V4-only facade/JNI 隔离测试;本 Step 到此停止。

View File

@@ -0,0 +1,44 @@
# STEP-078 — QBIN2 V4 Android / JNI / RN 桥接实现 v2
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`4b5192e224613a885c1d038594fe06c5eb58df50`。
- 保留 v1 未提交 bridge 草稿,并修正其已确认的 ABI/契约问题。
- 开始时已有 STEP-062 两处 dirty、历史 untracked 与 `.codex-build-logs/`;未清理、改写、暂存或提交它们。
## 完成项
- [x] RN → TurboModule → Kotlin → Java → MapLibreMap → NativeMap/NativeMapView → JNI → `RasterArraySource` 完整贯通独立 V4 submit、两类 binding、forecast time/state/speed 与两类 clear API。
- [x] `EncodedColorBindingInput.expectedUnitID` 端到端传递;不在 bridge 默认为 NONE。
- [x] `ParticleBinding` ABI 固定 U/V/direction 三组 primitive selector slot;Vector 只接受 active U/V,Direction 只接受 active direction,inactive slot 仅允许 `(0,0)`。
- [x] forecast playback state 与 speed 分别调用 `setForecastPlaybackState`、`setForecastPlaybackSpeed`,没有互相覆盖。
- [x] frozen `contractJson` 使用 `contractSchemaVersion`、`requiredFlag` 等 Core 同名字段;新增无 Android/JNI 依赖的纯 C++ parser,JNI 仅调用该 parser。
- [x] parser 单测覆盖合法 schema、缺字段、未知 schema、整数越界、重复 selector、非法 enum、requiredPlanes 上限及非法 profileID。
- [x] QBIN 仍仅通过绝对路径或 `file://` local file URI 进入 Kotlin,沿用读取前后 1..64 MiB guard;没有 URL、HTTP、Base64 或 JS number array。
- [x] Core 十种 `RasterArrayEncodedFieldSubmitResult` 保持一一 `.name` resolve;source 缺失/类型不符只在私有 JNI `0` sentinel 转为 `SOURCE_MISSING` Promise rejection,不伪装 Core result。
## 实际修改范围
- RN/TurboModule/Kotlin:`FieldRuntime.ts`、`types.ts`、`NativeWeatherRuntimeBridge.ts`、`WeatherRuntimeBridgeModule.kt`。
- Java/JNI:`WeatherRuntime`、`MapLibreMap`、`NativeMap`、`NativeMapView`、`native_map_view.hpp/.cpp`。
- 新增 provider-neutral parser:`field_profile_contract_json.hpp/.cpp` 及 `field_profile_contract_json.test.cpp`,并注册 Core/Test CMake。
- 未修改 Renderer、GPU、shader、Test Host、NavSea、STEP-073~076 已冻结 production 语义。
## Linux 与静态验证
- [x] 在 `particle-codex` tmux session 中通过全局 `build-output-throttle` 重建 `mbgl-test-runner`。
- [x] parser 单测、STEP-076 Source V4、STEP-075 Binding、STEP-074 QBIN/Ingress、STEP-073 Runtime/Profile 与 V3 Source 过滤回归:PASS。
- [x] package TypeScript `tsc --noEmit`:PASS。
- [x] TurboModule schema combine/codegen:PASS(仅 `NativeWeatherRuntimeBridge` Spec)。
- [x] Java native declarations 与 C++ JNI registration V4 name set 静态一一核对:PASS。
- [x] `git diff --check`:PASS。
## 未执行、阻塞与停止
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、ADB、实体机:按任务约束未执行,待后续一次性 Android 验收。
- 未执行 iOS/Metal 或 Jest。
- 无实现阻塞;本 Step 到此停止,不进入 STEP-079。

View File

@@ -0,0 +1,44 @@
# STEP-078 — QBIN2 V4 Android / JNI / RN 桥接实现 v3
状态:完成
日期:2026-09-13
## 目标与基线
- 基线:`73779b89bcce1094eb344f4727d6d23904f667f3`。
- 仅关闭 V4 bridge 的 QBIN transport 三层 64 MiB guard 与受限 ID 的 UTF-8 byte-length guard;不进入 STEP-079。
- 任务开始前 STEP-062 两处 dirty、历史 untracked、`.codex-build-logs/` 与 Test Host generated/android/.expo/tgz 均未清理、改写、暂存或提交。
## 完成项
- [x] Java `WeatherRuntime.submitEncodedField(...)` 在 map/JNI 前拒绝空及超过 64 MiB QBIN,并以 UTF-8 1..64 bytes 检查 fieldKey、fieldID 和 contract JSON 中 profileID。
- [x] JNI 在读取 `jbyteArray` 长度后、复制到 `std::vector` 前重复检查 1..64 MiB;无效 transport 直接返回既有 `RejectedDecode`,不产生 oversized vector。
- [x] Kotlin local-file 既有 1..64 MiB read guard 保持;Kotlin fieldKey、fieldID、profileID 改为 `toByteArray(Charsets.UTF_8).size` 的 1..64 bytes 检查。
- [x] RN/TS 改用 `TextEncoder` UTF-8 bytes,fieldKey、runtime fieldID 与 profileID 在 33 个 CJK 字符(99 bytes)时 fail-fast。
- [x] 新增 provider-neutral C++ transport-size helper/test、JDK-only Java guard test 与 TS directed test;未改变 QBIN decoder 的 64 MiB file / 32 MiB decoded-raw 语义、Core 十种 submit result、binding 或 forecast state/speed。
## 修改范围
- RN:`FieldRuntime.ts` 与 private `EncodedFieldGuards.ts`、其 directed test。
- Kotlin/Java:`WeatherRuntimeBridgeModule.kt`、`WeatherRuntime.java` 与 JDK-only V4 guard/helper test。
- JNI:`native_map_view.cpp`;Core only 增加 transport-size header 和 Linux unit test。
- CMake/test registration、本 Step、本报告、`粒子开发记录.md`。
## 验证
- [x] 在 `particle-codex` tmux session,经全局 `build-output-throttle`:package `tsc --noEmit` PASS、TS UTF-8 directed test PASS、TurboModule schema combine/codegen PASS。
- [x] JDK-only Java guard test PASS:64 MiB accepted,0 与 64 MiB+1 rejected,33 CJK 字符 rejected。
- [x] `mbgl-test-runner` 实际 rebuild PASS;`ParticleFieldProfileContractJson.*`、`Source.RasterArrayV4HostApi*`、`ParticleFieldBinding.*`、`ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`、`ParticleEncodedFieldTransport.*`、`ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`、`Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` 合并过滤 PASS。
- [x] Java declaration ↔ C++ JNI registration V4 name-set 静态核对、Kotlin/Java UTF-8 guard 静态核对、`git diff --check`:PASS。
## 未完成、阻塞与下一步
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、ADB、设备验证按任务约束未执行;需要后续一次性 Android 验收。iOS/Metal 与 Jest 同样未执行。
- 无实现阻塞。本 Step 在 scoped commit/push 后立即停止,不进入 STEP-079。
## Git 收尾
- 实现 commit:`75a974e77503c56ec5bd2823ac1b689225ed80a7`(`fix: close STEP-078 V4 bridge guard audit`)。
- push:PASS,`73779b89b..75a974e77 particle/main -> particle/main`;remote branch 为 `origin/particle/main`。
- 本记录及完成报告的 Git 收尾补充将以独立、仅文档的 STEP-078 scoped commit 推送;其 Final HEAD 记录于该次文档收尾的提交输出,之后立即停止。

View File

@@ -0,0 +1,57 @@
# STEP-078 — QBIN2 V4 ColorBinding VELOCITY_COMPONENT 接口修订 v4
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD:`dd1d7014710819fe55857968558fcc9538f28d9c`。
- 开始 `origin/particle/main`:`dd1d7014710819fe55857968558fcc9538f28d9c`。
- 仅解除 STEP-079 已确认的 ColorBinding public API blocker:让 `FieldRuntime.setEncodedColorBinding(...)` 能表达既有 Core `VELOCITY_COMPONENT`;不进入 STEP-079。
- 任务开始时已有 STEP-062 两处 dirty、历史 untracked 与 build 日志;均未清理、改写、暂存或提交。
## 完成项
- [x] `EncodedColorBindingInput.expectedValueClass` 新增 `VELOCITY_COMPONENT`,TS public fail-fast validation 同步允许该值。
- [x] Kotlin `WeatherRuntimeBridgeModule` 将 `SCALAR` / `VELOCITY_COMPONENT` / `DIRECTION_ANGLE` 精确映射为 Core value class `0` / `1` / `2`。
- [x] JNI `setRasterArraySourceEncodedColorBinding(...)` 仅允许 `0..2`;`VALIDITY=3` 与其它非法值继续 fail-closed。
- [x] Core 回归证明以真实 `VelocityComponent` U-east plane 的 ColorBinding 可 resolve,并证明 `Validity` ColorBinding 返回 `InvalidBinding`。
- [x] TurboModule primitive string ABI 与 Java `WeatherRuntime.setEncodedColorBinding(...)` 的参数数量、顺序及 primitive 形状均未修改。
## 修改范围
- `packages/maplibre-particle-rn/src/types.ts`
- `packages/maplibre-particle-rn/src/FieldRuntime.ts`
- `packages/maplibre-particle-rn/android/src/main/java/org/navsea/maplibre/particle/weather/WeatherRuntimeBridgeModule.kt`
- `platform/android/MapLibreAndroid/src/cpp/native_map_view.cpp`
- `test/particle/field_binding.test.cpp`
- 本记录、完成报告与 `粒子开发记录.md`。
未修改 Renderer/GPU/shader、QBIN container/decoder、Store、ForecastClock、Projection identity、ParticleBinding、Test Host V4 或 NavSea。
## 验证
所有 noisy command 均在 `particle-codex` tmux session 内经 `build-output-throttle` 执行,日志位于 `/tmp/step078v4-build-logs`:
- [x] package `tsc --noEmit`。
- [x] TurboModule schema combine/codegen。
- [x] Linux OpenGL `mbgl-test-runner` rebuild。
- [x] `ParticleFieldBinding.*`。
- [x] `Source.RasterArrayV4HostApi*`。
- [x] `ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`。
- [x] `ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`。
- [x] `Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*`。
- [x] Java `nativeSetRasterArraySourceEncodedColorBinding` 6-parameter declaration、C++ method signature 和 registration 静态一致性核对。
- [x] task-scope `git diff --check`。
## 未完成、阻塞与下一步
- Android Kotlin/Java/JNI 真实编译、Gradle、APK、install、ADB、设备/模拟器验证均按任务约束未执行;Windows 11 Android Test Host 编译仍待用户手工验收。Jest、iOS/Metal 亦未执行。
- 无实现阻塞。完成 scoped Git 收尾后立即停止;STEP-079 由 ChatGPT 独立复验本修订后重启。
## Git 收尾
- implementation commit:`e02353820aafa1834ce3a2ab3afe9261fadd83bc`(`fix: expose V4 velocity component color binding`)。
- implementation push:PASS,`dd1d70147..e02353820 particle/main -> particle/main`;remote branch 为 `origin/particle/main`。
- implementation Final HEAD:`e02353820aafa1834ce3a2ab3afe9261fadd83bc`。本记录、完成报告和开发记录将以独立、仅文档 scoped commit 推送;完成后立即停止。

View File

@@ -0,0 +1,46 @@
# STEP-079 — 真实 QBIN Golden Fixture 接入 RN Test Host v1
状态:阻塞
日期:2026-09-13
## 目标
把 WeatherV2 冻结的 QBIN V2 Golden Fixture 通过 STEP-078 的 V4 public API 接入 `MapLibreParticleRNTestHost`;本 Step 不执行 Android Gradle、APK、安装或 ADB。
## 起始基线
- `HEAD`:`2ea8f03064b2379d5220ce67806e3543e9cba0a7`
- `origin/particle/main`:`2ea8f03064b2379d5220ce67806e3543e9cba0a7`
- 开始前存在 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、Test Host generated Android/Expo/modules 与 package tgz;均未清理、暂存或提交。
## 已完成的只读核对
- [x] 核对冻结来源 `/root/sourceserver/weatherV2/_artifacts/MapLibre_QBIN_V2_单元测试数据_v1/`、说明、原始 manifest、profile/semantic/unit registry。
- [x] 核对 8 个 raw `.qbin` 的目标(Wind/Wave/Current/Rain,各 T0/T1)及 descriptor:Wind/Current U/V 都是 `VELOCITY_COMPONENT`,semantic `1/2`、group `1`、unit `1`。
- [x] 核对 STEP-078 V4 TypeScript、TurboModule/Kotlin bridge 与 Core ColorBinding resolver。
- [x] 确认不存在可在不改变冻结 public API 语义的前提下为 Wind/Current 提交正确 ColorBinding 的路径。
## 阻塞
Wind/Current 的任务合同要求选择 U 或 V 做 ColorBinding;其正确 `expectedValueClass` 是 `VELOCITY_COMPONENT`。然而冻结的 RN public API 在 `FieldRuntime.setEncodedColorBinding` 只接受 `SCALAR` 或 `DIRECTION_ANGLE`,Kotlin bridge 同样只映射这两者。将 U/V 伪装为 `SCALAR` 虽能通过 JS/Kotlin 参数白名单,但 Core resolver 会以 `PlaneClassMismatch` 拒绝真实 velocity plane。
因此无法同时满足“所有 dataset 至少有 ColorBinding”“所有 binding 仅通过冻结 V4 public API”“不得改变 STEP-078 public API 语义”。按任务的接口阻塞条款,未创建 Test Host wrapper、未修改 package/Core/Renderer,也未保留半成品 fixture 复制。
完整证据与解阻条件见 `docs/报告/STEP-079_真实QBIN_GoldenFixture接入RNTestHost_接口阻塞报告_v1.md`。
## 构建与验证
- 未执行 TypeScript/codegen/C++ build 或测试:在实现开始前已得到不可绕过的 public-API 合同矛盾,继续执行不会验证一个可交付实现。
- 未执行 Android Gradle、APK、install、ADB、模拟器、实体机或 iOS/Metal 操作。
- `git diff --check` 与 staged `git diff --cached --check`:在 `particle-codex` tmux session 执行,PASS。
## Git 收尾
- 阻塞记录提交:`afcc74066f1395678621fa2b1a70d10bc722a623`,`docs: record STEP-079 V4 fixture API blocker`。
- 已推送至 `origin/particle/main`;该提交只含本 Step 的 Step 记录、接口阻塞报告和开发记录,不含 fixture、Test Host 或既有 dirty/untracked 文件。
## 允许/禁止范围结果
- 未修改 Renderer/GPU/shader、Core/QBIN/Binding/Host API、Android bridge、RN package public API、NavSea、网络、下载或 gzip。
- 未进入下一 Step;等待具备明确授权的 STEP-078 API 补充/修订后重新开始本 Step。

View File

@@ -0,0 +1,49 @@
# STEP-079 — 真实 QBIN Golden Fixture 接入 RN Test Host v2
状态:完成(IMPLEMENTED / WAITING FOR WINDOWS 11 ANDROID VALIDATION)
日期:2026-09-13
## 目标与基线
- 开始 HEAD / `origin/particle/main`:`a83ab9cc94a14426913f971786eae6b13a6f1702`。
- 将 WeatherV2 冻结来源的 Wind、Wave、Current、Rain QBIN V2 原始 T0/T1 fixture 接入现有 RN Test Host 的 V4 public API;保留全部 V3 测试区。
- 开始前的 STEP-062 dirty、历史 untracked、`.codex-build-logs/`、Test Host generated `android/.expo/modules`、tgz 和旧任务书均未清理、暂存或提交。
## 完成项
- [x] 从 `/root/sourceserver/weatherV2/_artifacts/MapLibre_QBIN_V2_单元测试数据_v1/` 逐字节复制 8 个 raw `.qbin` 到 Test Host 自有 `assets/qbin-golden/`;运行时不读取该来源、不使用 HTTP/CDN/NAS/download/gzip。
- [x] 新增 V4 fixture contract:稳定 `fieldKey`、`fieldID`、generation/dataVersion、真实 validTime/midpoint、profile contract、source 相对路径、bytes 与 SHA-256。
- [x] Wind/Current 使用真实 U ColorBinding:semantic 1 / group 1 / `VELOCITY_COMPONENT` / unit 1,并使用 U/V `VECTOR_COMPONENTS` ParticleBinding;未伪装为 `SCALAR`。
- [x] Wave 使用 height ColorBinding(20/0/`SCALAR`/2)及 direction ParticleBinding(10/0/unit 4);Rain 使用 precipitation ColorBinding(30/0/`SCALAR`/5),Particle 明确 N/A。
- [x] 沿用 `expo-asset` 将 bundled asset materialize 为 fail-closed 的本地 `file://` URI,再调用 `FieldRuntime.submitEncodedField(...)`;Metro 仅追加 `qbin` asset extension。
- [x] 保留 V3 区域;新增 V4 dataset 选择、load、分开的 T0/T1 submit 结果、binding 结果、T0/midpoint/T1 seek、PLAY/PAUSE、正数 speed、clear selected/clear all 控制。load 固定顺序为 clear → binding → T0 → T1 → midpoint,T0/T1 非 `ACCEPTED_INSERTED` 会显示失败;midpoint 仅显示 command accepted,不宣称观测到 mix=0.5。
- [x] 新增开发期 fail-closed fixture verifier,逐个同时核验冻结 source 与 bundled copy 的 raw bytes/SHA-256。
## 修改范围
- 仅修改 `integration/react-native/MapLibreParticleRNTestHost/` 下的 V4 fixture、assets、materialize/UI、Metro、README 与 verifier,以及本 Step、完成报告、`粒子开发记录.md`。
- 未修改 Core、QBIN decoder、JNI、Android bridge、RN package public API、Renderer/GPU/shader、NavSea 或网络层。
## 验证
以下均在 `particle-codex` tmux session 内执行;所有 build/typecheck/codegen/test 经 `build-output-throttle`,日志位于 `/tmp/step079-build-logs`:
- [x] Test Host `npm --prefix integration/react-native/MapLibreParticleRNTestHost run typecheck`:PASS。
- [x] package `npx tsc --noEmit -p packages/maplibre-particle-rn/tsconfig.json`:PASS。
- [x] TurboModule schema combine 与 Android modules codegen:PASS。
- [x] `scripts/verify-qbin-golden-fixtures.py`:PASS(8 个 raw source/copy 文件 bytes + SHA-256)。
- [x] Linux OpenGL `cmake --build build-linux-opengl --target mbgl-test-runner -j3`:PASS(当前无 Core 变更,target 已最新)。
- [x] `ParticleFieldProfileContractJson.*`、`ParticleEncodedFieldTransport.*`、`Source.RasterArrayV4HostApi*`、`ParticleFieldBinding.*`、`ParticleQbinV2Decoder.*:ParticleEncodedFieldIngress.*`、`ParticleFieldRuntime.*:ParticleFieldProfileValidator.*`、`Source.ParticleFieldDataAdapter*:Source.RasterArrayTimeline*:Source.HostField*` 合并过滤:PASS。
- [x] `git diff --check` 与 task-scope cached `git diff --check`:PASS。
## 未完成、无法验证与下一步
- Android Gradle/APK/install/ADB/模拟器/真机均未在 Linux 执行;Jest、iOS/Metal 也未执行。
- 无法在 Linux 证明 Android Test Host 实际读取 Expo bundled `.qbin`、Kotlin/JNI/Renderer consumer 接受 fixture,或观察 Android 画面/播放效果;这些必须由 Windows 11 用户手工验收。
- 无 implementation blocker。下一步仅为 Windows 11 Android 手工验收;本 Step 不进入 Renderer consumer、NavSea 正式接入或下一 Step。
## Git 收尾
- implementation commit:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`,`feat: add V4 QBIN golden fixture test host`。
- implementation Final HEAD:`f1ea88b0ce9c6995f05a8d2ae2eb2696d72e5f28`。本 Step/报告/开发记录使用独立且仅文档的 scoped commit 收尾并 push 到 `origin/particle/main`;该文档收尾后立即停止。

View File

@@ -0,0 +1,69 @@
# STEP-080 — Android AAR 与 RN Test Host 标准构建流水线 v1
> **历史版本。** 当前正常公网发布/Win11 验证流程以同目录 `STEP-080_Android_AAR与RNTestHost标准构建流水线_v2.md` 为准;v1 的本地 bundle/MavenLocal 流程仅作历史/离线参考。
状态:完成
日期:2026-09-13
## 目标与基线
- 开始 HEAD / `origin/particle/main`:`d490578b973508d267c590dd604b033507c9b859`。
- 固定交付版本:RN package / Native AAR 均为 `13.4.1-particle.3`,Maven coordinate 为 `cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3`。
- 任务开始前已有 Native `VERSION` 升级、JNI 头文件修正、STEP-062 文档 dirty、历史 untracked task/build/cache/tgz 和 Test Host generated Android/Expo/modules;均未清理、暂存或提交。
## 必做项
- [x] 新增 Linux producer `tools/particle_android_pipeline/linux_build_publish.sh`:报告 HEAD/branch/dirty;版本完全相等校验;SDK 解析;OpenGL Release 四 ABI 完整构建与 Maven Local publish;Maven artifact、旧 `.2` 保护、AAR/tgz bytes/SHA-256 校验;版本隔离 bundle/manifest。
- [x] Linux 所有 noisy native build、Gradle publish、RN TypeScript typecheck 与 npm pack 均强制经 build-output-throttle;runner 不可达即 fail-closed。
- [x] 默认 Linux 流程会生成可传输 AAR/POM/module/tgz 与 `artifacts/maplibre-particle/<version>/build-manifest.json`;目录已有 manifest 或不同 partial file 时 fail-closed,不覆盖历史 handoff。
- [x] 明确提供 `--reuse-existing`:仅能消费已完整存在的 Maven Local 发布物并在 manifest 标识 `PASS_REUSED_EXISTING_MAVEN_LOCAL`;不会冒充重新 AAR 构建。`--preflight` 无写入。
- [x] 新增 Windows consumer `tools/particle_android_pipeline/windows_build_testhost.ps1`:manifest 是唯一 artifact/version 输入,校验 SHA、安装 AAR/POM/module、同步 clean Test Host tracked source、固定 tgz 与 Maven coordinate、显式 prebuild/Debug APK build、输出 APK bytes/SHA-256。
- [x] Windows 默认不做安装、启动或 ADB;只有 `-Install` 与 `-Install -Run` 才使用固定设备 `192.168.1.146`。支持 `-DryRun`,先打印版本、AAR、tgz、Host、Maven Local 与 Gradle command。
- [x] 新增中文 README;版本升级必须先同步 `platform/android/VERSION` 与 RN `package.json`,pipeline 只校验,绝不暗改版本。
- [x] `.gitignore` 忽略 `/artifacts/maplibre-particle/`,生成 AAR/tgz/manifest 不进入 scoped commit。
## 允许修改
- `tools/particle_android_pipeline/` 的 Linux/Windows 脚本及说明。
- `.gitignore`、本 Step、完成报告与 `粒子开发记录.md`。
- 运行时写入 Git ignore 的 `local.properties`、`.codex-build-logs/`、`artifacts/`。
## 禁止修改
- Core、Renderer、QBIN、Binding、JNI 业务语义。
- Native/RN 版本值、既有 dirty/untracked、历史 Maven Local `.2` 发布物。
- Windows APK/ADB/设备、Jest、iOS/Metal;本 Step 不重复完整 Linux AAR 编译。
## 构建与验证
以下 Linux 验证均在 `particle-codex` tmux session 内完成:
- [x] `bash -n tools/particle_android_pipeline/linux_build_publish.sh`:PASS。
- [x] `linux_build_publish.sh --preflight`:PASS;SDK 解析为 `/root/Android/Sdk`,无 build/package/artifact 写入。
- [x] `linux_build_publish.sh --reuse-existing`:PASS;不运行完整 AAR build/publish,验证现存 `.3` Maven Local 的 AAR/POM/module,保护 `.2` 字节不变;经限流完成 RN package typecheck 与 npm pack。
- [x] manifest JSON contract:AAR/POM/module/tgz 的存在、绝对路径、bytes、SHA-256、版本和 Maven coordinate 全部 PASS。
- [x] tarball `package/package.json` version 与 Native `13.4.1-particle.3` 一致:PASS。
- [x] `git diff --check -- .gitignore`:PASS。
- [ ] Windows PowerShell parser/static validation:本 Linux 环境无 `pwsh`,明确 `POWERSHELL_PARSE_UNAVAILABLE_NO_PWSH`;未伪称 PASS。
## 当前结果
本轮 bundle 已在 Git ignore 路径生成:
```text
artifacts/maplibre-particle/13.4.1-particle.3/build-manifest.json
```
manifest 记录构建 HEAD、分支、dirty 状态、版本、四 ABI、OpenGL、Maven coordinate、AAR/POM/module/tgz 的 path/bytes/SHA-256 和 Linux validation result。验证使用的 existing AAR SHA-256 为 `9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd`,tgz SHA-256 为 `6c8af6ff19adb4e25785b54bea3edd1dc7d0502d9158cf1c519d84b69d08ee55`。
## 未完成与阻塞
- 未重复执行完整四 ABI AAR 构建与 Maven Local publish:任务明确禁止为验证重复完整 AAR 编译;默认脚本已固化该受限命令,验证仅复用现存完整 `.3` publication。
- Linux 无 `pwsh`,因此未能本机解析 Windows PowerShell。Windows `-DryRun`、npm/prebuild/Gradle APK、安装、启动和设备验收均未执行,须在 Windows 11 使用该 manifest 完成;这不是 Linux 流水线实现阻塞。
- Jest、Windows APK/ADB/设备、iOS/Metal 均未执行。
## 下一步
Windows 11 将完整 Linux bundle 复制到本机后,先运行 `windows_build_testhost.ps1 -ManifestPath <build-manifest.json> -DryRun`,核对路径与 SHA;再无设备开关地运行同一脚本完成 Debug APK。只有用户明确选择时才附加 `-Install` 或 `-Run`。

View File

@@ -0,0 +1,535 @@
# STEP-080 — Android AAR 与 RN Test Host 标准构建流水线 v2
状态:已按真实 Linux → pbf → Windows 11 → Android 模拟器链路验证通过
日期:2026-09-14
## 1. 目的
本文固定 MapLibreParticleLab Android Native AAR 完成后,到 Windows 11 构建 RN Test Host、启动 Metro、安装 App、执行 QBIN V4 验证的标准流程。
以后正常验证不得再临时复制 AAR、临时改 MavenLocal、在 Windows 重编 Native AAR,或凭经验猜 Metro 端口。
## 2. 最终权威链路
```text
Linux MapLibreParticleLab
→ 编译 Native OpenGL AAR
→ 发布到 /mnt/nas/pbf/maven
→ 公网 https://pbf.jpcn.ccwu.cc/maven
Linux RN package
→ npm pack
→ 发布到 /mnt/nas/pbf/npm
→ 公网 https://pbf.jpcn.ccwu.cc/npm
Windows 11 RN Test Host
→ npm install 公网 tgz
→ Gradle 从公网 Maven 自动取得 Native AAR
→ Expo prebuild
→ assembleDebug
→ Metro :8091
→ adb reverse 8081 → 8091
→ 安装并启动 App
→ QBIN V4 真机/模拟器验证
```
## 3. Linux 与 Windows 的职责边界
### Linux 负责
- MapLibre Native Android OpenGL AAR 编译。
- Native AAR 版本管理。
- RN package 打包。
- AAR/POM/module 与 RN tgz 发布到 pbf。
- 公网回读并校验 SHA-256。
### Windows 11 负责
- 从 pbf npm 安装 RN package。
- 通过 Gradle 从 pbf Maven 消费对应 Native AAR。
- Expo prebuild。
- Test Host Debug APK 编译。
- Metro、ADB reverse、安装、启动和人工验证。
### 禁止
- Windows 正常流程中重新编译 MapLibre Native AAR。
- Windows 正常流程中使用 Maven Local 替代 pbf Maven。
- Linux 与 Windows 之间通过局域网临时复制 AAR 作为正式验证路径。
- 覆盖旧 Maven 版本坐标。
## 4. 版本一致性硬门槛
以当前已验证版本为例:
```text
RN package = 13.4.1-particle.3
Native AAR = 13.4.1-particle.3
```
以下位置必须全部一致,否则停止构建:
1. `platform/android/VERSION`
2. `packages/maplibre-particle-rn/package.json`
3. `packages/maplibre-particle-rn/android/build.gradle`
4. `packages/maplibre-particle-rn/app.plugin.js`
5. `integration/react-native/MapLibreParticleRNTestHost/plugins/withParticleNativeAndroid.js`
## 5. Linux:AAR 构建与发布
推荐使用仓库内标准脚本:
```bash
cd /mnt/sda1/MapLibreParticleLab
tools/particle_android_pipeline/linux_build_publish.sh
```
如果只是检查版本、SDK、路径和发布条件:
```bash
tools/particle_android_pipeline/linux_build_publish.sh --preflight
```
Linux Android SDK 当前实际路径:
```text
/root/Android/Sdk
```
AAR 必须使用新的版本目录发布,不能覆盖历史 `.2`:
```text
/mnt/nas/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/<version>/
```
当前 `.3` 目录为:
```text
/mnt/nas/pbf/maven/cc/navsea/maplibre/android-sdk-particle-opengl/13.4.1-particle.3/
```
至少包含:
```text
android-sdk-particle-opengl-13.4.1-particle.3.aar
android-sdk-particle-opengl-13.4.1-particle.3.pom
android-sdk-particle-opengl-13.4.1-particle.3.module
```
RN package 使用 `npm pack` 生成,并发布到:
```text
/mnt/nas/pbf/npm/navsea-maplibre-particle-rn-<version>.tgz
```
## 6. Linux:把 AAR 正式发布到公网 pbf
`https://pbf.jpcn.ccwu.cc/` 的服务器文件根目录固定映射为:
```text
/mnt/nas/pbf
```
因此 Maven 公网根:
```text
https://pbf.jpcn.ccwu.cc/maven/
```
对应 Linux:
```text
/mnt/nas/pbf/maven/
```
npm/tgz 公网根:
```text
https://pbf.jpcn.ccwu.cc/npm/
```
对应 Linux:
```text
/mnt/nas/pbf/npm/
```
发布不需要 SSH 到其他服务器,也不需要从 Win11 上传;Linux 直接写 `/mnt/nas/pbf` 即完成公网发布。
### 6.1 发布 Native AAR
先定义版本:
```bash
VERSION=13.4.1-particle.3
GROUP_PATH=cc/navsea/maplibre/android-sdk-particle-opengl
SRC="$HOME/.m2/repository/$GROUP_PATH/$VERSION"
DST="/mnt/nas/pbf/maven/$GROUP_PATH/$VERSION"
```
正常发布必须使用一个从未发布过的新版本。先保护旧版本:
```bash
test ! -e "$DST" || {
echo "目标版本已经存在,禁止覆盖:$DST" >&2
exit 1
}
```
确认本地 Maven publication 完整:
```bash
ls -lh "$SRC"
sha256sum "$SRC/android-sdk-particle-opengl-$VERSION.aar"
```
创建公网 Maven 版本目录并复制:
```bash
mkdir -p "$DST"
cp -a "$SRC"/. "$DST"/
```
至少确认:
```bash
test -f "$DST/android-sdk-particle-opengl-$VERSION.aar"
test -f "$DST/android-sdk-particle-opengl-$VERSION.pom"
test -f "$DST/android-sdk-particle-opengl-$VERSION.module"
```
### 6.2 公网回读验证 AAR
公网 URL 规则:
```text
https://pbf.jpcn.ccwu.cc/maven/cc/navsea/maplibre/android-sdk-particle-opengl/<VERSION>/android-sdk-particle-opengl-<VERSION>.aar
```
发布后必须从公网重新下载,不允许只检查 `/mnt/nas/pbf` 本地文件:
```bash
LOCAL_AAR="$DST/android-sdk-particle-opengl-$VERSION.aar"
PUBLIC_AAR="https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.aar"
TMP_AAR="$(mktemp)"
curl -fL "${PUBLIC_AAR}?nocache=$(date +%s)" -o "$TMP_AAR"
sha256sum "$LOCAL_AAR" "$TMP_AAR"
cmp -s "$LOCAL_AAR" "$TMP_AAR"
```
`cmp` 必须返回 0。只有公网回读字节完全一致,才算 AAR 发布完成。
同时可验证 POM/module HTTP 200:
```bash
curl -fI "https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.pom"
curl -fI "https://pbf.jpcn.ccwu.cc/maven/$GROUP_PATH/$VERSION/android-sdk-particle-opengl-$VERSION.module"
```
### 6.3 发布 RN package 到公网 npm 目录
在仓库中生成 tgz:
```bash
cd /mnt/sda1/MapLibreParticleLab/packages/maplibre-particle-rn
npm pack
```
生成文件示例:
```text
navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
```
正式发布路径:
```bash
TGZ="navsea-maplibre-particle-rn-$VERSION.tgz"
PUBLIC_TGZ_DIR="/mnt/nas/pbf/npm"
test ! -e "$PUBLIC_TGZ_DIR/$TGZ" || {
echo "公网同版本 tgz 已存在,禁止覆盖;请升级版本" >&2
exit 1
}
cp "$TGZ" "$PUBLIC_TGZ_DIR/$TGZ"
```
公网 URL:
```text
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-<VERSION>.tgz
```
发布后必须公网回读并比较 SHA:
```bash
TMP_TGZ="$(mktemp)"
curl -fL "https://pbf.jpcn.ccwu.cc/npm/$TGZ?nocache=$(date +%s)" -o "$TMP_TGZ"
sha256sum "$TGZ" "$TMP_TGZ"
cmp -s "$TGZ" "$TMP_TGZ"
```
正常发布不得重新覆盖同一版本的 tgz。今天 `.3` 的覆盖属于修复历史错误包的例外,不应成为日常流程。
## 7. Windows 11:从公网取得 RN package 与 AAR
Windows 正常流程不直接下载 AAR 文件。AAR 由 Gradle 根据 RN package 中的 Maven coordinate 自动从 pbf Maven 获取。
当前 Test Host 建议工作目录:
```text
D:\React\MapLibreParticleRNTestHost-V4
```
不要在 `V:\` 盘符根目录执行 Expo prebuild;本次实测 Expo autolinking 在盘符根目录会出现 `Couldn't find package.json up from path V:\`。
### 7.1 安装 RN package
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npm install --force `
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
```
正常情况下新版本不需要清 npm cache;只有确认同版本曾被错误覆盖时才做:
```powershell
npm cache clean --force
```
安装后必须核对实际依赖:
```powershell
Get-Content node_modules\@navsea\maplibre-particle-rn\package.json
Select-String `
-Path node_modules\@navsea\maplibre-particle-rn\android\build.gradle `
-Pattern 'android-sdk-particle-opengl'
```
必须看到目标版本,例如:
```text
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3
```
### 7.2 Expo prebuild 与 Android SDK
执行:
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npx expo prebuild --platform android
```
Windows Android SDK 当前实际路径:
```text
C:\Users\taibe\AppData\Local\Android\Sdk
```
如果 Gradle 报 `SDK location not found`,优先在当前 PowerShell 显式设置:
```powershell
$env:ANDROID_HOME='C:\Users\taibe\AppData\Local\Android\Sdk'
$env:ANDROID_SDK_ROOT='C:\Users\taibe\AppData\Local\Android\Sdk'
```
并写入:
```text
D:\React\MapLibreParticleRNTestHost-V4\android\local.properties
```
内容:
```properties
sdk.dir=C\:\\Users\\taibe\\AppData\\Local\\Android\\Sdk
```
### 7.3 构建 Debug APK
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4\android
$env:ANDROID_HOME='C:\Users\taibe\AppData\Local\Android\Sdk'
$env:ANDROID_SDK_ROOT=$env:ANDROID_HOME
.\gradlew.bat :app:assembleDebug
```
成功产物:
```text
D:\React\MapLibreParticleRNTestHost-V4\android\app\build\outputs\apk\debug\app-debug.apk
```
## 8. Metro、ADB reverse、安装与启动
Test Host 的固定规则是:
```text
Metro 实际监听端口:8091
App 默认访问端口:8081
ADB reverse:tcp:8081 → tcp:8091
```
### 8.1 启动 Metro
```powershell
cd D:\React\MapLibreParticleRNTestHost-V4
npx expo start --port 8091 --clear
```
### 8.2 配置 reverse
另开一个 PowerShell:
```powershell
adb -s 192.168.1.146 reverse --remove-all
adb -s 192.168.1.146 reverse tcp:8081 tcp:8091
adb -s 192.168.1.146 reverse --list
```
必须看到:
```text
tcp:8081 tcp:8091
```
### 8.3 安装并启动
```powershell
adb -s 192.168.1.146 install -r `
D:\React\MapLibreParticleRNTestHost-V4\android\app\build\outputs\apk\debug\app-debug.apk
adb -s 192.168.1.146 shell am force-stop org.maplibre.particle.rntesthost
adb -s 192.168.1.146 shell am start `
-n org.maplibre.particle.rntesthost/.MainActivity
```
## 9. V3 fixture 与 Metro watchFolders
当前 Test Host 同时保留旧 V3 数据集和 V4 QBIN 数据集。
独立 Windows Host 使用:
```text
D:\React\MapLibreParticleRNTestHost-V4
D:\React\particle-v3-fields
```
`src/fieldDatasets.ts` 使用 `../../particle-v3-fields/...`,因此这些 fixture 位于 Metro 项目根之外。
此时 `metro.config.js` 必须把外部 fixture 目录加入 `watchFolders`,例如独立 Windows Host:
```js
const path = require("path");
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
config.resolver.assetExts = [...new Set([...config.resolver.assetExts, "f32", "u8", "qbin"])];
config.watchFolders = [
path.resolve(__dirname, "..", "particle-v3-fields"),
];
module.exports = config;
```
修改 `metro.config.js` 后必须重启 Metro,普通 Reload 不足以保证配置重载。
如果看到:
```text
Unable to resolve ../../particle-v3-fields/...
```
先确认文件实际存在,再检查 `watchFolders`;不要重复复制同一批 fixture 或误判为 QBIN 问题。
## 10. QBIN V4 验收确认
V4 测试区必须走:
```text
assets/qbin-golden/*.qbin
→ FieldRuntime.submitEncodedField(...)
→ WeatherRuntimeBridgeModule.submitEncodedField(...)
→ WeatherRuntime.submitEncodedField(...)
→ JNI native_map_view.cpp
→ RasterArraySource::submitEncodedField(...)
→ QbinV2Decoder::decode(...)
```
这与旧 V3 `metadata.json + .f32 + .u8 → submitField(...)` 是两条不同链路。
至少验证:
- V4 Wind:T0/T1 load、Color Binding、Particle Binding、T0/midpoint/T1 seek。
- V4 Wave:同上。
- V4 Current:同上。
- V4 Rain:Color Binding、T0/midpoint/T1;Particle Binding 为 N/A。
- PLAY / PAUSE / playback speed。
- `clearEncodedField` / `clearAllEncodedFields`。
人工确认 V4 功能正常后,才可判定 Windows Android QBIN V4 验证通过。
## 11. 本次 `.3` 已验证发布基线
Native AAR:
```text
cc.navsea.maplibre:android-sdk-particle-opengl:13.4.1-particle.3
SHA-256: 9ef03eb0d0c03457adc04847cfa69fb67d85191daecd64d3b76db2ad8cc688cd
```
最终修正后的 RN tgz:
```text
https://pbf.jpcn.ccwu.cc/npm/navsea-maplibre-particle-rn-13.4.1-particle.3.tgz
SHA-256: abdd5b5f313b6dc2bc2f2b00848d1901be72fed6a6e1fa15899efb10df9f923f
```
Windows 11 Debug APK 已于 2026-09-14 成功构建、安装并由用户完成功能验证。
## 12. 本次实际踩过的弯路与固定处理
### A. Win11 不要重编 Native AAR
症状:为了验证 Test Host,又在 Windows 尝试重建 MapLibre Native。
固定规则:Native AAR 只在 Linux 生产;Windows 只通过 pbf Maven 消费。
### B. 不要用局域网复制 AAR 作为正常路径
固定规则:Linux 发布到 `/mnt/nas/pbf/maven`,Windows Gradle 从 `https://pbf.jpcn.ccwu.cc/maven` 获取。
### C. RN package 版本号相同但内部仍依赖旧 AAR
必须检查 `android/build.gradle`、`app.plugin.js`、Test Host plugin 的 Maven coordinate,不能只看 `package.json` 版本号。
### D. Android Kotlin 必须实编译
Linux TypeScript/Core 检查无法替代 Android Kotlin 编译。本次实际发现 `value >= if (...)` 在 Kotlin 编译器下失败,最终改为显式括号与 `Double` 常量。
### E. Expo 不要在 `V:\` 根目录 prebuild
本次实际出现 `Couldn't find package.json up from path V:\`。固定使用普通子目录,例如 `D:\React\MapLibreParticleRNTestHost-V4`。
### F. App 报 localhost:8081,而 Metro 在 8091
这是正常设计,不要把 Metro 改回 8081。正确映射是:
```text
adb reverse tcp:8081 tcp:8091
```
### G. fixture 文件存在但 Metro 仍 Unable to resolve
优先检查 `watchFolders`,不要重复复制 fixture。仓库 Test Host 的 `metro.config.js` 已加入仓库布局/独立 Windows 布局自动探测。
## 13. 文档优先级
本文件 `v2` 覆盖 STEP-080 v1 中“Linux bundle → Windows MavenLocal”的正常发布结论。
`tools/particle_android_pipeline/` 中旧脚本保留用于历史/离线用途;正常联网验证以本文的 pbf 公网链路为准。

View File

@@ -0,0 +1,64 @@
# STEP-081 iOS Metal Renderer 与真机 Smoke 入口 v1
状态:CLOSED / PASS
## 目标
为既有 C++ RasterArray/QBIN V4 → WeatherColor + RasterParticle path 增加 Metal shader registry 接线,并在 iOS Test App 中提供 Wind Golden QBIN 的真机 smoke 入口。
## 本轮结果
- [x] 新增 Metal `ParticleGpuValidationShader` source、属性/纹理表和 registry 注册。
- [x] 保持 `ParticleGpuValidationUBO` 为唯一 shared UBO,MSL 静态契约为 `31 * 16`。
- [x] 在 Test App Runtime Styling 菜单加入 T0/midpoint/T1/clear private smoke seam。
- [x] CMake 将既有 Wind Golden T0/T1 复制到 app bundle。
- [x] 仅做静态检查;未执行 Jest。
- [x] iPhoneOS arm64 Test App 无签名编译通过,`BUILD SUCCEEDED`。
- [x] Pleiades(iPad mini 5 / iPadOS 26.5.2)完成签名安装、真实 Metal runtime 与视觉验收。
- [x] Deterministic Uniform East 向右、Uniform North 向上、Vortex 可见并具有连续轨迹。
- [x] QBIN T0 / MID / T1 / Clear / Clear 后重新 T0 均通过。
## 允许范围与限制
修改仅涉及 Metal thin-hook、iOS Test App、CMake Metal/app 资源接线以及本 Step 文档。未修改 QBIN V4/Decoder/Profile/Binding、Android/RN API、版本坐标或项目总则。现有 gfx 代码使用 `gfx::RenderTarget`、`Texture2D`、`Lines` 与 shader registry;静态审查未见 layer 内的直接 OpenGL 调用。
## 真机验收入口
在 iOS Test App 中,`Particle Core Validation (Fixtures)` 用 Uniform East / Uniform North / Vortex 验证正式 Host ingress → RasterParticleLayer → Metal 路径;`Particle / QBIN V4 Smoke (T0)` 用 T0 / midpoint / T1 / Clear 验证真实 Wind Golden QBIN。QBIN T0/MID/T1 对应 1789239600000 / 1789241400000 / 1789243200000。
## 构建与阻塞
`Codex` 未执行任何编译;GPT 审查后由 RDC 完成 CMake/Xcode iPhoneOS arm64 build。最终签名 device build `BUILD SUCCEEDED`,Bundle ID `jp.eyaku.maplibreparticlelab.step081`,Wind T0/T1 QBIN 均进入 `ParticleSmokeFixtures/`。随后在 Pleiades 真机完成安装、启动、Metal shader runtime 与视觉 smoke。
## GPT / RDC 独立审查(2026-09-17)
Codex 结束后由 RDC 读取完整 diff、设计文档和完成报告,GPT 独立审查通过;编译仍未执行。审查中做了 3 个小范围、事实明确的修正:
- State A/B RenderTarget 明确为 `TextureChannelDataType::Float`,Metal fragment 输出由 `half4` 改为 `float4`,避免 Web Mercator 粒子状态在 shader 输出阶段降为半精度。
- Metal `resolveAdvection()` 的 VELOCITY 零向量行为与 OpenGL fragment fail-closed 对齐:长度 `<= 0.00001` 返回 false。
- Smoke 选择器由 iPad 需要 popover anchor 的 ActionSheet 改为 Alert,保证 iPhone/iPad 真机都可直接进入。
`git diff --check` 通过;未发现 QBIN V4/Core contract、Android/RN API 或版本坐标越界修改。下一步允许 RDC 执行 iPhoneOS device build;仍禁止 Simulator。
## RDC 编译验证(2026-09-17)
- `cmake --preset ios-metal` 配置成功,Xcode 26.3 / iPhoneOS 26.2 / AppleClang 17。
- Darwin style codegen 需要 Bazel;按仓库 `.bazelversion` 使用 Bazel 8.6.0 后通过。
- 首次签名构建被 `org.maplibre.app` provisioning 阻塞,因此改用 `CODE_SIGNING_ALLOWED=NO` 做 device-architecture 编译验证;未使用 Simulator。
- 编译中只对 Test App 做小范围修正:使用 `${CMAKE_SOURCE_DIR}/src` 暴露 private smoke 所需内部头;`weakSelf` 改为显式 `MBXViewController *`;fixture path 使用 `NSString *`。
- 最终 `app` target `BUILD SUCCEEDED`;`particle_gpu_validation.cpp` 和 `MBXViewController.mm` 均通过 arm64 编译。
- 当前登记 iPhone/iPad 均为 `unavailable`,因此状态进入 `WAITING_DEVICE_SMOKE`,不能声明真机/Metal runtime PASS。
## RDC 真机收口(2026-09-18)
- 设备:Pleiades,iPad mini (5th generation) / iPad11,2,iPadOS 26.5.2;使用 iPhoneOS device build,未使用 Simulator。
- 确认 Metal render-pipeline cache key 缺少 attachment pixel format:State RGBA32Float / Trail RGBA8Unorm / 主 renderable BGRA8Unorm 之间可能错误复用 pipeline。`src/mbgl/shaders/mtl/shader_program.cpp` 已把 color/depth/stencil format 合入 cache key。
- Trail target 强制写测试在修复前全零、修复后 RGBA8 有效写入,证明 cache-key 根因。
- State GPU 与 CPU/视觉验证确认 canonical U/V、T0/T1、State ping-pong、vertex-stage texture sampling 和 z0 matrix 投影正确。
- 实测正式 previous→current 线段平均约 0.065 px、最大约 0.24 px,全部 `<0.5 px`;Metal line primitive 无稳定覆盖。保留真实 line,同时增加 Metal-only 1 px current-endpoint quad coverage,不改变物理位移。
- Deterministic:Uniform East 向东/右,Uniform North 向北/上,Vortex 粒子可见、移动连续、有轨迹感。
- QBIN Golden fixture:grid 513×769,bounds=112.5E..157.5E / 0..55.776573N;Smoke 相机定位约 27.8883N / 135E、zoom 3 后,T0 / MID / T1 / Clear / Clear 后重进全部人工确认正常。
- 中间 `STEP081_*` GPU readback / fixed-line / point fallback 诊断均已删除;错误的 Point coverage 曾导致整屏绿色,最终未保留。
- `git diff --check` 在最终清理后通过;无关 `MBXAppDelegate.m` 与 vendor/submodule dirty 不纳入 STEP-081 commit。
- 功能与真机验收实现已提交并成功推送:`f1c0f92b2b`(`fix: close STEP-081 iOS Metal particle rendering`),`origin/particle/main` 已同步。

View File

@@ -0,0 +1,40 @@
# STEP-082 iOS Native FieldRuntime Facade 与 XCFramework 发行基线 v1
状态:CLOSED / PASS
## 目标与基线
起始分支 `particle/main`,起始 HEAD `3e03949d3411c830cfff4bf320cd350f5b6a7527`。本 Step 只新增 iOS public V4 facade 和 XCFramework distribution 基线;不重做 STEP-081,也不进入 STEP-083/RN。
## 执行清单
- [x] iOS public Native FieldRuntime facade。
- [x] MapView-owned replace/current/dispose 生命周期。
- [x] V4 submit、binding、forecast、clear 转发。
- [x] style reload/source missing fail-closed 与 public header 无 Core/STL 泄漏。
- [x] XCFramework public header 构建图接线。
- [x] distribution-ready 模板和中文设计文档。
- [x] macOS XCFramework 成功构建、Headers/symbol/zip SHA-256 验证。
- [x] `git diff --check` 已通过;本次文档收尾后的 scoped closeout commit/push 由宿主执行并回填最终 hash。
## 允许与禁止
允许修改 iOS public header/implementation、Bazel public export、release 模板以及指定中文文档。禁止改 QBIN V2、FieldProfile、BindingProjection、renderer/Metal physics、STEP-081 smoke、Android/RN/NavSea;本 Step 未修改这些范围。
## 最终构建记录与关闭依据
宿主已在真实实现提交 `136ed726cf749c24b3673c046581a38ef48be390`(`fix: close ios xcframework packaging blockers`)上完成阻塞修复和 macOS 验证。`bazel build //:generated_style_code` PASS;`bazel build //platform/ios:MapLibre.dynamic` PASS,最终日志 `/tmp/step082-host-maplibre-dynamic-final2.log` 含 `Build completed successfully`。
产物为 `bazel-bin/platform/ios/MapLibre.dynamic.xcframework.zip`,大小 `24639200` bytes,SHA-256 为 `64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`。`ios-arm64` 与 `ios-arm64_x86_64-simulator` slice 均导出 `Headers/MLNParticleFieldRuntime.h`,两份 `Mapbox.h` 均 import `MLNParticleFieldRuntime.h`;`MLNMapView_Private.h` 的 public export count 为 0。两 slice binary 均存在 `_OBJC_CLASS_$_MLNParticleFieldRuntime` 和 `_OBJC_CLASS_$_MLNParticleFieldRuntimeConfiguration`;device 架构为 arm64,simulator 架构为 x86_64+arm64。
本次仅完成工作树文档收尾;Git closeout commit/push 将由宿主在本文件和 v2 完成报告生成后执行,最终 hash 以宿主回填/最终回复为准。
## 下一步
STEP-082 已关闭。远端发布仍未执行,manifest 的 artifact URL 保持 `LOCAL_BUILD`;后续发布必须另行上传并以发布后的 URL/校验结果更新 release 元数据。STEP-083 不因本记录自动开始。
## STEP-083 post-close correction(2026-09-18)
本段追加纠正,不改写上方当时的历史记录。后续真实 RN device 验证确认,上方使用裸 `bazel build //platform/ios:MapLibre.dynamic` 取得的默认 OpenGL artifact(`24639200` bytes,SHA-256=`64faa174c5c0dc0a2da3e289f0beaf2dedf7298665e3345f39836729b668e239`,source commit `136ed726…`)只有色场、没有 Particle render,故作废且不再作为 iOS Particle 发行基线。
iOS Particle 发行固定改用 `bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic`。Metal XCFramework 为 `24943196` bytes,SHA-256=`ae88975fc20f4d9961b95079638d0e9c0d8e61d4394d5c0a8ffcc48d173a827f`,source commit=`bdd7f40f9029031816c9bf4db6e1cd6c7c69b7bd`。其 device binary 含 `MLNMapView`、Metal/`MTLDevice`/`ParticleGPU` 标记且无 OpenGL marker;Pleiades iPad mini 5 RN Test Host signed Release 安装后,用户已确认粒子恢复。artifact 仍为本地构建,URL 保持 `LOCAL_BUILD`。

View File

@@ -0,0 +1,38 @@
# STEP-083 iOS RN 统一 FieldRuntime / TurboModule / Fabric 与 XCFramework 消费 v1
状态:`CLOSED / PASS`
## 基线与范围
- branch:`particle/main`;开始 HEAD:`bdd7f40f9029031816c9bf4db6e1cd6c7c69b7bd`。
- STEP-082 public facade 与 `//platform/ios:MapLibre.dynamic` Bazel build 已由 RDC 真实验证 PASS。
- 本批次仅增加 RN package iOS bridge/pod/codegen/autolinking、Test Host iOS identity 和记录;没有修改 Android、Shared Core、QBIN contract、renderer、NavSea 或 frozen TS public API。
## 已实现
- `WeatherRuntimeBridge` 是 RN 0.85.2 codegen TurboModule:`NativeWeatherRuntimeBridgeSpecJSI`、Promise `resolve/reject` ABI 及 package `ios.modulesProvider` 均使用当前本地 RN 0.85.2 source 的真实 pattern。
- 每个 Map command 先校验正整数 `reactTag`,再通过 RN 注入的当前 Fabric/UIManager `viewRegistry_DEPRECATED` 进入 UI block,重新确认 `MLRNMapViewComponentView` → `contentView` → `MLRNMapView` → public `MLNMapView`。不缓存 tag、map、Core pointer 或建立第二 registry。
- V3、V4、runtime state、weather/particle visibility、timeline、binding 与所有视觉命令均映射到现有 public `MLNParticleFieldRuntime`。V3 `clearWindFields` 的 Android 语义本身等价 `clearFields`。
- V3/QBIN file ingress 均只接收 absolute path 或 `file://`,要求 readable regular file、1..64 MiB,并比较读前后 length;metadata、contract、UTF-8 identifier、safe integer 与 inactive `(0,0)` selector 同 Android guard。
- pod 只 dependency `MapLibreReactNative 11.3.6`,不声明 MapLibre pod/framework/SPM product。MLRN 11.3.6 的真实 `$MLRN_SPM_SPEC` 将唯一 `MapLibre` product 注入 Pods 和 app target。
## RDC 实际 iOS 验证(2026-09-18)
- RDC 已实际执行 npm dependency install(`npm ci`)、typecheck、RN codegen、Expo prebuild、Pod integration、device arm64 build、signed Release build 与安装。MapLibre.framework 签名成功。
- Test Host 消费的是 single MapLibre SPM product;未发现第二份 MapLibre binary。新 XCFramework 必须以 Metal 命令 `bazel build --//:renderer=metal //platform/ios:MapLibre.dynamic` 生成。默认裸 Bazel 命令的 OpenGL artifact 只有色场、无粒子,已由 STEP-082 post-close correction 作废。
- Pleiades iPad mini 5 上 signed Release 安装运行。一次 V4 Wind load 实际观察到 `clearAllEncodedFields` → color binding → particle binding → T0 `ACCEPTED_INSERTED` → T1 `ACCEPTED_INSERTED` → midpoint seek;用户确认粒子恢复。
## 已执行检查
- `ruby -c packages/maplibre-particle-rn/MapLibreParticleRN.podspec`:成功。
- `npm ci`、typecheck、RN codegen、Expo prebuild、Pod integration、device arm64 build、signed Release build/installation:均由 RDC 实际执行;Release 日志为 `BUILD SUCCEEDED`。
- `npm pack --dry-run --json --cache /private/tmp/step083-npm-cache`:成功,tgz 清单包含 podspec、`ios/WeatherRuntimeBridge.h/.mm`、既有 Android 与 `src`。
- package `codegenConfig.ios.modulesProvider.WeatherRuntimeBridge` 静态检查和 Native Spec 34 个方法与 ObjC++ 34 个实现逐项计数完成。
## Final control regression 真机关闭
`integration/react-native/MapLibreParticleRNTestHost/App.tsx` 新增唯一按钮 `RUN V4 FINAL CONTROL REGRESSION`,仅在当前 `reactTag` 有效时执行,且运行中禁用以避免重复并发。它复用 V4 加载的 `prepareV4` 公共 API 路径:clearAll → color binding →(如有)particle binding → T0/T1 `ACCEPTED_INSERTED` → midpoint seek。
Pleiades iPad mini 5 真机已由用户用该单按钮完成最终控制序列:`prepare#1 → PLAY → 约500ms → PAUSE → clear selected → prepare#2 → clear all → detach → attach → prepare#3 → refresh`。用户明确反馈“FINAL CONTROL REGRESSION PASS”;最后一次 prepare/refresh 后粒子恢复并保持可见。此前同一真机的 V4 Wind 验收中,用户也已确认“粒子恢复了”。
上述结果关闭此前唯一未完成的逐控制面真机验收项;STEP-083 正式 `CLOSED / PASS`。回归按钮保留为后续入口,未改动其逻辑。

View File

@@ -0,0 +1,50 @@
# STEP-084 正式粒子速度着色默认关闭与红头修复 v1
状态:`完成(等待 STEP-085 后由 RDC 统一执行 Native/真机验收)`
## 1. 目标
修复 NavSea 天气正式粒子在 Android/iOS 均出现橙红色“头部”的问题。正式 `RasterParticleLayer` 默认应严格使用宿主显式提供的 `particleColor`;速度着色只作为显式 opt-in 能力保留,不再作为正式粒子的默认行为。
## 2. 基线
- 机器:Mac-mini-tei.lan。
- 工作仓库:`/Users/tei/dev/MapLibreParticleLab-step084`,独立 worktree,避免触碰主 Mac 工作树既有 dirty/untracked。
- branch:`step/084-particle-speed-color-default-off`。
- start commit:`91d4859121afef98e05efac923797b31835a49df`(tag `maplibre-particle/rn/13.4.1-particle.7`)。
- Integration repo 已同步并核对 `main`;本修正不改变 RN 公共 API、QBIN/Field contract 或 NavSea/Particle 责任边界。
## 3. 根因
正式 fragment/Metal shader 在 `speedColorEnabled=true` 时,会对当前粒子段覆盖 `particleColor`,使用内置速度色带;高速端为橙红色。`RasterParticleLayer::Impl` 当前默认值为 `true`,因此 Host 未显式设置该属性时,Android OpenGL 与 iOS Metal 都会出现同样的红头。
## 4. 必做项
- [x] 将正式 `RasterParticleLayer` 的 `speedColorEnabled` 默认值改为 `false`。
- [x] 将 `ParticleGpuResources` 初始默认值同步为 `false`,避免资源首次创建后、视觉属性同步前出现单帧速度着色。
- [x] 保留 shader 中速度着色逻辑和显式 setter,不删除 Validation/Test 能力。
- [x] 更新 `Layer.RasterParticleVisualProperties` 回归测试:默认必须为 OFF,显式打开后仍可为 ON,再可关闭。
- [x] 默认 OFF → 显式 ON → 再 OFF 的 Core Layer 回归语义已由定向测试源码断言覆盖;未运行测试二进制。
- [x] `ParticleGpuResources` 初始值、RenderLayer visual-property 同步和 shader UBO 开关的静态调用链已复核。
- [x] `git diff --check` 通过。
- [x] scoped commit 并 push 到 `origin/particle/main`(commit 与远端确认见本轮最终交付)。
- [ ] Mac Metal 精确编译、Android Native/AAR 编译与 Android/iOS 设备验收:`BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC`。任务书要求与 STEP-085 性能修复合并验收,故本 Step 不单独声称平台 PASS。
## 5. 禁止范围
- 不修改 QBIN V4、FieldProfile、Runtime Window、时间插值或天气数据。
- 不增加 iOS/Android 专用公共协议,不增加 RN 平台分叉。
- 不修改 NavSea。
- 不删除 speed-color 能力;只改变正式层默认值。
- 不执行 Android Gradle/AAR/APK 编译、安装或 ADB 操作;Android 构建仍由用户手工执行。
## 6. 验收语义
默认情况下 `current ? particleColor : trailColor` 选择完成后,不再进入 `particleSpeedColor(...)` 覆盖分支;只有调用方显式打开 speed-color 时才允许速度色覆盖当前粒子段。由于 OpenGL GLSL 与 Metal shader 共用同一 `speedColorEnabled`/UBO 语义,本修正应在重新构建各平台 Native 后同时生效。
## 7. Codex 静态收尾
- 远端 `origin/particle/main` 在本 Step 开始时与 start commit 一致;现有 dirty 仅含任务书列出的三处源码/测试修改及本 Step 记录,未混入 STEP-085 性能代码。
- `RasterParticleLayer::Impl` 默认值与 `ParticleGpuResources` 的资源首帧默认值均为 `false`;显式 setter 及 shader speed-color 分支保持不变。
- `Layer.RasterParticleVisualProperties` 明确覆盖 default OFF、set ON 与 set OFF,因此正式宿主未设属性时不再选择内置速度色带,诊断/显式 opt-in 仍然可用。
- 未执行任何 Native 编译、链接、artifact、Gradle、ADB、iOS 安装或真机工作;这些项目由 RDC 在 STEP-085 后统一验收。

View File

@@ -0,0 +1,35 @@
# STEP-085 V4 Renderer 性能修复 v1
状态:`完成(BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC)`
## 基线
- 机器:Mac-mini-tei.lan。
- worktree:`/Users/tei/dev/MapLibreParticleLab-step085`。
- branch:`step/085-v4-render-efficiency`。
- STEP-085 Start HEAD:`bc6b8dd84b4c10e5263d61832661508c02d84d88`。
- STEP-084 Final HEAD:`bc6b8dd84b4c10e5263d61832661508c02d84d88`,已进入 `origin/particle/main`。
## 本轮结果
- [x] V4 WeatherColor PAUSE/静态 window 不再因缺少 legacy timeline 永久 `needsRepaint()`。
- [x] V4 Particle 使用 Source-owned backend-neutral cadence;30/60 gate simulation,AUTO 保持 animation-frame 语义。
- [x] Forecast PAUSE 与 Particle visual simulation 解耦;camera redraw 未到 deadline 时只 composite。
- [x] V4 Renderer prepare 改持 shared immutable snapshot handle,稳定 window 不深复制大数组。
- [x] WeatherColor 稳定 wrap/resource 状态复用 drawable,mix/opacity/stops 由 tweaker UBO 更新。
- [x] 加入 DEBUG/Validation cumulative counters。
- [x] Android/iOS existing Runtime thin seam 为 V4 30/60 提供 deadline wake;无新 RN API 或平台专用 Core。
- [x] 自审将 platform wake 收紧为“已绑定的 fieldKey 已收到 accepted V4 submit”;clear 非粒子 field 不会错误停止仍可运行的粒子,disable 后 re-enable 可恢复既有 V4 field 的 wake。
- [x] 审查 `advanceForecastClock` production owner;当前未发现,已明确记录而未扩大重构。
- [x] `git diff --check` 通过。
- [ ] Native C++/Metal/Android 编译、链接、codegen、artifact、安装、设备和性能采样:`BUILD_NOT_RUN_BY_CODEX — WAITING_FOR_RDC`。
## 禁止范围核对
未修改 NavSea、provider/network、QBIN V4、FieldProfile/Binding 对外 contract、RN 公共 API、C-001、`detach()` 语义或项目总则。未新增第二套 runtime/timeline,也未引入平台专用 Particle Core。
## 已知风险与下一步
V4 ForecastClock PLAY 的自动生产推进仍无调用 owner,因此未声称 PLAY 自动推进已经修复。RDC 必须先按完成报告进行 Mac C++/Metal、Android Native/AAR、shader/build graph 编译;成功后用统一颜色+性能真机矩阵验证 30/60/AUTO、PAUSE、speed-color、camera 和 debug counters。
本 Step 只完成源码和静态自审;Android Java/Objective-C++ wake 状态调整尚未经过目标平台编译。

View File

@@ -0,0 +1,34 @@
# STEP-087 QBIN V3 解码与 13.4.1-particle.9 升级
## 状态
CLOSED / PASS
## 已完成
- 已将用户提供的《WeatherV2 QBIN V3 通用天气场二进制格式与 React 独立 Render Binding 设计 v2.2 最终冻结稿》保存到 `docs/30_设计/`,原文件内容与 SHA-256 保持一致。
- 新增独立 `QbinV3Decoder`,生产 Block Geometry 冻结为 16×16。
- `EncodedFieldIngress` 改为 QBV2 / QBV3 显式 magic 分流;QBV2 兼容链保留。
- `RasterArraySource::submitEncodedField()` 改为统一走 `EncodedFieldIngress::decodeAndValidate()`。
- CMake / Bazel Core 已注册 V3 decoder;CMake test 已注册 V3 decoder tests。
- 新增 8 个 Current / Rain / Wave / Wind QBIN V3 Golden Fixtures、manifest 与中文校验脚本。
- 新增 C++ 定向测试,覆盖 block boundary、Direction wrap、all-invalid、非最优 Rice-K、CRC/descriptor/validity corruption、Rice padding、残差越界、Node grid 与 ingress 分流。
- Mac clang 定向构建并运行 8 个 V3 tests:8/8 PASS。
- Native / RN 版本声明已统一升级到 `13.4.1-particle.9`。
## Block Geometry
真实 8 帧 benchmark:16×16 数值 payload 为 2,470,419 bytes;32×32 为 2,514,288 bytes,32×32 增加约 1.7758%。32×32 的 decode 总时间仅减少约 2.67 ms / 8 帧,因此最终冻结 16×16。详见 `docs/30_设计/MapLibreParticle_QBIN_V3_Decoder与BlockGeometry实施决策_v1.md`。
## 最终验证与发布
- `verify-qbin-v3-golden-fixtures.py`:PASS(8 files,block=16×16)。
- QBIN V3 C++ 定向 tests:PASS 8 / FAIL 0。
- Android Release 四 ABI:armeabi-v7a、arm64-v8a、x86、x86_64 全部构建通过。
- iOS Metal `MapLibre.dynamic`:PASS;device-arm64 XCFramework 已生成并发布。
- RN tgz:已生成并发布。
- Android Maven / RN tgz / iOS XCFramework 公网 readback:PASS。
- SwiftPM `6.28.0-particle.9`:已发布。
- Release tag:`maplibre-particle/rn/13.4.1-particle.9`。
- Release Final HEAD:`63047540cba703c8efeae0df71e759e165315f85`。
- Android APK / ADB 仍按项目规则由用户手工执行,不属于本次自动发布门槛。

View File

@@ -0,0 +1,122 @@
# STEP-088 静态底图缓存 PoC 与 Renderer 观测 v1
## 状态
进行中
`IMPLEMENTED / WAITING FOR ANDROID VALIDATION`
## 目标
在固定 Camera、Style/Tile 稳定、2D 且存在 WeatherColor/RasterParticle 动态层时,把动态边界之前的连续普通 LayerGroup 前缀缓存为 retained underlay。Particle Tick 仍进入现有 MapLibre update/createRenderTree/prepare/upload 主链,但绘制阶段复用静态底图纹理,只实时绘制动态边界及其上方内容。
## 基线
- 分支:`particle/main`
- 起始 HEAD:`f47469d2ab77cdb959ecfe4081dc571fa1ab78f9`
- 实现提交:`8b9ab303228c307612c47f7f3379ad06206ea14e`
- 当前版本坐标:`13.4.1-particle.10`
## 必做项
- [x] Renderer-internal dynamic boundary。
- [x] LayerGroup before/from、forward/reverse 与 original ordinal。
- [x] Renderer-owned underlay cache 与 OpenGL/Metal compositor。
- [x] full-render fallback 与 legacy ordering 保护。
- [x] 7 个 Renderer debug counters。
- [x] Native 定向测试代码与 CMake 注册。
- [x] Mac OpenGL/Metal 编译及 4/4 定向测试由 RDC 完成。
- [ ] Android/NavSea 实机视觉、counters 与性能验证。
## 允许与禁止范围
允许修改 Renderer/Core/gfx backend/shader/CMake/Native test 和本 Step 文档;必要的 `test/gl/gl_functions.test.cpp` macOS 平台条件小修纳入本次。禁止 Android/NavSea/React 业务修改、Host API、C-001、STEP-089、STEP-090、production 全量 invalidation 与 animation-only fast path。
## 当前问题
STEP-086 已确认 Particle/WeatherColor 的 `needsRepaint()` 会唤醒完整 MapLibre frame。固定 Camera 下仍重复执行 source/layer orchestration、prepare、LayerGroup opaque/translucent draw 与 present;复杂 NavSea Style 因而持续重复提交大量静态 fill/line/symbol/PBF draw。
## 实现结构
- `RenderLayer` 新增仅 Renderer 内部使用的 `startsRetainedDynamicRange()`;WeatherColor 与 RasterParticle 返回 true,不增加 Host/public API。
- `RenderOrchestrator` 保存最小动态 boundary,并提供 before/from、forward/reverse 的 LayerGroup 分段 visitor;callback 保留完整序列 original ordinal。
- Renderer-owned `RetainedUnderlayCache` 持有 RGBA offscreen texture、depth/stencil renderable、compositor LayerGroup、key、valid 状态和 build/reuse/fallback/invalidation 数据。
- 新增独立 `RetainedUnderlayCompositeShader`,OpenGL/Metal 均为 fullscreen textured quad,只采样 premultiplied RGBA,不做 map transform、color ramp 或 Particle 业务。
- OpenGL OffscreenTexture 补齐可选 depth/stencil attachment;通用 gfx Context 的四参数 offscreen 接口由 OpenGL、Metal、Vulkan、WebGPU 后端显式覆盖。
- legacy `RenderLayer::render()` 当前仅 LocationIndicator 需要保护;若它位于 boundary 以下则 full-render fallback。
## Boundary
- WeatherColor 与 RasterParticle 都是 candidate。
- 两者同时存在时取实际 ordered layer 序列中的最小 index。
- only Particle 时使用 Particle index。
- 无 candidate 时 retained path disabled,完整原 Renderer 路径不变。
- 不依赖 React layer ID/name,不由 Host/NavSea 传入。
## Underlay Cache Flow
1. 保留 `Renderer::render → createRenderTree`、source update/prepare、layer prepare、`updateLayers/processChanges`、LayerGroup tweaker/upload。
2. 先按原顺序处理 independent RenderTargets。
3. cache invalid 时,把 boundary 前 LayerGroups 以原始 ordinal/depth 映射分别执行 opaque reverse 与 translucent forward,绘制到透明 clear 的 offscreen。
4. main framebuffer 仍按原 `backgroundColor/depth/stencil` 语义 clear;background optimized-as-color 路径未改。
5. composite cached underlay,再按完整序列 original ordinal 绘制 boundary 及之后的 opaque/translucent groups。
6. legacy items、debug overlay、postRender 与 present 保持原位置。
7. fixed key 且安全条件连续成立时复用;viewport、camera matrix 或 boundary 改变时重新 build。
## Fallback
以下状态不复用缓存:无 boundary、静态前缀为空、style 尚未 loaded、3D、任意 debug options、静态层 transition/repaint、placement/fade、boundary 以下 legacy ordering、offscreen/compositor 创建失败。不能建立安全资源时计数并回到完整绘制路径。
## Renderer 观测
在既有 debug-only counters 中新增:
- `FullRendererFrame`
- `RetainedUnderlayBuild`
- `RetainedUnderlayReuse`
- `RetainedUnderlayFallback`
- `RetainedUnderlayInvalidation`
- `RetainedUnderlayStaticGroupDraw`
- `RetainedDynamicGroupDraw`
`NDEBUG` 下仍为 no-op;未增加 Host API,也未增加逐帧日志。
## 构建与测试
以下由 RDC / 普通 Terminal 在当前 STEP-088 实现上完成:
- `cmake --build build-step088-opengl --target mbgl-test-runner -j3`:成功。
- STEP-088 定向测试:4/4 成功,覆盖 boundary、LayerGroup range/original ordinal、build/reuse/resize/boundary invalidation、3D/resource failure fallback。
- `cmake --build build-ios --config Debug --target mbgl-core -j3`:iOS/Metal device arm64 `BUILD SUCCEEDED`。
- `git diff --check`:成功。
- RDC 为 Mac OpenGL test-runner 链接补充 `test/gl/gl_functions.test.cpp` Apple 平台条件:macOS 不引用仅 iPhone/Simulator 提供的 `glInvalidateFramebuffer` / `glInvalidateSubFramebuffer`。
Codex 收尾阶段按新规则未再启动任何 build/test。Android Gradle、AAR/APK、ADB、Android 设备、NavSea 实机与 Jest 均未执行。
## PoC 限制
- 本 Step 只消除稳定场景下静态前缀的重复 GPU draw;没有跳过 Map update、createRenderTree、source/TilePyramid update、prepare、tweaker 或 upload。
- production 级 style/layer/source/tile/bucket/drawable、sprite/image/glyph、context/surface 等完整 invalidation 属于 STEP-089;本 PoC 只允许在固定 Camera、Style/Tile 稳定条件下判断 reuse。
- 未做 animation-only CPU fast path;STEP-090 未执行。
- 未做通用任意 static/dynamic RenderGraph,也未修改 NavSea、React 或 C-001。
## 用户 Android/NavSea 手工验证项
1. 固定 Camera、复杂真实 Style、Particle 约 30 FPS,确认视觉与原路径一致。
2. 观察 `RetainedUnderlayBuild ≈ 1`、`RetainedUnderlayReuse` 和 `ParticleSimulationAdvance` 持续增长。
3. 确认建 cache 后 `RetainedUnderlayStaticGroupDraw` 不随 Particle FPS 增长,`RetainedDynamicGroupDraw` 持续增长。
4. 分别验证 only Particle、WeatherColor + Particle 的两种 layer 顺序。
5. pan/zoom/resize 后确认 invalidation/rebuild,不能出现 stale underlay。
6. 验证 3D、debug 与 boundary 以下 legacy ordering 会安全回到 full render。
7. 对比 Particle OFF/ON 的 draw calls、RenderThread CPU、main/JS CPU 与画面叠放顺序。
## 当前结果与下一步
STEP-088 实现与 Mac OpenGL/Metal、Native 定向测试已完成;Android/NavSea 运行态证据尚缺。因此当前不满足进入 STEP-089 的验收条件,不得进入 STEP-089/090。下一步仅由用户/RDC 完成上述 Android/NavSea 手工验证并回传事实。
## 必做输出与阻塞
- Step 记录:本文。
- Codex 完成报告:`docs/报告/STEP-088_静态底图缓存PoC与Renderer观测_Codex完成报告_v1.md`。
- 开发记录:已追加 `粒子开发记录.md`。
- 阻塞:Android/NavSea 实机验证尚未执行;这是关闭 STEP-088 和进入 STEP-089 的唯一当前验收阻塞。

View File

@@ -0,0 +1,139 @@
# STEP-088 静态底图缓存 PoC 与 Renderer 观测 v2
## 状态
进行中
`ANDROID TEST APP PASS / WAITING FOR NAVSEA VALIDATION`
STEP-088 尚未关闭,也不得据此进入 STEP-089/090。
## 本版目的
本文在 v1 实现记录基础上,记录 Android MapLibreAndroidTestApp 实机发现的 placement fallback 问题、Renderer 修正、Mac 最终复验和 Android Test App 运行态验收。v1 中的 retained underlay 架构、范围和 PoC 限制继续有效。
## 基线
- 仓库:`/Users/tei/dev/MapLibreParticleLab`
- 分支:`particle/main`
- v1 实现提交:`8b9ab303228c307612c47f7f3379ad06206ea14e`
- debug counters 日志提交:`e7d109246a225eb1c1b2f177aa30e3c7fcdf658e`
- Android placement-scoping 修正提交:`1d1a7915e0886c46be40f4fc9a2eff13192f804b`
- 当前版本坐标:`13.4.1-particle.10`
## Android 首轮问题
较早安装的 STEP-088 APK 在 fixed camera / only Particle 场景持续 full-render fallback:
```text
frame=1200 build=0 reuse=0 fallback=1199 invalidation=1199
staticDraw=0 dynamicDraw=0 particleAdvance=1199
```
根因是 retained eligibility 无条件受全局 placement transition、`placementChanged` 和 `symbolFadeChange` 约束。即使 retained static prefix 没有 symbol/placement layer,动态范围或全局 placement 状态也会阻止 cache build/reuse。
## Renderer 修正
- `RenderOrchestrator::retainedUnderlayUsesPlacement(boundary)` 只检查 boundary 之前的 retained static prefix。
- static prefix 不使用 placement 时,全局 placement transition、`placementChanged` 和 `symbolFadeChange` 不再阻止 retained cache。
- static prefix 真正使用 placement 时,上述 placement stability 保护仍然生效并安全 fallback。
- 原有 3D、debug、legacy ordering、style-not-loaded、static transition、resource/compositor failure 等 fallback 不变。
- debug-only `ParticleRetained` 周期日志保留 `reason=<lastInvalidationReason>`,用于区分 `first-frame`、`camera-or-boundary`、`style-not-loaded`、`static-transition`、`static-placement` 等原因;Release/NDEBUG 语义不变。
## Android Validation 构建辅助
`platform/android/MapLibreAndroidTestApp/build.gradle.kts` 新增测试专用 property:
```text
maplibre.validation.stripNativeDebugSymbols=true
```
默认 Debug 行为不变,仍保留 native debug symbols。只有 Validation 构建显式启用该 property 时才允许 strip native symbols;x86_64 Debug APK 由约 375 MB 降至约 57 MB。该改动不改变 Renderer、debug counters 或 `NDEBUG` 语义。
## RDC Mac 最终复验
- `cmake --build build-step088-opengl --target mbgl-test-runner -j3`:PASS / no work to do;RDC 已确认当前 placement-scoping 源码已编入 object。
- STEP-088 定向测试:4/4 PASS:
- `RetainedDynamicBoundary.SelectsFirstCandidate`
- `RetainedLayerGroupRange.PreservesForwardReverseOriginalOrdinals`
- `RetainedUnderlayCache.BuildReuseAndInvalidation`
- `RetainedUnderlayCache.UnsupportedAndResourceFailureFallBackSafely`
- `cmake --build build-ios --config Debug --target mbgl-core -j3`:`BUILD SUCCEEDED`。
- `git diff --check`:PASS。
上述命令均由 RDC 执行;Codex 文档与 Git 收尾阶段未重新执行 build/test。
## Android Test App 环境
- 设备:`192.168.200.139:5555`
- 设备标识:FZG1-2
- 系统:Android 13
- ABI:x86_64
- App:MapLibreAndroidTestApp OpenGL Debug x86_64
- Validation strip property:启用
- 构建、安装、ADB 与 Activity 启动:均成功
## Android Test App 运行态结果
### Fixed camera / only Particle
```text
frame=840 build=1 reuse=836 fallback=2 invalidation=3
reason=first-frame staticDraw=2 dynamicDraw=5022 particleAdvance=839
```
后续 `reuse` 持续增长,`staticDraw` 保持不变,证明 retained static prefix 首次 build 后被复用。
### Camera DATELINE
- 变化前:`build=1`、`staticDraw=2`。
- camera transition 后:`build=2`、`staticDraw=4`。
- 随后 `reuse` 重新持续增长。
- 观测原因:`reason=camera-or-boundary`。
结果符合 camera change → transition 期间安全 fallback/invalidate → 稳定后 rebuild 一次 → reuse。
### REAL WEATHER
- `REAL_WEATHER_INITIAL_WINDOW_READY` 正常。
- `WEATHER_COLOR_FIELD_READY` 正常。
- GSI raster + WeatherColor + Particle 正常进入 retained path。
- 代表数据:`frame=6960 build=3 reuse=6907 fallback=49 invalidation=52 staticDraw=6 particleAdvance=6959`。
- fixed camera 后 `reuse` 持续增长,`staticDraw` 保持 6。
- `TARGET_FPS_CHANGED target=FPS_30` 成功,缓存继续 reuse。
### Resize / rotation
viewport 完成 `1280x680 → 800x1160 → 1280x680`。过渡期间出现安全的 `style-not-loaded` fallback;稳定后 rebuild 并继续 reuse。设备最终恢复 `accelerometer_rotation=1`、`user_rotation=0`。
### STATIC / ACTIVE
- STATIC 时 RenderThread 约 0%。
- ACTIVE 后恢复 continuous render,retained reuse 继续增长且 `staticDraw` 保持不变。
- 旧 Test App 设备上 ACTIVE RenderThread 约 96%~107%,仅作为 Test App 对照,不代表 NavSea 最终性能。
### 异常扫描
未发现 `FATAL EXCEPTION`、`SIGSEGV`、`SIGABRT`、ANR、`GL_INVALID`、`EGL_BAD` 或 OOM。
## 当前验收边界
Android MapLibreAndroidTestApp 运行态验证为 PASS,但它不能替代 NavSea 复杂真实 Style 的最终验证。以下仍待 NavSea 完成:
1. 真实复杂 Style 的视觉与 layer ordering。
2. 大量 PBF/fill/line/symbol 下 retained static prefix 的 `staticDraw` 稳定性。
3. Particle OFF/ON 的 RenderThread、main、JS CPU 对比。
4. pan/zoom/resize/fallback 后无 stale underlay。
5. only Particle 与 WeatherColor + Particle 在真实产品 layer stack 中的一致性。
## PoC 边界
- 未实现 STEP-089 production 全量 invalidation。
- 未实现 STEP-090 animation-only CPU fast path。
- 未修改 NavSea、React、Host API 或 C-001。
- Android Test App PASS 不构成 STEP-088 overall CLOSED/PASS。
- NavSea 验证完成前不得进入 STEP-089/090。
## 当前结论
placement-scoping 修正解决了 Android 上 static prefix 不含 placement 时的每帧 fallback。Mac OpenGL/Metal、Native 定向测试及 Android Test App 运行态均已通过;STEP-088 当前唯一剩余 Gate 是 NavSea 复杂真实 Style 验证。

View File

@@ -0,0 +1,65 @@
# STEP-088 静态底图缓存 PoC 与 Renderer 观测 v3
## 状态
**ANDROID / NATIVE VALIDATION PASS;STEP-088 收尾。**
STEP-088 已验证 retained underlay 能停止静态 LayerGroup 的重复 GPU draw,并补充了 Release 可用的运行时 Cache ON/OFF、最小诊断接口、Android/iOS Native API、React Native RendererRuntime,以及 Android Test App 的真实 NavSea PBF 性能基线工具。
## 本轮新增能力
- Retained Underlay 默认开启,可按 Map 实例运行时开启/关闭。
- 关闭时立即失效缓存并恢复原始 full-render;重新开启后在满足条件时重新 build/reuse。
- Release 保留最小 pull-based diagnostics:enabled/build/reuse/fallback/invalidation/lastReason。
- Debug 继续保留详细 ParticleRetained 周期日志。
- RN 新增 RendererRuntime:
- setRetainedUnderlayEnabled(reactTag, enabled)
- getRetainedUnderlayDiagnostics(reactTag)
- Android Test App 新增:
- NavSea 正式 63 层 PBF Style;
- Particle 位置 BOTTOM / AFTER_SEA / AFTER_CLIP / BEFORE_SYMBOL_CLUSTER / TOP;
- runtime Cache ON/OFF;
- PBF ON/OFF;
- PURE PARTICLE;
- PURE PBF;
- CUSTOM PARTICLE COUNT,范围 1..10000。
## 关键 Android 性能证据
测试设备:Android 13 / x86_64 / FZG1-2,固定唐津 Camera,Particle target FPS 30。
- PURE PARTICLE,2048 粒子,无 PBF:
- RenderThread ≈ 10.2%
- Process ≈ 13.7%
- Main ≈ 2.5%
- PURE PBF,63 层正式 Style,无 Particle:
- RenderThread ≈ 0%
- Process ≈ 0%
- NavSea Style 保留、60 个 navsea_delivery layer 全隐藏、Particle 2048:
- RenderThread ≈ 25.5%
- Process ≈ 29.1%
- PBF + Particle,AFTER_CLIP:
- Cache ON RenderThread ≈ 96.5%,Process ≈ 102.2%
- Cache OFF RenderThread ≈ 99.5%,Process ≈ 111.8%
- PBF + Particle,BEFORE_SYMBOL_CLUSTER:
- Cache ON RenderThread ≈ 96.5%,Process ≈ 102.8%
- Cache OFF RenderThread ≈ 100.2%,Process ≈ 114.2%
- TOP 因 static-placement/static-transition 持续 fallback,不能作为稳定 retained 场景。
结论:STEP-088 能消除静态 GPU draw,但主要 CPU 成本仍来自 Particle continuous repaint 下 PBF/vector/static renderer pipeline 被反复处理。该证据支持下一阶段先做 STEP-089 invalidation/revision 基础,再进入 STEP-090 Static CPU Fast Path。
## 验证结果
- Android x86_64 stripped Debug Validation APK:BUILD SUCCESSFUL,约 57 MB,实机通过。
- Mac OpenGL mbgl-test-runner:PASS。
- STEP-088 directed tests:5/5 PASS。
- iOS Metal mbgl-core:BUILD SUCCEEDED。
- iOS ios-sdk-static:BUILD SUCCEEDED。
- RN package TypeScript:PASS。
- RN Test Host TypeScript:PASS。
- RN iOS bridge 单独编译:被 Test Host 仍引用旧 STEP-083 本地 SwiftPM 包阻塞;旧包缺少本轮新增 MLNMapView retained-underlay API。当前源码 iOS SDK 已通过编译,因此该项记录为测试宿主产物过期,不阻塞 STEP-089。
- git diff --check:PASS。
## 下一步
进入 STEP-089:建立静态内容 invalidation/revision 可靠判据,只做 fast-path 前置安全基础,不提前实现 STEP-090 的 CPU fast path。

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