feat: add STEP-093 runtime validation tooling

This commit is contained in:
tei
2026-09-24 14:39:39 +09:00
parent 8a5d88008c
commit b3ec8edc87
20 changed files with 765 additions and 24 deletions

View File

@@ -729,7 +729,7 @@ jni::jboolean NativeMapView::setContinuousLayerRange(JNIEnv& env,
jni::Local<jni::Array<jni::String>> NativeMapView::getRetainedUnderlayDiagnostics(JNIEnv& env) {
const auto snapshot = map->getRetainedUnderlayDiagnostics();
const std::array<std::string, 20> values = {
const std::array<std::string, 39> values = {
snapshot.enabled ? "true" : "false",
std::to_string(snapshot.build),
std::to_string(snapshot.reuse),
@@ -742,14 +742,33 @@ jni::Local<jni::Array<jni::String>> NativeMapView::getRetainedUnderlayDiagnostic
snapshot.endLayerId,
std::to_string(snapshot.startIndex),
std::to_string(snapshot.endIndex),
std::to_string(snapshot.continuousBegin),
std::to_string(snapshot.continuousEndExclusive),
snapshot.bottomPresent ? "true" : "false",
snapshot.bottomCacheState,
std::to_string(snapshot.bottomBuild),
std::to_string(snapshot.bottomReuse),
std::to_string(snapshot.bottomInvalidation),
snapshot.bottomLastInvalidationReason,
std::to_string(snapshot.bottomTextureCreate),
std::to_string(snapshot.bottomCompositeCreate),
std::to_string(snapshot.bottomResourceRelease),
snapshot.topPresent ? "true" : "false",
snapshot.topCacheState,
std::to_string(snapshot.topBuild),
std::to_string(snapshot.topReuse),
std::to_string(snapshot.topInvalidation),
snapshot.topLastInvalidationReason,
std::to_string(snapshot.topTextureCreate),
std::to_string(snapshot.topCompositeCreate),
std::to_string(snapshot.topResourceRelease),
snapshot.rangeFallback ? "true" : "false",
snapshot.fastPathEligible ? "true" : "false",
snapshot.fastPathActive ? "true" : "false",
snapshot.fastPathFallbackReason,
std::to_string(snapshot.fastPathHit),
std::to_string(snapshot.fastPathMiss),
std::to_string(snapshot.fullCpuFrame),
};
auto result = jni::Array<jni::String>::New(env, values.size());
for (std::size_t index = 0; index < values.size(); ++index) {

View File

@@ -18,18 +18,37 @@ public final class RetainedUnderlayDiagnostics {
@NonNull public final String endLayerId;
public final int startIndex;
public final int endIndex;
public final int continuousBegin;
public final int continuousEndExclusive;
public final boolean bottomPresent;
@NonNull public final String bottomCacheState;
public final long bottomBuild;
public final long bottomReuse;
public final long bottomInvalidation;
@NonNull public final String bottomLastInvalidationReason;
public final long bottomTextureCreate;
public final long bottomCompositeCreate;
public final long bottomResourceRelease;
public final boolean topPresent;
@NonNull public final String topCacheState;
public final long topBuild;
public final long topReuse;
public final long topInvalidation;
@NonNull public final String topLastInvalidationReason;
public final long topTextureCreate;
public final long topCompositeCreate;
public final long topResourceRelease;
public final boolean rangeFallback;
public final boolean fastPathEligible;
public final boolean fastPathActive;
@NonNull public final String fastPathFallbackReason;
public final long fastPathHit;
public final long fastPathMiss;
public final long fullCpuFrame;
RetainedUnderlayDiagnostics(@NonNull String[] values) {
if (values.length != 20) {
throw new IllegalArgumentException("Retained-range diagnostics require 20 values");
if (values.length != 39) {
throw new IllegalArgumentException("Retained-range diagnostics require 39 values");
}
enabled = Boolean.parseBoolean(values[0]);
build = Long.parseLong(values[1]);
@@ -43,20 +62,41 @@ public final class RetainedUnderlayDiagnostics {
endLayerId = values[9];
startIndex = Integer.parseInt(values[10]);
endIndex = Integer.parseInt(values[11]);
bottomPresent = Boolean.parseBoolean(values[12]);
bottomBuild = Long.parseLong(values[13]);
bottomReuse = Long.parseLong(values[14]);
bottomInvalidation = Long.parseLong(values[15]);
topPresent = Boolean.parseBoolean(values[16]);
topBuild = Long.parseLong(values[17]);
topReuse = Long.parseLong(values[18]);
topInvalidation = Long.parseLong(values[19]);
continuousBegin = Integer.parseInt(values[12]);
continuousEndExclusive = Integer.parseInt(values[13]);
bottomPresent = Boolean.parseBoolean(values[14]);
bottomCacheState = values[15];
bottomBuild = Long.parseLong(values[16]);
bottomReuse = Long.parseLong(values[17]);
bottomInvalidation = Long.parseLong(values[18]);
bottomLastInvalidationReason = values[19];
bottomTextureCreate = Long.parseLong(values[20]);
bottomCompositeCreate = Long.parseLong(values[21]);
bottomResourceRelease = Long.parseLong(values[22]);
topPresent = Boolean.parseBoolean(values[23]);
topCacheState = values[24];
topBuild = Long.parseLong(values[25]);
topReuse = Long.parseLong(values[26]);
topInvalidation = Long.parseLong(values[27]);
topLastInvalidationReason = values[28];
topTextureCreate = Long.parseLong(values[29]);
topCompositeCreate = Long.parseLong(values[30]);
topResourceRelease = Long.parseLong(values[31]);
rangeFallback = Boolean.parseBoolean(values[32]);
fastPathEligible = Boolean.parseBoolean(values[33]);
fastPathActive = Boolean.parseBoolean(values[34]);
fastPathFallbackReason = values[35];
fastPathHit = Long.parseLong(values[36]);
fastPathMiss = Long.parseLong(values[37]);
fullCpuFrame = Long.parseLong(values[38]);
}
static RetainedUnderlayDiagnostics unavailable() {
return new RetainedUnderlayDiagnostics(new String[] {
"true", "0", "0", "0", "0", "unavailable", "false", "false", "", "", "-1", "-1",
"false", "0", "0", "0", "false", "0", "0", "0"
"-1", "-1", "false", "disabled", "0", "0", "0", "none", "0", "0", "0",
"false", "disabled", "0", "0", "0", "none", "0", "0", "0", "false", "false",
"false", "no-prepared-tree", "0", "0", "0"
});
}
}

View File

@@ -288,6 +288,7 @@
</activity>
<activity
android:name=".activity.particle.ParticleCoreLayerValidationActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:description="@string/description_particle_core_layer_validation"
android:exported="true"
android:label="@string/activity_particle_core_layer_validation">

View File

@@ -2,6 +2,8 @@ package org.maplibre.android.testapp.activity.particle
import android.graphics.Color
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.InputType
import android.util.Log
import android.widget.Button
@@ -28,6 +30,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private lateinit var mapView: MapView
private lateinit var maplibreMap: MapLibreMap
private lateinit var status: TextView
private lateinit var diagnostics: TextView
private var active = true
private var enabled = true
private var maskMode = MASK_GLOBAL
@@ -51,6 +54,21 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private var navSeaPbfRenderEnabled = true
private var navSeaPbfLayerVisibility = emptyList<NavSeaPbfLayerVisibility>()
private var retainedCacheEnabled = true
private var step093Mode = false
private var step093Topology = Step093Topology.BOTTOM_CONTINUOUS_TOP
private var step093Variant = Step093Variant.DEFAULT
private var step093StartVisible = true
private var step093EndVisible = true
private var step093CameraPreset = 0
private val step093Handler = Handler(Looper.getMainLooper())
private val step093DiagnosticsPoll = object : Runnable {
override fun run() {
if (step093Mode && this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized) {
refreshStep093Diagnostics(log = false)
step093Handler.postDelayed(this, STEP093_DIAGNOSTICS_INTERVAL_MS)
}
}
}
private var realWeatherTuningMenu: PopupMenu? = null
private var realWeatherMenuAnchor: android.view.View? = null
private var openRealWeatherTuningAfterStyle = false
@@ -88,6 +106,13 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
?: NavSeaPbfPosition.AFTER_CLIP
navSeaPbfRenderEnabled = savedInstanceState?.getBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, true) ?: true
retainedCacheEnabled = savedInstanceState?.getBoolean(STATE_RETAINED_CACHE_ENABLED, true) ?: true
step093Mode = savedInstanceState?.getBoolean(STATE_STEP093_MODE, false) ?: false
step093Topology = savedInstanceState?.getString(STATE_STEP093_TOPOLOGY)
?.let { value -> Step093Topology.values().firstOrNull { it.name == value } }
?: Step093Topology.BOTTOM_CONTINUOUS_TOP
step093Variant = savedInstanceState?.getString(STATE_STEP093_VARIANT)
?.let { value -> Step093Variant.values().firstOrNull { it.name == value } }
?: Step093Variant.DEFAULT
super.onCreate(savedInstanceState)
Log.i(
TAG,
@@ -98,6 +123,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
mapView = findViewById(R.id.mapView)
status = findViewById(R.id.validationStatus)
diagnostics = findViewById(R.id.validationDiagnostics)
mapView.addOnDidFailLoadingMapListener { errorMessage ->
if (navSeaPbfMode || purePbfMode) {
Log.e(TAG, "NAVSEA_PBF_STYLE_ERROR reason=$errorMessage")
@@ -119,7 +145,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
updateNavSeaPbfButtonState()
updatePbfModeButtonState()
updateRetainedCacheButtonState()
loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
if (step093Mode) loadStep093Style(configureBeforeStyle = false)
else loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
}
findViewById<Button>(R.id.validationActive).setOnClickListener {
@@ -251,6 +278,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
findViewById<Button>(R.id.validationMenuNavSeaPbf).setOnClickListener { showNavSeaPbfMenu(it) }
findViewById<Button>(R.id.validationMenuPbfMode).setOnClickListener { showPbfModeMenu(it) }
findViewById<Button>(R.id.validationMenuRetainedCache).setOnClickListener { showRetainedCacheMenu(it) }
findViewById<Button>(R.id.validationMenuStep093).setOnClickListener { showStep093Menu(it) }
findViewById<Button>(R.id.validationMenuMore).setOnClickListener { dismissRealWeatherTuning(); 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)) }
}
@@ -264,8 +292,32 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
TOP("TOP")
}
private enum class Step093Topology(val label: String, val bottom: Boolean, val top: Boolean) {
BOTTOM_CONTINUOUS_TOP("A BOTTOM + CONTINUOUS + TOP", true, true),
BOTTOM_CONTINUOUS("B BOTTOM + CONTINUOUS", true, false),
CONTINUOUS_TOP("C CONTINUOUS + TOP", false, true),
CONTINUOUS_ONLY("D CONTINUOUS ONLY", false, false),
SINGLE_LAYER("E SINGLE-LAYER CONTINUOUS", true, true)
}
private enum class Step093Variant {
DEFAULT,
CONTINUOUS_ADD,
CONTINUOUS_REORDER,
BOTTOM_ADD,
BOTTOM_REORDER,
TOP_ADD,
TOP_REORDER,
CROSS_BOTTOM,
CROSS_CONTINUOUS,
CROSS_TOP,
START_MISSING,
END_MISSING,
FAST_PATH_FAIL_CLOSED
}
private fun activateRealWeather(anchor: android.view.View?) {
if (isPbfMatrixMode()) leaveRealWeatherMode()
if (isPbfMatrixMode() || step093Mode) leaveRealWeatherMode()
realWeatherMenuAnchor = anchor ?: findViewById(R.id.validationMenuRealWeather)
if (realWeatherMode) {
if (realWeatherTuningMenu == null) showRealWeatherTuning(realWeatherMenuAnchor!!) else dismissRealWeatherTuning()
@@ -296,6 +348,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun leaveRealWeatherMode() {
step093Mode = false
step093Handler.removeCallbacks(step093DiagnosticsPoll)
dismissRealWeatherTuning()
weatherRuntime?.dispose()
weatherRuntime = null
@@ -381,6 +435,233 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
)
}
private fun showStep093Menu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
Step093Topology.values().forEachIndexed { index, topology ->
menu.add(0, index, index, topology.label)
}
menu.add(0, 10, 10, "MUTATION / FAST PATH")
menu.add(0, 11, 11, "ENDPOINT / VISIBILITY")
menu.add(0, 12, 12, "INVALID RANGE")
menu.add(0, 13, 13, "CAMERA NEXT")
menu.add(0, 14, 14, "READ DIAGNOSTICS")
menu.add(0, 15, 15, "RESET DEFAULT A")
setOnMenuItemClickListener { item ->
when (item.itemId) {
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
10 -> showStep093MutationMenu(anchor)
11 -> showStep093EndpointMenu(anchor)
12 -> showStep093InvalidMenu(anchor)
13 -> applyStep093CameraPreset()
14 -> refreshStep093Diagnostics(log = true)
15 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
}
true
}
show()
}
}
private fun showStep093MutationMenu(anchor: android.view.View) {
val actions = arrayOf(
"CONTINUOUS ADD", "CONTINUOUS REORDER", "BOTTOM ADD", "BOTTOM REORDER",
"TOP ADD", "TOP REORDER", "CROSS → BOTTOM", "CROSS → CONTINUOUS",
"CROSS → TOP", "FAST FAIL-CLOSED", "RESTORE DEFAULT"
)
PopupMenu(this, anchor).apply {
actions.forEachIndexed { index, label -> menu.add(0, index, index, label) }
setOnMenuItemClickListener { item ->
val variant = when (item.itemId) {
0 -> Step093Variant.CONTINUOUS_ADD
1 -> Step093Variant.CONTINUOUS_REORDER
2 -> Step093Variant.BOTTOM_ADD
3 -> Step093Variant.BOTTOM_REORDER
4 -> Step093Variant.TOP_ADD
5 -> Step093Variant.TOP_REORDER
6 -> Step093Variant.CROSS_BOTTOM
7 -> Step093Variant.CROSS_CONTINUOUS
8 -> Step093Variant.CROSS_TOP
9 -> Step093Variant.FAST_PATH_FAIL_CLOSED
else -> Step093Variant.DEFAULT
}
applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP, variant)
true
}
show()
}
}
private fun showStep093EndpointMenu(anchor: android.view.View) {
val actions = arrayOf(
"START VISIBLE ↔ HIDDEN", "END VISIBLE ↔ HIDDEN", "DELETE START", "RESTORE START",
"DELETE END", "RESTORE END"
)
PopupMenu(this, anchor).apply {
actions.forEachIndexed { index, label -> menu.add(0, index, index, label) }
setOnMenuItemClickListener { item ->
when (item.itemId) {
0 -> toggleStep093Endpoint(start = true)
1 -> toggleStep093Endpoint(start = false)
2 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP, Step093Variant.START_MISSING)
3 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
4 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP, Step093Variant.END_MISSING)
5 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
}
true
}
show()
}
}
private fun showStep093InvalidMenu(anchor: android.view.View) {
val actions = arrayOf(
"MISSING START", "MISSING END", "REVERSED", "CONFIGURE WHILE STYLE LOADING", "EMPTY ID", "RESTORE VALID"
)
PopupMenu(this, anchor).apply {
actions.forEachIndexed { index, label -> menu.add(0, index, index, label) }
setOnMenuItemClickListener { item ->
if (!ensureStep093Scene()) {
Log.i(TAG, "STEP093_ACTION scene initialized; repeat invalid-range action after READY")
return@setOnMenuItemClickListener true
}
when (item.itemId) {
0 -> setStep093Range("step093-missing-start", STEP093_PARTICLE_LAYER_ID, "INVALID_MISSING_START")
1 -> setStep093Range(STEP093_WEATHER_LAYER_ID, "step093-missing-end", "INVALID_MISSING_END")
2 -> setStep093Range(STEP093_PARTICLE_LAYER_ID, STEP093_WEATHER_LAYER_ID, "INVALID_REVERSED")
3 -> applyStep093Scene(
Step093Topology.BOTTOM_CONTINUOUS_TOP,
Step093Variant.DEFAULT,
configureBeforeStyle = true
)
4 -> {
val accepted = maplibreMap.setContinuousLayerRange("", STEP093_PARTICLE_LAYER_ID)
Log.i(TAG, "STEP093_RANGE action=EMPTY_ID accepted=$accepted expected=false")
refreshStep093Diagnostics(log = true)
}
5 -> setStep093Range(STEP093_WEATHER_LAYER_ID, STEP093_PARTICLE_LAYER_ID, "RESTORE_VALID")
}
true
}
show()
}
}
private fun ensureStep093Scene(): Boolean {
if (step093Mode && styleReady) return true
applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
return false
}
private fun applyStep093Scene(
topology: Step093Topology,
variant: Step093Variant = Step093Variant.DEFAULT,
configureBeforeStyle: Boolean = false
) {
leaveRealWeatherMode()
step093Mode = true
step093Topology = topology
step093Variant = variant
step093StartVisible = true
step093EndVisible = true
timeWindowMode = true
productionFeedMode = false
hostSubmissionMode = true
realWeatherMode = false
pureParticleMode = false
purePbfMode = false
navSeaPbfMode = false
active = true
enabled = true
retainedCacheEnabled = true
maplibreMap.setRetainedUnderlayEnabled(true)
updateRetainedCacheButtonState()
loadStep093Style(configureBeforeStyle)
}
private fun loadStep093Style(configureBeforeStyle: Boolean) {
weatherRuntime?.dispose()
weatherRuntime = null
styleReady = false
val epoch = ++styleLoadEpoch
val range = step093Range()
status.text = "STEP-093 · ${step093Topology.label} · ${step093Variant.name} · LOADING"
maplibreMap.setStyle(Style.Builder().fromJson(step093StyleJson())) {
if (!step093Mode || epoch != styleLoadEpoch) return@setStyle
styleReady = true
if (!configureBeforeStyle) maplibreMap.setContinuousLayerRange(range.first, range.second)
submitHostValidationDataset()
fitRealWeatherBounds()
status.text = "STEP-093 · ${step093Topology.label} · ${step093Variant.name}"
startStep093DiagnosticsPolling()
refreshStep093Diagnostics(log = true)
Log.i(
TAG,
"STEP093_SCENE_READY topology=${step093Topology.name} variant=${step093Variant.name} " +
"range=${range.first}..${range.second}"
)
}
if (configureBeforeStyle) {
maplibreMap.setContinuousLayerRange(range.first, range.second)
Log.i(TAG, "STEP093_RANGE action=CONFIGURE_WHILE_STYLE_LOADING start=${range.first} end=${range.second}")
refreshStep093Diagnostics(log = true)
}
}
private fun step093Range(): Pair<String, String> = if (step093Topology == Step093Topology.SINGLE_LAYER) {
STEP093_PARTICLE_LAYER_ID to STEP093_PARTICLE_LAYER_ID
} else {
STEP093_WEATHER_LAYER_ID to STEP093_PARTICLE_LAYER_ID
}
private fun setStep093Range(start: String, end: String, action: String) {
val accepted = maplibreMap.setContinuousLayerRange(start, end)
Log.i(TAG, "STEP093_RANGE action=$action accepted=$accepted start=$start end=$end")
refreshStep093Diagnostics(log = true)
}
private fun toggleStep093Endpoint(start: Boolean) {
if (!ensureStep093Scene()) return
val id = if (start) step093Range().first else step093Range().second
val visible = if (start) !step093StartVisible else !step093EndVisible
val accepted = maplibreMap.setStyleLayerVisibility(id, visible)
if (start) step093StartVisible = visible else step093EndVisible = visible
Log.i(TAG, "STEP093_VISIBILITY endpoint=${if (start) "start" else "end"} id=$id visible=$visible accepted=$accepted")
refreshStep093Diagnostics(log = true)
}
private fun applyStep093CameraPreset() {
if (!ensureStep093Scene()) return
step093CameraPreset = (step093CameraPreset + 1) % 4
val position = when (step093CameraPreset) {
1 -> CameraPosition.Builder().target(LatLng(31.0, 139.0)).zoom(3.5).bearing(0.0).tilt(0.0).build()
2 -> CameraPosition.Builder().target(LatLng(25.0, 132.0)).zoom(4.5).bearing(55.0).tilt(0.0).build()
3 -> CameraPosition.Builder().target(LatLng(37.0, 145.0)).zoom(5.0).bearing(120.0).tilt(45.0).build()
else -> CameraPosition.Builder().target(LatLng(28.0, 135.0)).zoom(3.0).bearing(0.0).tilt(0.0).build()
}
maplibreMap.animateCamera(CameraUpdateFactory.newCameraPosition(position), 700)
Log.i(TAG, "STEP093_CAMERA preset=$step093CameraPreset position=$position")
}
private fun startStep093DiagnosticsPolling() {
step093Handler.removeCallbacks(step093DiagnosticsPoll)
step093Handler.post(step093DiagnosticsPoll)
}
private fun refreshStep093Diagnostics(log: Boolean) {
val snapshot = maplibreMap.retainedUnderlayDiagnostics
val cacheCount = (if (snapshot.bottomPresent) 1 else 0) + (if (snapshot.topPresent) 1 else 0)
diagnostics.text = buildString {
append("Range cfg=${snapshot.rangeConfigured} valid=${snapshot.rangeValid} fallback=${snapshot.rangeFallback} reason=${snapshot.lastReason}\n")
append("${snapshot.startLayerId}[${snapshot.startIndex}]..${snapshot.endLayerId}[${snapshot.endIndex}] C=[${snapshot.continuousBegin},${snapshot.continuousEndExclusive}) caches=$cacheCount\n")
append("B ${snapshot.bottomPresent}/${snapshot.bottomCacheState} b/r/i=${snapshot.bottomBuild}/${snapshot.bottomReuse}/${snapshot.bottomInvalidation} res=${snapshot.bottomTextureCreate}/${snapshot.bottomCompositeCreate}/${snapshot.bottomResourceRelease} ${snapshot.bottomLastInvalidationReason}\n")
append("T ${snapshot.topPresent}/${snapshot.topCacheState} b/r/i=${snapshot.topBuild}/${snapshot.topReuse}/${snapshot.topInvalidation} res=${snapshot.topTextureCreate}/${snapshot.topCompositeCreate}/${snapshot.topResourceRelease} ${snapshot.topLastInvalidationReason}\n")
append("Fast e/a=${snapshot.fastPathEligible}/${snapshot.fastPathActive} h/m/full=${snapshot.fastPathHit}/${snapshot.fastPathMiss}/${snapshot.fullCpuFrame} ${snapshot.fastPathFallbackReason}")
}
if (log) {
Log.i(TAG, "STEP093_DIAGNOSTICS ${diagnostics.text.toString().replace('\n', ' ')}")
}
}
private fun dismissRealWeatherTuning() { realWeatherTuningMenu?.dismiss(); realWeatherTuningMenu = null }
private fun showRealWeatherTuning(anchor: android.view.View) {
@@ -564,7 +845,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun enterPbfMatrix() {
if (realWeatherMode) leaveRealWeatherMode()
if (realWeatherMode || step093Mode) leaveRealWeatherMode()
if (!isPbfMatrixMode()) {
preRealWeatherVisuals = ParticleVisuals(particleCount, lineWidth, particleLength, particleOpacity, trailOpacity, trailFade)
particleCount = REAL_WEATHER_PARTICLE_COUNT
@@ -1277,6 +1558,10 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
}
private fun statusText(): String {
if (step093Mode) {
return "STEP-093 · ${step093Topology.label} · ${step093Variant.name} · " +
"CACHE ${if (retainedCacheEnabled) "ON" else "OFF"}"
}
val mask = when (maskMode) {
MASK_OCEAN_A -> "OCEAN A"
MASK_OCEAN_B -> "OCEAN B"
@@ -1318,6 +1603,94 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private fun realWeatherHudValues(): String = "FPS ${realWeatherTargetFps.name.removePrefix("FPS_")} · P $particleCount · W ${"%.2f".format(lineWidth)} · L ${"%.4f".format(particleLength)} · O ${"%.2f".format(particleOpacity)} · T ${"%.2f".format(trailOpacity)} · F ${"%.2f".format(trailFade)}"
private fun step093StyleJson(): String {
val sources = JSONObject()
.put(PARTICLE_SOURCE_ID, JSONObject().put("type", "raster-array").put("host-field-submission", true))
.put("step093-bottom-shape", step093GeoJsonSource("""{"type":"Polygon","coordinates":[[[112,0],[158,0],[158,56],[112,56],[112,0]]]}"""))
.put("step093-semi-shape", step093GeoJsonSource("""{"type":"Polygon","coordinates":[[[134,8],[151,8],[151,49],[134,49],[134,8]]]}"""))
.put("step093-opaque-shape", step093GeoJsonSource("""{"type":"Polygon","coordinates":[[[124,18],[136,18],[136,42],[124,42],[124,18]]]}"""))
.put("step093-line-shape", step093GeoJsonSource("""{"type":"LineString","coordinates":[[115,6],[154,50],[154,6],[115,50]]}"""))
fun fill(id: String, source: String, color: String, opacity: Double) = JSONObject()
.put("id", id).put("type", "fill").put("source", source)
.put("paint", JSONObject().put("fill-color", color).put("fill-opacity", opacity))
fun line(id: String, source: String, color: String, width: Double) = JSONObject()
.put("id", id).put("type", "line").put("source", source)
.put("paint", JSONObject().put("line-color", color).put("line-width", width).put("line-opacity", 0.95))
val layers = JSONArray()
if (step093Topology.bottom) {
layers.put(JSONObject().put("id", STEP093_BOTTOM_BACKGROUND_ID).put("type", "background")
.put("paint", JSONObject().put("background-color", "#0b2942")))
val bottomBase = fill(STEP093_BOTTOM_FILL_ID, "step093-bottom-shape", "#176b87", 0.72)
val bottomExtra = fill(STEP093_BOTTOM_EXTRA_ID, "step093-semi-shape", "#2aa198", 0.18)
if (step093Variant == Step093Variant.BOTTOM_REORDER) {
layers.put(bottomExtra).put(bottomBase)
} else {
layers.put(bottomBase)
if (step093Variant == Step093Variant.BOTTOM_ADD) layers.put(bottomExtra)
}
if (step093Variant == Step093Variant.CROSS_BOTTOM) {
layers.put(fill(STEP093_CROSS_LAYER_ID, "step093-opaque-shape", "#9b59b6", 0.18))
}
}
if (step093Variant != Step093Variant.START_MISSING) {
layers.put(
JSONObject().put("id", STEP093_WEATHER_LAYER_ID).put("type", "weather-color")
.put("source", PARTICLE_SOURCE_ID)
.put("paint", JSONObject().put("weather-color-opacity", 0.32))
)
}
if (step093Variant == Step093Variant.CONTINUOUS_ADD ||
step093Variant == Step093Variant.CONTINUOUS_REORDER) {
val first = fill(STEP093_CONTINUOUS_A_ID, "step093-semi-shape", "#ff00ff", 0.16)
val second = fill(STEP093_CONTINUOUS_B_ID, "step093-opaque-shape", "#00ffff", 0.12)
if (step093Variant == Step093Variant.CONTINUOUS_REORDER) layers.put(second).put(first)
else layers.put(first).put(second)
}
if (step093Variant == Step093Variant.CROSS_CONTINUOUS) {
layers.put(fill(STEP093_CROSS_LAYER_ID, "step093-opaque-shape", "#9b59b6", 0.18))
}
if (step093Variant == Step093Variant.FAST_PATH_FAIL_CLOSED) {
layers.put(fill(STEP093_UNKNOWN_CONTINUOUS_ID, "step093-semi-shape", "#ff5500", 0.20))
}
if (step093Variant != Step093Variant.END_MISSING) layers.put(navSeaParticleLayer(active = true))
if (step093Variant == Step093Variant.CROSS_TOP) {
layers.put(fill(STEP093_CROSS_LAYER_ID, "step093-opaque-shape", "#9b59b6", 0.18))
}
if (step093Topology.top) {
val semi = fill(STEP093_TOP_SEMI_ID, "step093-semi-shape", "#ffd166", 0.42)
val opaque = fill(STEP093_TOP_OPAQUE_ID, "step093-opaque-shape", "#ef476f", 1.0)
val outline = line(STEP093_TOP_LINE_ID, "step093-line-shape", "#ffffff", 6.0)
val extra = line(STEP093_TOP_EXTRA_ID, "step093-opaque-shape", "#00ff99", 3.0)
if (step093Variant == Step093Variant.TOP_REORDER) {
layers.put(outline).put(opaque).put(semi)
} else {
layers.put(semi).put(opaque).put(outline)
if (step093Variant == Step093Variant.TOP_ADD) layers.put(extra)
}
}
return JSONObject()
.put("version", 8)
.put("name", "STEP-093 Continuous Range Runtime Validation")
.put("sources", sources)
.put("layers", layers)
.toString()
}
private fun step093GeoJsonSource(geometryJson: String): JSONObject = JSONObject()
.put("type", "geojson")
.put(
"data",
JSONObject()
.put("type", "Feature")
.put("properties", JSONObject())
.put("geometry", JSONObject(geometryJson))
)
private fun validationStyle(active: Boolean, enabled: Boolean, reason: String): String {
val fixtureId = fixtureId()
val nextFixtureId = if (timeWindowMode) {
@@ -1396,11 +1769,11 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
""".trimIndent()
}
override fun onStart() { super.onStart(); mapView.onStart() }
override fun onStart() { super.onStart(); mapView.onStart(); if (step093Mode) startStep093DiagnosticsPolling() }
override fun onResume() { super.onResume(); mapView.onResume() }
override fun onPause() { mapView.onPause(); super.onPause() }
override fun onStop() { mapView.onStop(); super.onStop() }
override fun onDestroy() { weatherRuntime?.dispose(); weatherRuntime = null; mapView.onDestroy(); super.onDestroy() }
override fun onStop() { step093Handler.removeCallbacks(step093DiagnosticsPoll); mapView.onStop(); super.onStop() }
override fun onDestroy() { step093Handler.removeCallbacks(step093DiagnosticsPoll); weatherRuntime?.dispose(); weatherRuntime = null; mapView.onDestroy(); super.onDestroy() }
override fun onLowMemory() { super.onLowMemory(); mapView.onLowMemory() }
override fun onSaveInstanceState(outState: Bundle) {
@@ -1425,6 +1798,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
outState.putString(STATE_NAVSEA_PBF_POSITION, navSeaPbfPosition.name)
outState.putBoolean(STATE_NAVSEA_PBF_RENDER_ENABLED, navSeaPbfRenderEnabled)
outState.putBoolean(STATE_RETAINED_CACHE_ENABLED, retainedCacheEnabled)
outState.putBoolean(STATE_STEP093_MODE, step093Mode)
outState.putString(STATE_STEP093_TOPOLOGY, step093Topology.name)
outState.putString(STATE_STEP093_VARIANT, step093Variant.name)
super.onSaveInstanceState(outState)
mapView.onSaveInstanceState(outState)
}
@@ -1458,9 +1834,26 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private const val STATE_NAVSEA_PBF_POSITION = "particle_validation.navsea_pbf_position"
private const val STATE_NAVSEA_PBF_RENDER_ENABLED = "particle_validation.navsea_pbf_render_enabled"
private const val STATE_RETAINED_CACHE_ENABLED = "particle_validation.retained_cache_enabled"
private const val STATE_STEP093_MODE = "particle_validation.step093_mode"
private const val STATE_STEP093_TOPOLOGY = "particle_validation.step093_topology"
private const val STATE_STEP093_VARIANT = "particle_validation.step093_variant"
private const val PARTICLE_LAYER_ID = "particle-raster-core"
private const val WEATHER_COLOR_LAYER_ID = "weather-color-real"
private const val PARTICLE_SOURCE_ID = "particle-fixture"
private const val STEP093_DIAGNOSTICS_INTERVAL_MS = 1000L
private const val STEP093_WEATHER_LAYER_ID = WEATHER_COLOR_LAYER_ID
private const val STEP093_PARTICLE_LAYER_ID = PARTICLE_LAYER_ID
private const val STEP093_BOTTOM_BACKGROUND_ID = "step093-bottom-background"
private const val STEP093_BOTTOM_FILL_ID = "step093-bottom-fill"
private const val STEP093_BOTTOM_EXTRA_ID = "step093-bottom-extra"
private const val STEP093_CONTINUOUS_A_ID = "step093-continuous-a"
private const val STEP093_CONTINUOUS_B_ID = "step093-continuous-b"
private const val STEP093_UNKNOWN_CONTINUOUS_ID = "step093-unknown-continuous"
private const val STEP093_CROSS_LAYER_ID = "step093-cross-layer"
private const val STEP093_TOP_SEMI_ID = "step093-top-semi"
private const val STEP093_TOP_OPAQUE_ID = "step093-top-opaque"
private const val STEP093_TOP_LINE_ID = "step093-top-line"
private const val STEP093_TOP_EXTRA_ID = "step093-top-extra"
private const val MASK_GLOBAL = 0
private const val MASK_OCEAN_A = 1
private const val MASK_OCEAN_B = 2

View File

@@ -3,6 +3,7 @@
<org.maplibre.android.maps.MapView android:id="@id/mapView" android:layout_width="match_parent" android:layout_height="match_parent" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="#DD102030" android:orientation="vertical" android:padding="4dp">
<TextView android:id="@+id/validationStatus" android:layout_width="match_parent" android:layout_height="42dp" android:gravity="center_vertical" android:maxLines="2" android:paddingLeft="4dp" android:paddingRight="4dp" android:textColor="@android:color/white" android:textSize="12sp" />
<TextView android:id="@+id/validationDiagnostics" android:layout_width="match_parent" android:layout_height="92dp" android:fontFamily="monospace" android:gravity="center_vertical" android:maxLines="6" android:paddingLeft="4dp" android:paddingRight="4dp" android:text="STEP-093 diagnostics: open STEP 093" android:textColor="#FFDD88" android:textSize="9sp" />
<LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal">
<Button android:id="@+id/validationMenuTime" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="TIME" />
<Button android:id="@+id/validationMenuVisual" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="VIS" />
@@ -12,6 +13,7 @@
<Button android:id="@+id/validationMenuPbfMode" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="PBF MODE" android:textSize="9sp" />
<Button android:id="@+id/validationMenuNavSeaPbf" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="NAVSEA PBF" android:textSize="9sp" />
<Button android:id="@+id/validationMenuRetainedCache" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="CACHE ON" android:textSize="9sp" />
<Button android:id="@+id/validationMenuStep093" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="STEP 093" android:textSize="9sp" />
<Button android:id="@+id/validationMenuMore" style="@style/ParticleValidationButton" android:layout_width="0dp" android:layout_weight="1" android:text="MORE" />
</LinearLayout>
<!-- Existing action IDs are invisible menu targets; no permanent button grid. -->

View File

@@ -44,14 +44,33 @@ MLN_EXPORT
@property (nonatomic, copy, readonly) NSString *endLayerId;
@property (nonatomic, readonly) NSInteger startIndex;
@property (nonatomic, readonly) NSInteger endIndex;
@property (nonatomic, readonly) NSInteger continuousBegin;
@property (nonatomic, readonly) NSInteger continuousEndExclusive;
@property (nonatomic, readonly) BOOL bottomPresent;
@property (nonatomic, copy, readonly) NSString *bottomCacheState;
@property (nonatomic, readonly) uint64_t bottomBuild;
@property (nonatomic, readonly) uint64_t bottomReuse;
@property (nonatomic, readonly) uint64_t bottomInvalidation;
@property (nonatomic, copy, readonly) NSString *bottomLastInvalidationReason;
@property (nonatomic, readonly) uint64_t bottomTextureCreate;
@property (nonatomic, readonly) uint64_t bottomCompositeCreate;
@property (nonatomic, readonly) uint64_t bottomResourceRelease;
@property (nonatomic, readonly) BOOL topPresent;
@property (nonatomic, copy, readonly) NSString *topCacheState;
@property (nonatomic, readonly) uint64_t topBuild;
@property (nonatomic, readonly) uint64_t topReuse;
@property (nonatomic, readonly) uint64_t topInvalidation;
@property (nonatomic, copy, readonly) NSString *topLastInvalidationReason;
@property (nonatomic, readonly) uint64_t topTextureCreate;
@property (nonatomic, readonly) uint64_t topCompositeCreate;
@property (nonatomic, readonly) uint64_t topResourceRelease;
@property (nonatomic, readonly) BOOL rangeFallback;
@property (nonatomic, readonly) BOOL fastPathEligible;
@property (nonatomic, readonly) BOOL fastPathActive;
@property (nonatomic, copy, readonly) NSString *fastPathFallbackReason;
@property (nonatomic, readonly) uint64_t fastPathHit;
@property (nonatomic, readonly) uint64_t fastPathMiss;
@property (nonatomic, readonly) uint64_t fullCpuFrame;
- (instancetype)init NS_UNAVAILABLE;

View File

@@ -267,6 +267,13 @@ int processIsTranslated() {
_endLayerId = @"";
_startIndex = -1;
_endIndex = -1;
_continuousBegin = -1;
_continuousEndExclusive = -1;
_bottomCacheState = @"disabled";
_bottomLastInvalidationReason = @"none";
_topCacheState = @"disabled";
_topLastInvalidationReason = @"none";
_fastPathFallbackReason = @"no-prepared-tree";
}
return self;
}
@@ -278,14 +285,33 @@ int processIsTranslated() {
_endLayerId = [NSString stringWithUTF8String:snapshot.endLayerId.c_str()];
_startIndex = snapshot.startIndex;
_endIndex = snapshot.endIndex;
_continuousBegin = snapshot.continuousBegin;
_continuousEndExclusive = snapshot.continuousEndExclusive;
_bottomPresent = snapshot.bottomPresent;
_bottomCacheState = [NSString stringWithUTF8String:snapshot.bottomCacheState.c_str()];
_bottomBuild = snapshot.bottomBuild;
_bottomReuse = snapshot.bottomReuse;
_bottomInvalidation = snapshot.bottomInvalidation;
_bottomLastInvalidationReason = [NSString stringWithUTF8String:snapshot.bottomLastInvalidationReason.c_str()];
_bottomTextureCreate = snapshot.bottomTextureCreate;
_bottomCompositeCreate = snapshot.bottomCompositeCreate;
_bottomResourceRelease = snapshot.bottomResourceRelease;
_topPresent = snapshot.topPresent;
_topCacheState = [NSString stringWithUTF8String:snapshot.topCacheState.c_str()];
_topBuild = snapshot.topBuild;
_topReuse = snapshot.topReuse;
_topInvalidation = snapshot.topInvalidation;
_topLastInvalidationReason = [NSString stringWithUTF8String:snapshot.topLastInvalidationReason.c_str()];
_topTextureCreate = snapshot.topTextureCreate;
_topCompositeCreate = snapshot.topCompositeCreate;
_topResourceRelease = snapshot.topResourceRelease;
_rangeFallback = snapshot.rangeFallback;
_fastPathEligible = snapshot.fastPathEligible;
_fastPathActive = snapshot.fastPathActive;
_fastPathFallbackReason = [NSString stringWithUTF8String:snapshot.fastPathFallbackReason.c_str()];
_fastPathHit = snapshot.fastPathHit;
_fastPathMiss = snapshot.fastPathMiss;
_fullCpuFrame = snapshot.fullCpuFrame;
}
@end