fix: expose STEP-053 host field validation path

This commit is contained in:
OpenAI Codex
2026-09-04 19:09:44 +08:00
parent b31f80c72d
commit 4b453a109b
3 changed files with 25 additions and 4 deletions

View File

@@ -107,3 +107,15 @@ MORE 菜单新增:
未实现 Weather Service、HTTP/provider、React Native binding、Manifest/Catalog、下载/重试/cache、GRIB/NetCDF/vendor decoder、WeatherColorLayer、第二套状态机。Validation UI MIX/WAITING/INVALID observability 继续为 NON-BLOCKING;Rotation/Viewport 技术债未处理。 未实现 Weather Service、HTTP/provider、React Native binding、Manifest/Catalog、下载/重试/cache、GRIB/NetCDF/vendor decoder、WeatherColorLayer、第二套状态机。Validation UI MIX/WAITING/INVALID observability 继续为 NON-BLOCKING;Rotation/Viewport 技术债未处理。
当前状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。 当前状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。
## Android Validation Host 接线补充
Android Validation Host 原有 STEP-053 控件和 JNI/API 已存在,但 MORE 菜单将入口显示为 `HOST FIELD SUBMIT`,未紧邻 `PRODUCTION FEED`,且 Kotlin 在读取本地 canonical dataset 后没有单独输出 dataset-load 事件。该可观察性不足会使手工验收难以一眼区分 STEP-051 的 `PRODUCTION FEED` 与 STEP-053 Host submission。
- MORE 菜单现在按 `PRODUCTION FEED → HOST FIELD → INVALID PROD → HOST DUPLICATE → HOST INVALID` 显示相关入口;旧 `PRODUCTION FEED` 和 `INVALID PROD` 的语义未改。
- `HOST FIELD` 创建 `{"type":"raster-array","host-field-submission":true}` source,并在 style ready 后读取既有 T0~T3 metadata/binary resources,通过 `MapLibreMap.submitRasterArraySourceHostField()` 逐片提交。
- Kotlin 对每个时间片输出低频 `HOST_FIELD_DATASET_LOAD`,带 metadata resource、payload resource 和 payload bytes;Native 既有 `HOST_FIELD_SNAPSHOT_READY`/`HOST_FIELD_INITIAL_WINDOW_READY`/`HOST_FIELD_NEXT_SUBMIT` 继续输出 fieldId、generation、validTime 和结果。
- `HOST DUPLICATE` 重新提交 Host dataset 的最后一个 Field,复用同一 Host submit API,预期 `DUPLICATE_NOOP`;`HOST INVALID` 在已存在有效 Host window 时提交 `field-invalid.json`,经同一 API 结构化拒绝并保持 last valid field。两者均不通过 style reload 冒充 Host submission。
- 未新增 JNI forwarding,复用既有 `MapLibreMap → NativeMapView → RasterArraySource::submitHostField()` 薄桥。未修改 Adapter、Timeline、Runtime Field Window、Renderer 或任何 provider/network 逻辑。
Android Gradle、APK、ADB 和设备验收仍由用户手工执行;状态继续为:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`。

View File

@@ -189,7 +189,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
findViewById<Button>(R.id.validationMenuVisual).setOnClickListener { showMenu(it, "VIS", intArrayOf(R.id.validationVisualDefault, R.id.validationVisualColor, R.id.validationVisualSizeDown, R.id.validationVisualSizeUp, R.id.validationVisualLengthDown, R.id.validationVisualLengthUp, R.id.validationVisualOpacityDown, R.id.validationVisualOpacityUp, R.id.validationVisualTrailDown, R.id.validationVisualTrailUp, R.id.validationVisualFadeDown, R.id.validationVisualFadeUp)) } findViewById<Button>(R.id.validationMenuVisual).setOnClickListener { showMenu(it, "VIS", intArrayOf(R.id.validationVisualDefault, R.id.validationVisualColor, R.id.validationVisualSizeDown, R.id.validationVisualSizeUp, R.id.validationVisualLengthDown, R.id.validationVisualLengthUp, R.id.validationVisualOpacityDown, R.id.validationVisualOpacityUp, R.id.validationVisualTrailDown, R.id.validationVisualTrailUp, R.id.validationVisualFadeDown, R.id.validationVisualFadeUp)) }
findViewById<Button>(R.id.validationMenuCount).setOnClickListener { showMenu(it, "COUNT", intArrayOf(R.id.validationParticle256, R.id.validationParticle1024, R.id.validationParticle4096, R.id.validationParticle16384, R.id.validationParticle65536, R.id.validationParticle262144, R.id.validationParticle1048576)) } findViewById<Button>(R.id.validationMenuCount).setOnClickListener { showMenu(it, "COUNT", intArrayOf(R.id.validationParticle256, R.id.validationParticle1024, R.id.validationParticle4096, R.id.validationParticle16384, R.id.validationParticle65536, R.id.validationParticle262144, R.id.validationParticle1048576)) }
findViewById<Button>(R.id.validationMenuMap).setOnClickListener { showMenu(it, "MAP", intArrayOf(R.id.validationGlobal, R.id.validationOceanA, R.id.validationOceanB, R.id.validationDateline, R.id.validationWorldWrap)) } findViewById<Button>(R.id.validationMenuMap).setOnClickListener { showMenu(it, "MAP", intArrayOf(R.id.validationGlobal, R.id.validationOceanA, R.id.validationOceanB, R.id.validationDateline, R.id.validationWorldWrap)) }
findViewById<Button>(R.id.validationMenuMore).setOnClickListener { showMenu(it, "MORE", intArrayOf(R.id.validationActive, R.id.validationStatic, R.id.validationDisable, R.id.validationEnable, R.id.validationUniformEast, R.id.validationUniformNorth, R.id.validationVortex, R.id.validationTimeWindow, R.id.validationProductionInvalid, R.id.validationHostFieldSubmit, R.id.validationHostFieldDuplicate, R.id.validationHostFieldInvalid, R.id.validationAdvanceWindow, R.id.validationDuplicateWindow)) } findViewById<Button>(R.id.validationMenuMore).setOnClickListener { showMenu(it, "MORE", intArrayOf(R.id.validationActive, R.id.validationStatic, R.id.validationDisable, R.id.validationEnable, R.id.validationUniformEast, R.id.validationUniformNorth, R.id.validationVortex, R.id.validationTimeWindow, R.id.validationHostFieldSubmit, R.id.validationProductionInvalid, R.id.validationHostFieldDuplicate, R.id.validationHostFieldInvalid, R.id.validationAdvanceWindow, R.id.validationDuplicateWindow)) }
} }
private fun showMenu(anchor: android.view.View, title: String, actions: IntArray) { private fun showMenu(anchor: android.view.View, title: String, actions: IntArray) {
@@ -239,7 +239,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} }
private fun actionLabel(id: Int): String = when (id) { private fun actionLabel(id: Int): String = when (id) {
R.id.validationVisualDefault -> "VIS DEFAULT"; R.id.validationVisualSizeDown -> "SIZE -"; R.id.validationVisualSizeUp -> "SIZE +"; R.id.validationVisualLengthDown -> "LENGTH -"; R.id.validationVisualLengthUp -> "LENGTH +"; R.id.validationVisualOpacityDown -> "OPACITY -"; R.id.validationVisualOpacityUp -> "OPACITY +"; R.id.validationVisualTrailDown -> "TRAIL -"; R.id.validationVisualTrailUp -> "TRAIL +"; R.id.validationVisualFadeDown -> "FADE -"; R.id.validationVisualFadeUp -> "FADE +"; R.id.validationVisualColor -> "COLOR"; R.id.validationParticle256 -> "256"; R.id.validationParticle1024 -> "1K"; R.id.validationParticle4096 -> "4K"; R.id.validationParticle16384 -> "16K"; R.id.validationParticle65536 -> "64K"; R.id.validationParticle262144 -> "256K"; R.id.validationParticle1048576 -> "1M"; R.id.validationGlobal -> "GLOBAL"; R.id.validationOceanA -> "OCEAN A"; R.id.validationOceanB -> "OCEAN B"; R.id.validationDateline -> "DATELINE"; R.id.validationWorldWrap -> "WORLD WRAP"; R.id.validationActive -> "ACTIVE"; R.id.validationStatic -> "STATIC"; R.id.validationDisable -> "DISABLE"; R.id.validationEnable -> "ENABLE"; R.id.validationUniformEast -> "UNIFORM EAST"; R.id.validationUniformNorth -> "UNIFORM NORTH"; R.id.validationVortex -> "VORTEX"; R.id.validationTimeWindow -> "PRODUCTION FEED"; R.id.validationProductionInvalid -> "INVALID PROD"; R.id.validationHostFieldSubmit -> "HOST FIELD SUBMIT"; R.id.validationHostFieldDuplicate -> "HOST DUPLICATE"; R.id.validationHostFieldInvalid -> "INVALID HOST"; R.id.validationAdvanceWindow -> "ADVANCE WINDOW"; else -> "DUPLICATE T1" R.id.validationVisualDefault -> "VIS DEFAULT"; R.id.validationVisualSizeDown -> "SIZE -"; R.id.validationVisualSizeUp -> "SIZE +"; R.id.validationVisualLengthDown -> "LENGTH -"; R.id.validationVisualLengthUp -> "LENGTH +"; R.id.validationVisualOpacityDown -> "OPACITY -"; R.id.validationVisualOpacityUp -> "OPACITY +"; R.id.validationVisualTrailDown -> "TRAIL -"; R.id.validationVisualTrailUp -> "TRAIL +"; R.id.validationVisualFadeDown -> "FADE -"; R.id.validationVisualFadeUp -> "FADE +"; R.id.validationVisualColor -> "COLOR"; R.id.validationParticle256 -> "256"; R.id.validationParticle1024 -> "1K"; R.id.validationParticle4096 -> "4K"; R.id.validationParticle16384 -> "16K"; R.id.validationParticle65536 -> "64K"; R.id.validationParticle262144 -> "256K"; R.id.validationParticle1048576 -> "1M"; R.id.validationGlobal -> "GLOBAL"; R.id.validationOceanA -> "OCEAN A"; R.id.validationOceanB -> "OCEAN B"; R.id.validationDateline -> "DATELINE"; R.id.validationWorldWrap -> "WORLD WRAP"; R.id.validationActive -> "ACTIVE"; R.id.validationStatic -> "STATIC"; R.id.validationDisable -> "DISABLE"; R.id.validationEnable -> "ENABLE"; R.id.validationUniformEast -> "UNIFORM EAST"; R.id.validationUniformNorth -> "UNIFORM NORTH"; R.id.validationVortex -> "VORTEX"; R.id.validationTimeWindow -> "PRODUCTION FEED"; R.id.validationHostFieldSubmit -> "HOST FIELD"; R.id.validationProductionInvalid -> "INVALID PROD"; R.id.validationHostFieldDuplicate -> "HOST DUPLICATE"; R.id.validationHostFieldInvalid -> "HOST INVALID"; R.id.validationAdvanceWindow -> "ADVANCE WINDOW"; else -> "DUPLICATE T1"
} }
private fun loadValidationStyle(active: Boolean, enabled: Boolean, reason: String = "STYLE_CHANGE") { private fun loadValidationStyle(active: Boolean, enabled: Boolean, reason: String = "STYLE_CHANGE") {
@@ -414,14 +414,17 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
return MapLibreMap.HOST_FIELD_REJECTED_SOURCE_MODE return MapLibreMap.HOST_FIELD_REJECTED_SOURCE_MODE
} }
return try { return try {
val metadata = assets.open("integration/particle-production/field-0$index.json") val metadataAsset = "integration/particle-production/field-0$index.json"
val metadata = assets.open(metadataAsset)
.bufferedReader().use { it.readText() } .bufferedReader().use { it.readText() }
val payloadAsset = when (index) { val payloadAsset = when (index) {
1 -> "uniform-north" 1 -> "uniform-north"
2 -> "vortex" 2 -> "vortex"
else -> "uniform-east" else -> "uniform-east"
} }
val payload = assets.open("integration/particle-fixtures/$payloadAsset/field.f32").use { it.readBytes() } val payloadAssetPath = "integration/particle-fixtures/$payloadAsset/field.f32"
val payload = assets.open(payloadAssetPath).use { it.readBytes() }
Log.i(TAG, "ParticleField: event=HOST_FIELD_DATASET_LOAD index=$index metadata=$metadataAsset payload=$payloadAssetPath payloadBytes=${payload.size}")
val result = maplibreMap.submitRasterArraySourceHostField(PARTICLE_SOURCE_ID, metadata, payload) val result = maplibreMap.submitRasterArraySourceHostField(PARTICLE_SOURCE_ID, metadata, payload)
Log.i(TAG, "ParticleField: event=HOST_FIELD_SUBMIT index=$index duplicate=$duplicate payloadBytes=${payload.size} result=$result") Log.i(TAG, "ParticleField: event=HOST_FIELD_SUBMIT index=$index duplicate=$duplicate payloadBytes=${payload.size} result=$result")
result result

View File

@@ -384,3 +384,9 @@
- Android `MapLibreMap`/JNI 增加单 Field binary submit;Validation Host 增加 HOST FIELD SUBMIT、HOST DUPLICATE、INVALID HOST,并由 Kotlin 读取本地资源模拟未来 React Native Host。旧 fixture 与 STEP-051 production feed 保留。 - Android `MapLibreMap`/JNI 增加单 Field binary submit;Validation Host 增加 HOST FIELD SUBMIT、HOST DUPLICATE、INVALID HOST,并由 Kotlin 读取本地资源模拟未来 React Native Host。旧 fixture 与 STEP-051 production feed 保留。
- Linux OpenGL `mbgl-test-runner`、`mbgl-render`、`mbgl-render-test-runner` 构建通过;新增 Host/canonical 4/4、Source 44/44、RasterParticle property 2/2 PASS。未执行 Android Gradle/APK/ADB/设备或 Jest。 - Linux OpenGL `mbgl-test-runner`、`mbgl-render`、`mbgl-render-test-runner` 构建通过;新增 Host/canonical 4/4、Source 44/44、RasterParticle property 2/2 PASS。未执行 Android Gradle/APK/ADB/设备或 Jest。
- Renderer、GPU simulation/trail、Runtime Window、Playback ownership、WeatherColorLayer 分离边界未改。状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`;用户正式数据尚未提供,不能提前关闭。 - Renderer、GPU simulation/trail、Runtime Window、Playback ownership、WeatherColorLayer 分离边界未改。状态:`IMPLEMENTED / WAITING_ANDROID_VALIDATION`;用户正式数据尚未提供,不能提前关闭。
## 2026-09-04 — STEP-053 Android Validation Host 接线补充
- STEP-053 Host submission API/JNI 和 Host 控件已在 `b31f80c...` 存在;为让手工入口与 STEP-051 `PRODUCTION FEED` 一眼区分,MORE 现显示 `PRODUCTION FEED → HOST FIELD → INVALID PROD → HOST DUPLICATE → HOST INVALID`。
- `HOST FIELD` 继续只读取既有 T0~T3 metadata/binary assets 并逐片调用正式 `MapLibreMap.submitRasterArraySourceHostField()`;新增低频 `HOST_FIELD_DATASET_LOAD` 日志,Native 继续输出带 fieldId/generation/validTime 的 Host submit 事件。
- Host duplicate 与 Host invalid 均重用同一 Host submission contract,不通过 style reload 伪造验证。未修改 Adapter、Timeline、Runtime Window、Renderer、JNI/Core 语义或任何网络/provider 逻辑;状态仍为 `IMPLEMENTED / WAITING_ANDROID_VALIDATION`。