fix(step097): complete A2 clock and GPU field correctness
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -66,6 +66,7 @@ public final class WeatherRuntime {
|
||||
// This is a Host-command bookkeeping hint for scheduling only. Core still
|
||||
// decides whether a resolved V4 window may actually advance simulation.
|
||||
@Nullable private String v4ParticleBindingFieldKey;
|
||||
private boolean v4ForecastPlaying;
|
||||
private final Set<String> submittedEncodedFieldKeys = new HashSet<>();
|
||||
private boolean disposed;
|
||||
private Handler cadenceHandler;
|
||||
@@ -123,7 +124,7 @@ public final class WeatherRuntime {
|
||||
}
|
||||
public CommandResult setEncodedForecastTime(long timestamp) { requireAttached(); return timestamp < 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastTime(config.sourceId, timestamp)); }
|
||||
/** State and speed intentionally remain independent V4 Core commands. */
|
||||
public CommandResult setEncodedForecastPlaybackState(boolean playing) { requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackState(config.sourceId, playing)); }
|
||||
public CommandResult setEncodedForecastPlaybackState(boolean playing) { requireAttached(); CommandResult result = encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackState(config.sourceId, playing)); if (result == CommandResult.ACCEPTED) { v4ForecastPlaying = playing; cancelCadenceWake(); scheduleCadenceWake(); } return result; }
|
||||
public CommandResult setEncodedForecastPlaybackSpeed(float speed) { requireAttached(); return !Float.isFinite(speed) || speed <= 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastPlaybackSpeed(config.sourceId, speed)); }
|
||||
/** v2 provider-neutral ingress; missing scalar/vector is expressed only by its explicit presence flag. */
|
||||
public FieldSubmitResult submitField(@NonNull String metadataJson, @Nullable byte[] scalarBytes, @Nullable byte[] vectorBytes, @NonNull byte[] validityBytes) {
|
||||
@@ -223,7 +224,9 @@ public final class WeatherRuntime {
|
||||
private static CommandResult encodedCommand(int code) { if (code == 0) throw new IllegalStateException("SOURCE_MISSING"); return code > 0 ? CommandResult.ACCEPTED : CommandResult.REJECTED; }
|
||||
public enum FieldValueSource { SCALAR(0f), VECTOR_MAGNITUDE(1f); final float code; FieldValueSource(float code) { this.code = code; } }
|
||||
private boolean shouldWakeWeather() {
|
||||
if (disposed || state == State.DISABLED || targetFps == TargetFps.AUTO) return false;
|
||||
if (disposed || state == State.DISABLED) return false;
|
||||
if (v4ForecastPlaying && !submittedEncodedFieldKeys.isEmpty()) return true;
|
||||
if (targetFps == TargetFps.AUTO) return false;
|
||||
final boolean v4ParticleFieldReady = v4ParticleBindingFieldKey != null && submittedEncodedFieldKeys.contains(v4ParticleBindingFieldKey);
|
||||
return (v4ParticleFieldReady && particleVisible) ||
|
||||
(state == State.READY && (particleVisible || (weatherColorVisible && playbackState == PlaybackState.PLAY)));
|
||||
@@ -233,7 +236,7 @@ public final class WeatherRuntime {
|
||||
try {
|
||||
if (cadenceHandler == null) cadenceHandler = new Handler(Looper.getMainLooper());
|
||||
cadenceHandler.removeCallbacks(cadenceWake);
|
||||
cadenceHandler.postDelayed(cadenceWake, targetFps == TargetFps.FPS_30 ? 34L : 17L);
|
||||
cadenceHandler.postDelayed(cadenceWake, targetFps == TargetFps.FPS_60 ? 17L : 34L);
|
||||
} catch (RuntimeException ignored) {
|
||||
// Local JVM tests may not provide an Android Looper; Core cadence remains authoritative.
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{"contractSchemaVersion":1,"profileID":"wind","profileVersion":1,"allowAdditionalPlanes":true,"sharedValidityRequired":true,"timeRelation":"VALID_GTE_REFERENCE","requiredPlanes":[{"semanticID":1,"valueClass":"VELOCITY_COMPONENT","unitID":1,"groupID":1,"component":"U_EAST","requiredFlag":true},{"semanticID":2,"valueClass":"VELOCITY_COMPONENT","unitID":1,"groupID":1,"component":"V_NORTH","requiredFlag":true},{"semanticID":3,"valueClass":"VALIDITY","unitID":0,"groupID":0,"component":"NONE","requiredFlag":true}]}
|
||||
@@ -46,6 +46,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private var timeWindowMode = false
|
||||
private var productionFeedMode = false
|
||||
private var hostSubmissionMode = false
|
||||
private var step097V4GoldenReady = false // Test-App-only state; never a Core window mirror.
|
||||
private var realWeatherMode = false
|
||||
private var pureParticleMode = false
|
||||
private var purePbfMode = false
|
||||
@@ -998,7 +999,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
|
||||
private fun showTimeMenu(anchor: android.view.View) {
|
||||
PopupMenu(this, anchor).apply {
|
||||
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")
|
||||
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", "A2 V4 WIND TEST")
|
||||
labels.forEachIndexed { index, label -> menu.add(0, index, index, label) }
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
@@ -1016,12 +1017,69 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
11 -> controlTimeline(6, 0.0f)
|
||||
12 -> controlTimeline(7, 0.0f)
|
||||
13 -> controlTimeline(8, 0.0f)
|
||||
14 -> showStep097V4Menu(anchor)
|
||||
}; true
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStep097V4Menu(anchor: android.view.View) {
|
||||
PopupMenu(this, anchor).apply {
|
||||
val labels = arrayOf("LOAD WIND T0/T1", "PLAY", "PAUSE", "SEEK 25%", "SEEK 75%", "SPEED 2x", "SPEED 1x", "CLEAR")
|
||||
labels.forEachIndexed { index, label -> menu.add(0, index, index, label) }
|
||||
setOnMenuItemClickListener { item ->
|
||||
if (item.itemId == 0) loadStep097V4Golden() else controlStep097V4Golden(item.itemId)
|
||||
true
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadStep097V4Golden() {
|
||||
if (!styleReady || !enabled || !hostSubmissionMode || realWeatherMode) {
|
||||
Log.i(TAG, "STEP097_A2_V4_REJECT reason=SELECT_HOST_FIELD_FIRST styleReady=$styleReady")
|
||||
return
|
||||
}
|
||||
step097V4GoldenReady = false
|
||||
try {
|
||||
val runtime = weatherRuntime ?: WeatherRuntime(maplibreMap,
|
||||
WeatherRuntime.Config(PARTICLE_SOURCE_ID, WEATHER_COLOR_LAYER_ID, PARTICLE_LAYER_ID))
|
||||
.also { weatherRuntime = it }
|
||||
Step097V4GoldenFixture.load(assets, runtime)
|
||||
// The RN Golden QBIN covers Japan; default world center (0,0)
|
||||
// would leave the valid field outside this device's viewport.
|
||||
maplibreMap.cameraPosition = CameraPosition.Builder()
|
||||
.target(LatLng(36.2, 137.5)).zoom(4.8).build()
|
||||
step097V4GoldenReady = true
|
||||
timelineState = "V4 READY"
|
||||
status.text = statusText()
|
||||
} catch (error: Exception) {
|
||||
step097V4GoldenReady = false
|
||||
timelineState = "V4 ERROR"
|
||||
status.text = statusText()
|
||||
Log.e(TAG, "STEP097_A2_V4_ERROR reason=GOLDEN_LOAD", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun controlStep097V4Golden(action: Int) {
|
||||
if (!styleReady || !step097V4GoldenReady) {
|
||||
Log.i(TAG, "STEP097_A2_V4_REJECT reason=LOAD_GOLDEN_FIRST action=$action")
|
||||
return
|
||||
}
|
||||
val runtime = weatherRuntime ?: return
|
||||
val result = Step097V4GoldenFixture.command(runtime, action)
|
||||
if (action == 7 && result == WeatherRuntime.CommandResult.ACCEPTED) step097V4GoldenReady = false
|
||||
if (result == WeatherRuntime.CommandResult.ACCEPTED) {
|
||||
timelineState = when (action) {
|
||||
1 -> "V4 PLAY"; 2 -> "V4 PAUSE"; 3 -> "V4 25%"; 4 -> "V4 75%"
|
||||
5 -> "V4 2x"; 6 -> "V4 1x"; 7 -> "V4 CLEARED"; else -> timelineState
|
||||
}
|
||||
status.text = statusText()
|
||||
}
|
||||
Log.i(TAG, "STEP097_A2_V4_COMMAND action=$action result=$result targetLabel=$timelineState")
|
||||
}
|
||||
|
||||
private fun controlTimeline(command: Int, value: Float) {
|
||||
if (!styleReady || !enabled || !timeWindowMode) { Log.i(TAG, "ParticleTimeline: event=REJECTED reason=STYLE_NOT_READY"); return }
|
||||
val runtime = weatherRuntime
|
||||
@@ -1087,6 +1145,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun loadValidationStyle(active: Boolean, enabled: Boolean, reason: String = "STYLE_CHANGE") {
|
||||
step097V4GoldenReady = false
|
||||
if (pureParticleMode) {
|
||||
loadPureParticleStyle(active, enabled, reason)
|
||||
return
|
||||
@@ -1657,6 +1716,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
purePbfMode -> "PURE PBF"
|
||||
navSeaPbfMode -> "NAVSEA PBF"
|
||||
realWeatherMode -> "REAL WEATHER"
|
||||
step097V4GoldenReady -> "V4 GOLDEN"
|
||||
hostSubmissionMode -> "HOST"
|
||||
productionFeedMode -> "PRODUCTION"
|
||||
else -> "FIXTURE"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package org.maplibre.android.testapp.activity.particle
|
||||
|
||||
import android.content.res.AssetManager
|
||||
import android.util.Log
|
||||
import org.maplibre.android.maps.WeatherRuntime
|
||||
|
||||
/** STEP-097 A2 test-only ingress. Uses the existing public Android V4 API. */
|
||||
internal object Step097V4GoldenFixture {
|
||||
const val KEY = "fixture.wind"
|
||||
const val T0_MS = 1_789_239_600_000L
|
||||
const val T1_MS = 1_789_243_200_000L
|
||||
const val MID_MS = 1_789_241_400_000L
|
||||
private const val ROOT = "integration/step097-v4/wind/"
|
||||
private const val TAG = "ParticleValidation"
|
||||
|
||||
fun load(assets: AssetManager, runtime: WeatherRuntime) {
|
||||
val contract = assets.open(ROOT + "contract.json").bufferedReader().use { it.readText() }
|
||||
val clear = runtime.clearAllEncodedFields()
|
||||
val color = runtime.setEncodedColorBinding(KEY, 1, 1, 1, 1)
|
||||
val particle = runtime.setEncodedParticleBinding(KEY, 0, 1, 1, 2, 1, 0, 0, 1)
|
||||
val t0 = runtime.submitEncodedField(KEY, "fixture-wind", 1L, 1L,
|
||||
contract, assets.open(ROOT + "T0.qbin").use { it.readBytes() })
|
||||
val t1 = runtime.submitEncodedField(KEY, "fixture-wind", 2L, 2L,
|
||||
contract, assets.open(ROOT + "T1.qbin").use { it.readBytes() })
|
||||
val time = runtime.setEncodedForecastTime(MID_MS)
|
||||
Log.i(TAG, "STEP097_A2_V4_SUBMIT clear=$clear color=$color particle=$particle t0=$t0 t1=$t1 time=$time")
|
||||
check(clear == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
color == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
particle == WeatherRuntime.CommandResult.ACCEPTED &&
|
||||
t0 == WeatherRuntime.EncodedFieldSubmitResult.ACCEPTED_INSERTED &&
|
||||
t1 == WeatherRuntime.EncodedFieldSubmitResult.ACCEPTED_INSERTED &&
|
||||
time == WeatherRuntime.CommandResult.ACCEPTED) { "V4 golden ingress rejected" }
|
||||
Log.i(TAG, "STEP097_A2_V4_READY fieldKey=$KEY T0=$T0_MS T1=$T1_MS target=$MID_MS")
|
||||
}
|
||||
|
||||
fun command(runtime: WeatherRuntime, action: Int): WeatherRuntime.CommandResult = when (action) {
|
||||
1 -> runtime.setEncodedForecastPlaybackState(true)
|
||||
2 -> runtime.setEncodedForecastPlaybackState(false)
|
||||
3 -> runtime.setEncodedForecastTime(T0_MS + 900_000L)
|
||||
4 -> runtime.setEncodedForecastTime(T0_MS + 2_700_000L)
|
||||
5 -> runtime.setEncodedForecastPlaybackSpeed(2.0f)
|
||||
6 -> runtime.setEncodedForecastPlaybackSpeed(1.0f)
|
||||
7 -> runtime.clearAllEncodedFields()
|
||||
else -> WeatherRuntime.CommandResult.REJECTED
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ std::vector<char> charsFromData(NSData *data) {
|
||||
BOOL _particleVisible;
|
||||
NSString *_v4ParticleBindingFieldKey;
|
||||
NSMutableSet<NSString *> *_submittedEncodedFieldKeys;
|
||||
BOOL _v4ForecastPlaying;
|
||||
NSTimer *_v4CadenceTimer;
|
||||
}
|
||||
- (BOOL)isV4ParticleCadenceReady;
|
||||
@@ -147,9 +148,11 @@ std::vector<char> charsFromData(NSData *data) {
|
||||
- (void)refreshV4CadenceWake {
|
||||
[_v4CadenceTimer invalidate];
|
||||
_v4CadenceTimer = nil;
|
||||
if (_disposed || ![self isV4ParticleCadenceReady] || !_particleVisible ||
|
||||
_targetFps == MLNParticleFieldRuntimeTargetFpsAuto) return;
|
||||
const NSTimeInterval interval = _targetFps == MLNParticleFieldRuntimeTargetFps30 ? 0.034 : 0.017;
|
||||
const BOOL forecastWake = _v4ForecastPlaying && _submittedEncodedFieldKeys.count > 0;
|
||||
if (_disposed || _state == MLNParticleFieldRuntimeStateDisabled ||
|
||||
(!forecastWake && (![self isV4ParticleCadenceReady] || !_particleVisible ||
|
||||
_targetFps == MLNParticleFieldRuntimeTargetFpsAuto))) return;
|
||||
const NSTimeInterval interval = _targetFps == MLNParticleFieldRuntimeTargetFps60 ? 0.017 : 0.034;
|
||||
_v4CadenceTimer = [NSTimer scheduledTimerWithTimeInterval:interval
|
||||
target:self
|
||||
selector:@selector(runV4CadenceWake:)
|
||||
@@ -160,7 +163,7 @@ std::vector<char> charsFromData(NSData *data) {
|
||||
- (void)runV4CadenceWake:(NSTimer *)timer {
|
||||
if (timer != _v4CadenceTimer) return;
|
||||
_v4CadenceTimer = nil;
|
||||
if (_disposed || ![self isV4ParticleCadenceReady] || !_particleVisible) return;
|
||||
if (_disposed) return;
|
||||
[_mapView triggerRepaint];
|
||||
[self refreshV4CadenceWake];
|
||||
}
|
||||
@@ -289,7 +292,9 @@ std::vector<char> charsFromData(NSData *data) {
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedForecastPlaybackState:(BOOL)playing error:(NSError **)error {
|
||||
return [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { source.setForecastPlaybackState(playing ? mbgl::particle::ForecastPlaybackState::Playing : mbgl::particle::ForecastPlaybackState::Paused); return true; }];
|
||||
const BOOL completed = [self withCurrentSource:error action:^bool(mbgl::style::RasterArraySource &source, mbgl::style::WeatherColorLayer &, mbgl::style::RasterParticleLayer &) { source.setForecastPlaybackState(playing ? mbgl::particle::ForecastPlaybackState::Playing : mbgl::particle::ForecastPlaybackState::Paused); return true; }];
|
||||
if (completed) { _v4ForecastPlaying = playing; [self refreshV4CadenceWake]; }
|
||||
return completed;
|
||||
}
|
||||
|
||||
- (BOOL)setEncodedForecastPlaybackSpeed:(float)speed error:(NSError **)error {
|
||||
|
||||
Reference in New Issue
Block a user