diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt index df1bc9bb9..d2403b41a 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/ParticleCoreLayerValidationActivity.kt @@ -105,6 +105,26 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() { "fullCpuFrame=${snapshot.optLong("fullCpuFrame")} ranges=${ranges.joinToString(";")}" diagnostics.text = "STEP-102 $message" Log.i(TAG, "STEP102_SAMPLE $message") + if (step102Tick == 8L) { + // One-shot proof of actual runtime style ordering and annotation boundary. + val styleLayers = snapshot.optJSONArray("styleLayers") + val layerOrder = buildList { + for (index in 0 until (styleLayers?.length() ?: 0)) { + val layer = styleLayers?.optJSONObject(index) ?: continue + add("${layer.optInt("layerIndex")}:${layer.optString("layerId")}:${layer.optString("layerType")}") + } + } + val rangeOrder = buildList { + for (index in 0 until (segments?.length() ?: 0)) { + val segment = segments?.optJSONObject(index) ?: continue + add("${segment.optInt("segmentIndex")}:${segment.optString("strategy")}" + + "[${segment.optInt("beginIndex")},${segment.optInt("endExclusive")})" + + "=${segment.optJSONArray("layers")?.length() ?: 0}") + } + } + Log.i(TAG, "STEP102_STYLE_LAYER_LAYOUT scenario=$scenario layers=${layerOrder.joinToString(";")}" + + " segments=${rangeOrder.joinToString(";")}") + } if (step102Tick == 8L || step102Tick == 130L) { Log.i(TAG, "STEP102_WINDOW_MARKER phase=${if (step102Tick == 8L) "baseline" else "final"} " + "scenario=$scenario tick=$step102Tick") diff --git a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt index d5b2a2639..6e5ec4613 100644 --- a/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt +++ b/platform/android/MapLibreAndroidTestApp/src/main/java/org/maplibre/android/testapp/activity/particle/Step102PlacementFixture.kt @@ -3,7 +3,13 @@ package org.maplibre.android.testapp.activity.particle import org.json.JSONArray import org.json.JSONObject -/** Offline STEP-102 R-C-R fixture; only the upper retained range changes placement capability. */ +/** + * Offline STEP-102 locality fixture. + * R0 / particle C0 / upper R1 keep identical boundaries for T1 and T2. + * MapLibre appends its internal annotation Symbol layer after the style layers: + * keep that layer in a final live C2 range, outside the upper Retained cache. + * Never bypass Renderer placement safety gates to make a fixture pass. + */ internal object Step102PlacementFixture { enum class Scenario { T1_FREE_SYMBOL, T2_TWO_FREE } @@ -12,6 +18,8 @@ internal object Step102PlacementFixture { private const val PARTICLE_SOURCE = "step102-field" private const val PARTICLE_LAYER = "step102-particle" private const val UPPER_LAYER = "step102-upper" + // Built into MapLibre AnnotationManager; it is a Symbol even when the fixture has no symbols. + private const val INTERNAL_ANNOTATION_LAYER = "org.maplibre.annotations.points" fun styleJson(scenario: Scenario): String { val sources = JSONObject() @@ -56,7 +64,8 @@ internal object Step102PlacementFixture { .put("segments", JSONArray() .put(JSONObject().put("strategy", "RETAINED")) .put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", PARTICLE_LAYER)) - .put(JSONObject().put("strategy", "RETAINED").put("startLayerId", UPPER_LAYER))) + .put(JSONObject().put("strategy", "RETAINED").put("startLayerId", UPPER_LAYER)) + .put(JSONObject().put("strategy", "CONTINUOUS").put("startLayerId", INTERNAL_ANNOTATION_LAYER))) .toString() private fun geoJsonSource(geometryJson: String): JSONObject = JSONObject().put("type", "geojson")