diff --git a/docs/50_Step记录/STEP-118_tide失效引用受控排除与Fast真实验证_v1.md b/docs/50_Step记录/STEP-118_tide失效引用受控排除与Fast真实验证_v1.md new file mode 100644 index 000000000..710d5b3d4 --- /dev/null +++ b/docs/50_Step记录/STEP-118_tide失效引用受控排除与Fast真实验证_v1.md @@ -0,0 +1,21 @@ +# STEP-118 tide 失效引用受控排除与Fast真实验证 + +日期:2026-10-11 JST。用户授权:tide Source可以删除。最终状态依RDC的实际Android验收更新,不预先标PASS。 + +## 基线与数据事实 + +Integration最新已同步main=80667d6c343411d03a760425438e3d292f452118,Particle原main=fb8e78ac1ab65ccafa6fd2b181d038b90d9a00c3。STEP116旧实机原STATIC_RC:生产基础63层;Native65层、R[0,63)、C[63,65)、FastDelta0、FullDelta920,R Build/Reuse0、fallback=missing-source、missingSourceIds=[tide]、PlacementGate=false。STEP117生产Renderer/24Gate无改动,严格Android NDK C++20语法检查已通过。 + +完整旧设备JSON显示生产sources只有mapple和navsea_delivery,根本没有tide;63基础Layer中只有index2的tide-spots Symbol依赖tide。故删除不存在的Source本身不会改变Fast。 + +## 本轮受控实现 + +不修改远端正式PBF服务器Style、不改NavSea、Shared Core、ABI/公共API、iOS或Native Fast Gate。保留原五个STEP113场景及原63层原始定义和productionFingerprint。新增独立STATIC_RC_NO_TIDE:加载同样远端Style,断言生产无tide Source、唯一引用它的Layer必须是tide-spots Symbol;在仅供本机Test App测试的JSON副本中移除这一Layer,保留其他62个原定义/顺序、同样两天气层及相机/30fps/Pause和STEP116空Annotation保护。预期Native总64、R[0,62)、C[62,64)(尚须实际设备确认)。 + +Parity同时保留原production63清单/fingerprint与有效62清单/test fingerprint;明确baseLayersPreserved=false,删除唯一层id/source/count,禁止将修改后的场景宣称为原63场景。Python verifier新增场景,强制校验原63/有效62/Native64、tide-spots/tide唯一排除、原Source无tide、R[0,62)、空Annotation移除、24Gate和SHA;旧五场景仍严格要求basePreserved=true。 + +## 验收与风险 + +RDC先源码检查/scoped Git提交推送,再在用户既有授权下执行Mac标准OpenGL x86_64 Validation APK构建、设备192.168.200.139:5555安装、APK SHA与已安装base.apk SHA一致、preflight、派生场景连续90秒、真实Native Cache Build/Reuse、Fast/Full增量和24Gate及截图。无新设备结果前仅称IMPLEMENTED / WAITING_ANDROID_VALIDATION。 + +即使消除missing-source,R Symbol Placement changed/stale与#23可能继续阻塞。Fast0仍为目标FAIL;不得通过删其它Symbol、造Source、禁用fade、修改Gate或放宽24Gate得到假Fast。后续正式PBF Style是否永久删除tide-spots由Style/NavSea负责人按跨项目协调确定,本Test App派生结果不能直接替代正式Style发行验收。 diff --git a/docs/60_测试/STEP-118_tide移除Android实机验收_v1.md b/docs/60_测试/STEP-118_tide移除Android实机验收_v1.md new file mode 100644 index 000000000..2e9a70b11 --- /dev/null +++ b/docs/60_测试/STEP-118_tide移除Android实机验收_v1.md @@ -0,0 +1,12 @@ +# STEP-118 FZG1-2 tide移除专用TestApp验收 + +使用Mac仓库/现有标准build_validation_apk.sh和明确Android ADB serial 192.168.200.139:5555。严格串行,每一步退出值和实际产物均需PASS才能推进。完整证据存Git忽略artifacts目录。 + +1. Git源HEAD锁/Integration当前main,确认只改TestApp Kotlin、Python verifier、说明及报告,两份STEP110/111未跟踪用户任务书不动。 +2. 标准 ./tools/particle_android_validation/build_validation_apk.sh。核验Gradle build SUCCESS、ZIP/strip/native x86_64、APK/source commit及sha256。 +3. ADB -s 192.168.200.139:5555 install -r新APK,Success后pm path/设备base.apk SHA与本地APK一致。 +4. python3 -B tools/particle_android_validation/verify_step113_fast_path.py --preflight-only。必须PREFLIGHT_PASS。 +5. python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC_NO_TIDE --seconds 90。验证新scene的实际Native Layer64、原生产63层manifest及同旧fingerprint、有效62基础层、C天气2层、R[0,62)、tide Source确实不存在、missingSourceIds不再包含tide。 +6. 读取result.json的真实FastDelta/FullDelta、R Build/Reuse/lastFallbackReason、Gate #3/#4/#12/#13/#22/#23、decisionMismatch。退出码2 NO_FAST_OBSERVED是负结论,不允许跳过。 +7. 必要时通过设备菜单EXPORT LAST SNAPSHOT并用ADB run-as取原生JSON、截屏;Native120帧24Gate的各窗口失败计数和原动画/像素效果分开验收。 +8. 新场景不等于原63层:原STATIC_RC仍保留严格basePreserved=true与真实missing-source负基线。若Fast仍为0且placementGatePassed=false,结论只能是缺源局部已解除、Fast仍BLOCKED;不能冒称已获得性能收益。真正Fast>0仍需要资源/Source/Style变更回退Full及用户实际画面验收。 diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt index 77f0a1674..f4c37f69d 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step113PbfGateScene.kt @@ -28,7 +28,7 @@ internal class Step113PbfGateScene( private val map: MapLibreMap, private val display: (String) -> Unit ) { - enum class Scenario { PBF63, STATIC_RC, PARTICLE_OFF, PARTIAL_RCRR, SOURCE_CHANGE } + enum class Scenario { PBF63, STATIC_RC, PARTICLE_OFF, PARTIAL_RCRR, SOURCE_CHANGE, STATIC_RC_NO_TIDE } private val handler = Handler(Looper.getMainLooper()) private var generation = 0 @@ -123,6 +123,32 @@ internal class Step113PbfGateScene( val ids = (0 until base.length()).map { base.getJSONObject(it).getString("id") } check(ids.distinct().size == ids.size && "clip-outline" in ids && "sea-area-fill" in ids && "special-pattern-line-754" in ids) { "正式 Style 边界 ID 缺失或重复" } + // STEP-118: Keep the original 63-layer PBF baseline unchanged. + // Only the explicitly named user-approved variant excludes one + // orphaned Symbol layer whose tide Source is already absent. + val omitTideLayer = scenario == Scenario.STATIC_RC_NO_TIDE + val tideDependent = (0 until base.length()).filter { + base.getJSONObject(it).optString("source") == TIDE_SOURCE + } + if (omitTideLayer) { + check(!root.getJSONObject("sources").has(TIDE_SOURCE)) { + "正式Style出现真实tide Source;停止本次仅移除孤立层实验" + } + check(tideDependent.size == 1 && + base.getJSONObject(tideDependent.single()).optString("id") == TIDE_LAYER && + base.getJSONObject(tideDependent.single()).optString("type") == "symbol") { + "tide引用结构不符合已核验的唯一孤立Symbol层;禁止扩大删除范围" + } + } + val effectiveBase = if (omitTideLayer) JSONArray().apply { + for (i in 0 until base.length()) { + if (i !in tideDependent) put(base.getJSONObject(i)) + } + } else base + val effectiveIds = (0 until effectiveBase.length()).map { effectiveBase.getJSONObject(it).getString("id") } + check(!omitTideLayer || (effectiveBase.length() == 62 && TIDE_LAYER !in effectiveIds)) { + "tide移除数量不等于1;停止" + } val source = root.getJSONObject("sources").getJSONObject("navsea_delivery") check(root.optString("sprite") == SPRITE && root.optString("glyphs") == GLYPHS && source.optString("type") == "vector" && source.optJSONArray("tiles")?.optString(0) == TILES) { @@ -136,16 +162,27 @@ internal class Step113PbfGateScene( .put("styleName", root.optString("name")) .put("fingerprintBasis", "SDK-returned-production-json-utf8") .put("productionFingerprintSha256", sha256(loaded.json)) - .put("baseLayerCount", base.length()).put("baseLayersPreserved", true) + .put("baseLayerCount", base.length()).put("baseLayersPreserved", !omitTideLayer) + .put("effectiveBaseLayerCount", effectiveBase.length()) + .put("tideLayerExcluded", omitTideLayer) + .put("excludedLayerId", if (omitTideLayer) TIDE_LAYER else JSONObject.NULL) + .put("excludedLayerSource", if (omitTideLayer) TIDE_SOURCE else JSONObject.NULL) + .put("excludedLayerCount", if (omitTideLayer) 1 else 0) + .put("tideOriginalSourcePresent", root.getJSONObject("sources").has(TIDE_SOURCE)) .put("full113Equivalent", false).put("missingProductionDynamicLayerDefinitions", true) .put("dynamicParity", "测试场结构模式;缺少同版本113层完整Source/Layer/visibility/数据快照") .put("navSeaSourceHead", "ad9aa043fde37f87462f31de3fbd8ce008f8424b") .put("navSeaSourceSelector", "RenderSegmentHost.tsx:RC@rn-weather-color") .put("deviceReferenceSelector", "用户STEP-112:RCRR_PREFIX[0,13)/[13,33)/[33,107)/[107,113)") - .put("baseLayerManifest", layerManifest(base)).put("sprite", root.optString("sprite")) + .put("baseLayerManifest", layerManifest(base)) + .put("effectiveBaseLayerManifest", layerManifest(effectiveBase)) + .put("sprite", root.optString("sprite")) .put("glyphs", root.optString("glyphs")).put("vectorSource", JSONObject(source.toString())) .put("productionSources", JSONObject(root.getJSONObject("sources").toString())) - .put("internalAnnotationPolicy", "仅STATIC_RC加载时移除空SDK辅助层;后续添加不再移除;原63层及Source原样") + .put("internalAnnotationPolicy", "仅STATIC_RC及STATIC_RC_NO_TIDE加载时移除空SDK辅助层;后续添加不再移除") + .put("styleAdjustmentScope", if (omitTideLayer) + "TEST-APP-ONLY;原生产Style63层不变;测试副本排除唯一tide-spots→tide孤立Symbol层" else + "NONE;生产Style基础63层原样") val hasField = scenario != Scenario.PBF63 if (hasField) { val sources = root.getJSONObject("sources") @@ -153,9 +190,9 @@ internal class Step113PbfGateScene( sources.put(SOURCE, JSONObject().put("type", "raster-array").put("host-field-submission", true)) val layers = JSONArray() val prefix = scenario == Scenario.PARTIAL_RCRR - for (i in 0 until base.length()) { - layers.put(base.getJSONObject(i)) - if (prefix && base.getJSONObject(i).getString("id") == "clip-outline") addFields(layers) + for (i in 0 until effectiveBase.length()) { + layers.put(effectiveBase.getJSONObject(i)) + if (prefix && effectiveBase.getJSONObject(i).getString("id") == "clip-outline") addFields(layers) } if (!prefix) addFields(layers) // NavSea current source fragment appends to top. if (scenario == Scenario.SOURCE_CHANGE) { @@ -167,18 +204,25 @@ internal class Step113PbfGateScene( } root.put("layers", layers) } - // Prove every original definition, including visibility, survives unchanged and in order. + // Prove all non-excluded production definitions, including + // visibility and order, remain byte-equivalent within the JSON. + // The original 63-layer manifest/fingerprint is kept separately. val finalLayers = root.getJSONArray("layers") val retainedBase = (0 until finalLayers.length()).map { finalLayers.getJSONObject(it) } .filter { it.getString("id") in ids } - check(retainedBase.size == base.length() && retainedBase.map { it.getString("id") } == ids && - retainedBase.indices.all { retainedBase[it].toString() == base.getJSONObject(it).toString() }) { - "基础图层等价检查失败" + check(retainedBase.size == effectiveBase.length() && + retainedBase.map { it.getString("id") } == effectiveIds && + retainedBase.indices.all { + retainedBase[it].toString() == effectiveBase.getJSONObject(it).toString() + } && (!omitTideLayer || retainedBase.none { it.optString("source") == TIDE_SOURCE })) { + "修改后的基础图层集合与用户授权的单一tide排除不一致" } parity.put("testStyleFingerprintSha256", sha256(root.toString())) .put("testJsonLayerCount", finalLayers.length()) - .put("baseParityBasis", "同次SDK生产Style回读;完整定义逐层比较,不是设备画面证明") - val plan = planJson(scenario, ids.last()) + .put("baseParityBasis", if (omitTideLayer) + "同次SDK生产Style63层回读;测试副本仅排除tide-spots,其余62层定义逐项比较;不等同原63层像素验收" else + "同次SDK生产Style回读;完整定义逐层比较,不是设备画面证明") + val plan = planJson(scenario, effectiveIds.last()) parity.put("testPlanSelector", if (scenario == Scenario.PARTIAL_RCRR) "结构RCRR:sea-area-fill/special-pattern-line-754/最后生产层ID;非现场113边界" else if (hasField) "当前NavSea源码RC:rn-weather-color" else "基础63层全RETAINED") @@ -187,7 +231,9 @@ internal class Step113PbfGateScene( map.setStyle(Style.Builder().fromJson(root.toString())) testStyle@ { style -> if (!running || epoch != generation) return@testStyle try { - if (scenario == Scenario.STATIC_RC) removeEmptyAnnotationLayer(style, ids) + if (scenario == Scenario.STATIC_RC || scenario == Scenario.STATIC_RC_NO_TIDE) { + removeEmptyAnnotationLayer(style, ids) + } map.cameraPosition = CameraPosition.Builder().target(LatLng(33.45, 129.97)) .zoom(10.5).bearing(0.0).tilt(0.0).build() if (hasField) initializeFields() @@ -344,6 +390,12 @@ internal class Step113PbfGateScene( return JSONObject().put("schema", "step113-summary").put("schemaVersion", 1).put("scenario", scenario.name) .put("sceneReady", ready).put("blockedReason", failureReason ?: JSONObject.NULL) .put("sceneGeneration", generation).put("full113Equivalent", false).put("basePreserved", parity.optBoolean("baseLayersPreserved")) + .put("originalBaseLayerCount", parity.optInt("baseLayerCount")) + .put("effectiveBaseLayerCount", parity.optInt("effectiveBaseLayerCount")) + .put("tideLayerExcluded", parity.optBoolean("tideLayerExcluded")) + .put("excludedLayerId", parity.opt("excludedLayerId") ?: JSONObject.NULL) + .put("excludedLayerSource", parity.opt("excludedLayerSource") ?: JSONObject.NULL) + .put("excludedLayerCount", parity.optInt("excludedLayerCount")) .put("fingerprint", parity.optString("productionFingerprintSha256")).put("styleUrl", STYLE_URL) .put("actualLayers", native.optInt("styleLayerCount")).put("planValid", native.optBoolean("valid")) .put("planReason", native.optString("lastReason")).put("lifecycle", t.optLong("lifecycle")) @@ -436,6 +488,8 @@ internal class Step113PbfGateScene( private const val PARTICLE = "rn-weather-particles" private const val PROBE_SOURCE = "step113-source-probe" private const val PROBE_LAYER = "step113-source-probe-circle" + private const val TIDE_SOURCE = "tide" + private const val TIDE_LAYER = "tide-spots" private const val ANNOTATION_SOURCE = "org.maplibre.annotations" private const val ANNOTATION_LAYER = "org.maplibre.annotations.points" private const val FORECAST_MS = 1786060800000L diff --git a/tools/particle_android_validation/README.md b/tools/particle_android_validation/README.md index f6261766d..72bd2c178 100644 --- a/tools/particle_android_validation/README.md +++ b/tools/particle_android_validation/README.md @@ -135,3 +135,14 @@ FAST_OBSERVED 仅表示实际 Fast 数字增长,不代表像素等价、性能 本轮只交源码,缺 `tide` 与 Symbol R 缓存准入继续 fail-closed,不能宣称原 STATIC_RC 已出现 Fast。Native AnnotationManager 在后续真实 Symbol Annotation add/update 与 Style loaded 时同步恢复缺失 points;Test App stop 重加原空层,后续 Annotation/Style 变化撤销空层验收条件,不周期删除新内容。原63层和Source定义保留,内部Annotation Source不删除;源码预期STATIC_RC由66层变65层,须以最新设备manifest核验。 复测脚本现要求至少75秒,在PID出现后等待完整请求时长;建议 `--scenario STATIC_RC --seconds 90`。同generation/lifecycle、plan/base parity、24Gate、mismatch0、计数单调及blocked撤销均为必要条件。summary新增缺源ID/Placement/commands/Source表;120帧仍需用户手动导出。精确命令、用户一次手工build/install、RDC Native定向测试及阴性结果解释见 [STEP-116 专用APP验收](../../docs/60_测试/STEP-116_STEP113原PBF专用APP验收_v1.md)。历史STEP115记录保留为当时事实,不代表本轮设备已验收。 + +## STEP-118 用户批准排除 tide 的独立实验 + +2026-10-11 用户明确允许删除 tide。正式PBF的 sources 中本就不存在 tide;唯一引用它的是原63基础层中的 tide-spots(Symbol)。只运行removeSource并不能移除这个依赖。新增独立 STATIC_RC_NO_TIDE 实验场景,在 TestApp 的临时Style副本中**只移除这一条孤立Layer**,原五场景、远端PBF Style和Native24 Gate不变。原生产63层及fingerprint完整保留在Parity数据中,派生场景明确标为基础62层(加两天气层总64,SDK空Annotation移除),不允许冒充原63层生产验收。 + +派生场景在读取原Style时严格断言当前tide Source不存在、唯一引用层是Symbol tide-spots,否则BLOCKED。旧场景验收器仍检查basePreserved=true;新场景验收器独立要求原63/有效62/总64、唯一排除tide-spots/tide、R[0,62)、Annotation Source保留、24 Gate、SHA同版本约束、真实Native增量。源缺失被解除不代表Symbol Placement、Cache Build或Fast已经成功。 + +启动命令: + python3 -B tools/particle_android_validation/verify_step113_fast_path.py --scenario STATIC_RC_NO_TIDE --seconds 90 + +完整步骤见 docs/60_测试/STEP-118_tide移除Android实机验收_v1.md。 diff --git a/tools/particle_android_validation/verify_step113_fast_path.py b/tools/particle_android_validation/verify_step113_fast_path.py index 8cbad5d6e..5dcdfd400 100755 --- a/tools/particle_android_validation/verify_step113_fast_path.py +++ b/tools/particle_android_validation/verify_step113_fast_path.py @@ -18,7 +18,7 @@ APK = ARTIFACTS / "MapLibreAndroidTestApp-validation-opengl-debug-x86_64.apk" BUILD_INFO = ARTIFACTS / "build-info.txt" PACKAGE = "org.maplibre.android.testapp" ACTIVITY = f"{PACKAGE}/{PACKAGE}.activity.particle.ParticleCoreLayerValidationActivity" -SCENARIOS = ("STATIC_RC", "PBF63", "PARTICLE_OFF", "PARTIAL_RCRR", "SOURCE_CHANGE") +SCENARIOS = ("STATIC_RC", "PBF63", "PARTICLE_OFF", "PARTIAL_RCRR", "SOURCE_CHANGE", "STATIC_RC_NO_TIDE") LOG_ENTRY = re.compile(r"^[A-Z]/STEP113Gate\(\s*(\d+)\):\s*(\{.+\})\s*$") @@ -106,6 +106,37 @@ def extract(logcat, pid, expected): return [x[1] for x in sorted(result, key=lambda item: item[0])] +def scene_parity_valid(sample, scenario): + """Fail closed on the *declared* base style, not just a loose layer count. + + Original five STEP-113 scenarios require all 63 original base layers. + STEP-118 explicitly names a user-approved test-copy exclusion of the + orphaned tide-spots layer: 63 remote originals -> 62 copied basics, plus + the two known weather layers (and no empty Annotation point layer). + It is not equivalent to the unmodified original 63-layer test. + """ + if not sample.get("planValid"): + return False + if scenario != "STATIC_RC_NO_TIDE": + return sample.get("basePreserved") is True and not sample.get("tideLayerExcluded", False) + return ( + sample.get("basePreserved") is False + and sample.get("tideLayerExcluded") is True + and sample.get("originalBaseLayerCount") == 63 + and sample.get("effectiveBaseLayerCount") == 62 + and sample.get("excludedLayerId") == "tide-spots" + and sample.get("excludedLayerSource") == "tide" + and sample.get("excludedLayerCount") == 1 + and sample.get("actualLayers") == 64 + and sample.get("commands", {}).get("emptyBuiltinAnnotationLayerRemoved") is True + and sample.get("commands", {}).get("annotationSourcePreserved") is True + and all(source.get("sourceId") != "tide" for source in sample.get("sources", [])) + and len(sample.get("retained", [])) == 1 + and sample["retained"][0].get("begin") == 0 + and sample["retained"][0].get("end") == 62 + ) + + def run(): parser = argparse.ArgumentParser(description="Check actual STEP-113 PBF on-device Fast frames") parser.add_argument("--serial", default="192.168.200.139:5555") @@ -167,7 +198,7 @@ def run(): elif any((s.get("sceneGeneration"), s.get("lifecycle")) != (start.get("sceneGeneration"), start.get("lifecycle")) for s in samples): status = "SUMMARY_LIFECYCLE_CHANGED" - elif any(not s.get("planValid") or not s.get("basePreserved") for s in samples): + elif any(not scene_parity_valid(s, args.scenario) for s in samples): status = "INVALID_PLAN_OR_BASE_PARITY" elif any(len(s.get("gateFailureCounts", [])) != 24 for s in samples): status = "INCOMPLETE_GATE_SUMMARY" @@ -188,7 +219,12 @@ def run(): "device": args.serial, "appPid": process_id, "scenario": args.scenario, - "sourceReport": "STEP-113 original PBF test scene; not STEP-114 smoke", + "sourceReport": ( + "STEP-118 user-approved TEST-APP-ONLY 62-layer derivative; " + "original 63-layer PBF Style remains unmodified; not STEP-114 smoke" + if args.scenario == "STATIC_RC_NO_TIDE" + else "STEP-113 original 63-layer PBF test scene; not STEP-114 smoke" + ), "durationSecondsRequested": args.seconds, "durationSecondsObservedAfterPid": observation_elapsed, "completeSummarySamples": len(samples), diff --git a/粒子开发记录.md b/粒子开发记录.md index d4a299803..35e908eec 100644 --- a/粒子开发记录.md +++ b/粒子开发记录.md @@ -1183,3 +1183,5 @@ Android V3-08 单次综合验收已完成并关闭。Native `13.4.1-particle.2` - 2026-10-11 STEP-117 RDC:新加7个C++非Jest安全回归用例源码,针对缺Source/旧GPU组、Symbol首次Build及源/图像/字形/相机变更fail-closed;两个test/renderer源文件由Android NDK OpenGL/x86_64实际compile_commands.json做C++20 -fsyntax-only,均exit0(6.3s/5.3s)。生产Native/Fast Gate/APK未改,本轮不重复构建相同APK,GTest运行/真实Fast仍BLOCKED。需要Shared Core结果证书和Integration核验真实tide语义,报告见docs/20_调查/STEP-117_PBF_R首次缓存Build安全证据_v1.md。 - 2026-10-11 STEP-117 RDC严格编译校验收尾:发现测试辅助UpdateParameters遗漏retainedUnderlayState初始化会产生Wmissing-designated-field警告,已只补该测试字段,并以真实Android NDK C++20 compile_commands.json、保留-Werror、不再添加-Wno-error,重跑两个测试源的-fsyntax-only,全部exit0无警告。非GTest实际运行、不代表GPU/Fast PASS。 + +- 2026-10-11 STEP-118 用户允许删除tide:旧PBF正式sources实际无tide,唯一坏引用为63基础Layer的tide-spots Symbol。RDC新增TestApp独立STATIC_RC_NO_TIDE副本,只排除唯一孤立Layer,原五场景/63正式Style/Native24Gate不变;严格Python verifier校验原63→有效62加天气2(预期Native64)及真实SHA、来源和R边界。正式远端Style未改。待本轮Mac APK与设备实测判断R missing-source是否解除、Symbol/Cache/Fast是否改善。