fix: complete STEP-047 playback validation controls
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-04 07:39:01 +08:00
parent 49b2e8aebd
commit 0609199cce
6 changed files with 110 additions and 27 deletions

View File

@@ -34,6 +34,9 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private var timeWindowMode = false
private var styleLoadEpoch = 0L
private var styleReady = false
// Display mirror only; Core remains the playback state authority.
private var timelineState = "READY"
private var timelineSpeed = "1x"
override fun onCreate(savedInstanceState: Bundle?) {
active = savedInstanceState?.getBoolean(STATE_ACTIVE, true) ?: true
@@ -111,6 +114,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
timeWindowMode = true
fixtureMode = FIXTURE_UNIFORM_EAST
timeMix = 0.0f
timelineState = "READY"
if (this::maplibreMap.isInitialized) loadValidationStyle(active, enabled, "TIME_WINDOW")
}
findViewById<Button>(R.id.validationMix0).setOnClickListener { setTimeMix(0.0f) }
@@ -130,16 +134,16 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
if (this::maplibreMap.isInitialized) duplicateRuntimeFieldWindow()
}
findViewById<Button>(R.id.validationVisualDefault).setOnClickListener { resetVisualDefaults() }
findViewById<Button>(R.id.validationVisualSizeDown).setOnClickListener { setVisualFloat("raster-particle-line-width", lineWidth - 0.5f) { lineWidth = it.coerceIn(1.0f, 8.0f) } }
findViewById<Button>(R.id.validationVisualSizeUp).setOnClickListener { setVisualFloat("raster-particle-line-width", lineWidth + 0.5f) { lineWidth = it.coerceIn(1.0f, 8.0f) } }
findViewById<Button>(R.id.validationVisualLengthDown).setOnClickListener { setVisualFloat("raster-particle-particle-length", particleLength - 0.004f) { particleLength = it.coerceIn(0.002f, 0.080f) } }
findViewById<Button>(R.id.validationVisualLengthUp).setOnClickListener { setVisualFloat("raster-particle-particle-length", particleLength + 0.004f) { particleLength = it.coerceIn(0.002f, 0.080f) } }
findViewById<Button>(R.id.validationVisualOpacityDown).setOnClickListener { setVisualFloat("raster-particle-particle-opacity", particleOpacity - 0.1f) { particleOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualOpacityUp).setOnClickListener { setVisualFloat("raster-particle-particle-opacity", particleOpacity + 0.1f) { particleOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualTrailDown).setOnClickListener { setVisualFloat("raster-particle-trail-opacity", trailOpacity - 0.1f) { trailOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualTrailUp).setOnClickListener { setVisualFloat("raster-particle-trail-opacity", trailOpacity + 0.1f) { trailOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualFadeDown).setOnClickListener { setVisualFloat("raster-particle-trail-fade", trailFade - 0.05f) { trailFade = it.coerceIn(0.0f, 0.995f) } }
findViewById<Button>(R.id.validationVisualFadeUp).setOnClickListener { setVisualFloat("raster-particle-trail-fade", trailFade + 0.05f) { trailFade = it.coerceIn(0.0f, 0.995f) } }
findViewById<Button>(R.id.validationVisualSizeDown).setOnClickListener { setVisualFloat("raster-particle-line-width", lineWidth - 1.0f) { lineWidth = it.coerceIn(1.0f, 8.0f) } }
findViewById<Button>(R.id.validationVisualSizeUp).setOnClickListener { setVisualFloat("raster-particle-line-width", lineWidth + 1.0f) { lineWidth = it.coerceIn(1.0f, 8.0f) } }
findViewById<Button>(R.id.validationVisualLengthDown).setOnClickListener { setVisualFloat("raster-particle-particle-length", particleLength - 0.012f) { particleLength = it.coerceIn(0.002f, 0.080f) } }
findViewById<Button>(R.id.validationVisualLengthUp).setOnClickListener { setVisualFloat("raster-particle-particle-length", particleLength + 0.012f) { particleLength = it.coerceIn(0.002f, 0.080f) } }
findViewById<Button>(R.id.validationVisualOpacityDown).setOnClickListener { setVisualFloat("raster-particle-particle-opacity", particleOpacity - 0.25f) { particleOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualOpacityUp).setOnClickListener { setVisualFloat("raster-particle-particle-opacity", particleOpacity + 0.25f) { particleOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualTrailDown).setOnClickListener { setVisualFloat("raster-particle-trail-opacity", trailOpacity - 0.25f) { trailOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualTrailUp).setOnClickListener { setVisualFloat("raster-particle-trail-opacity", trailOpacity + 0.25f) { trailOpacity = it.coerceIn(0.0f, 1.0f) } }
findViewById<Button>(R.id.validationVisualFadeDown).setOnClickListener { setVisualFloat("raster-particle-trail-fade", trailFade - 0.12f) { trailFade = it.coerceIn(0.0f, 0.995f) } }
findViewById<Button>(R.id.validationVisualFadeUp).setOnClickListener { setVisualFloat("raster-particle-trail-fade", trailFade + 0.12f) { trailFade = it.coerceIn(0.0f, 0.995f) } }
findViewById<Button>(R.id.validationVisualColor).setOnClickListener { setSpeedColor(!speedColorEnabled) }
findViewById<Button>(R.id.validationDateline).setOnClickListener {
maplibreMap.cameraPosition = CameraPosition.Builder().target(LatLng(0.0, 179.0)).zoom(2.0).build()
@@ -150,7 +154,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
Log.i(TAG, "ParticleValidation30_03: WORLD_WRAP preset center=0,0 zoom=0")
}
findViewById<Button>(R.id.validationMenuTime).setOnClickListener { showTimeMenu(it) }
findViewById<Button>(R.id.validationMenuVisual).setOnClickListener { showMenu(it, "VIS", intArrayOf(R.id.validationVisualDefault, 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, R.id.validationVisualColor)) }
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.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.validationAdvanceWindow, R.id.validationDuplicateWindow)) }
@@ -166,7 +170,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
private fun showTimeMenu(anchor: android.view.View) {
PopupMenu(this, anchor).apply {
val labels = arrayOf("LOAD TIMELINE", "PLAY", "PAUSE", "SPEED 0.5x", "SPEED 1x", "SPEED 2x", "STEP PREV", "STEP NEXT", "SEEK START", "SEEK MID", "SEEK END", "INJECT GAP", "INJECT INVALID", "RECOVER")
val labels = arrayOf("SCENARIO NORMAL", "PLAY / RESTART", "PAUSE", "SPEED 0.5x", "SPEED 1x", "SPEED 2x", "STEP PREV", "STEP NEXT", "SEEK START", "SEEK MID", "SEEK END", "SCENARIO GAP", "SCENARIO INVALID NEXT", "RECOVER NEXT")
labels.forEachIndexed { index, label -> menu.add(0, index, index, label) }
setOnMenuItemClickListener { item ->
when (item.itemId) {
@@ -194,7 +198,12 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
if (!styleReady || !enabled || !timeWindowMode) { Log.i(TAG, "ParticleTimeline: event=REJECTED reason=STYLE_NOT_READY"); return }
if (!maplibreMap.controlRasterArraySourceTimeline(PARTICLE_SOURCE_ID, command, value)) {
Log.i(TAG, "ParticleTimeline: event=REJECTED command=$command")
} else Log.i(TAG, "ParticleTimeline: event=COMMAND command=$command value=$value")
} else {
timelineState = when (command) { 0 -> "PLAYING"; 1 -> "PAUSED"; 6, 7 -> "WAITING"; 8 -> "PAUSED"; else -> timelineState }
if (command == 2) timelineSpeed = "${value}x"
status.text = statusText()
Log.i(TAG, "ParticleTimeline: event=COMMAND command=$command value=$value")
}
}
private fun actionLabel(id: Int): String = when (id) {
@@ -371,7 +380,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
} else {
"EAST"
}
return "$mask · $runtime · $fields · MIX ${(timeMix * 100).toInt()}% · $particleCount · W ${"%.1f".format(lineWidth)} L ${"%.3f".format(particleLength)} O ${(particleOpacity * 100).toInt()} T ${(trailOpacity * 100).toInt()} F ${(trailFade * 100).toInt()} C ${if (speedColorEnabled) "ON" else "OFF"}"
val playback = if (timeWindowMode) " · $timelineState · $timelineSpeed" else ""
return "$mask · $runtime · $fields$playback · MIX ${(timeMix * 100).toInt()}% · $particleCount · W ${"%.1f".format(lineWidth)} L ${"%.3f".format(particleLength)} O ${(particleOpacity * 100).toInt()} T ${(trailOpacity * 100).toInt()} F ${(trailFade * 100).toInt()} C ${if (speedColorEnabled) "ON" else "OFF"}"
}
private fun validationStyle(active: Boolean, enabled: Boolean): String {