diff --git a/integration/react-native/MapLibreParticleRNTestHost/App.tsx b/integration/react-native/MapLibreParticleRNTestHost/App.tsx index 6e4dd3596..c0121c1ef 100644 --- a/integration/react-native/MapLibreParticleRNTestHost/App.tsx +++ b/integration/react-native/MapLibreParticleRNTestHost/App.tsx @@ -3,6 +3,7 @@ import { Button, SafeAreaView, ScrollView, StyleSheet, Text, TextInput, View, fi import { Camera, Map } from "@maplibre/maplibre-react-native"; import { FieldRuntime, type FieldColorStop, type RuntimeSnapshot, type TargetFps } from "@navsea/maplibre-particle-rn"; import { fieldDatasets, materializeV3Frame, type V3Dataset } from "./src/fieldDatasets"; +import { materializeV4Frame, qbinGoldenDatasets, type V4Dataset } from "./src/qbinGoldenFixtures"; import { weatherTestStyle } from "./src/weatherStyle"; const initialSnapshot: RuntimeSnapshot = { state: "WAITING_FOR_FIELDS", playbackState: "PAUSE", targetFps: "AUTO", disposed: false }; @@ -20,6 +21,10 @@ export default function App() { const [frames, setFrames] = useState>({ T0: "PENDING", T1: "PENDING" }); const [dataset, setDataset] = useState(fieldDatasets[0]); const [transport, setTransport] = useState("PENDING: V2 FILE_REF / FLOAT32_LE"); + const [v4Dataset, setV4Dataset] = useState(qbinGoldenDatasets[0]); + const [v4Frames, setV4Frames] = useState>({ T0: "PENDING", T1: "PENDING" }); + const [v4Bindings, setV4Bindings] = useState("PENDING"); + const [v4Transport, setV4Transport] = useState("PENDING: raw QBIN bundled asset → local file:// URI"); const [weatherVisible, setWeatherVisible] = useState(true); const [particleVisible, setParticleVisible] = useState(true); const [visual, setVisual] = useState(initialVisual); @@ -60,6 +65,36 @@ export default function App() { } catch (error) { setTransport(`ERROR: ${String(error)}`); setResult(`LOAD ${dataset.id}: ${String(error)}`); } }, [reactTag, dataset]); + const loadV4Dataset = useCallback(async () => { + if (reactTag == null) return setResult("MAP_NOT_READY: wait for GSI/style ready"); + try { + setV4Transport("LOADING: clear → binding → T0 → T1 → midpoint"); + setV4Frames({ T0: "PENDING", T1: "PENDING" }); + setV4Bindings("PENDING"); + const clear = await FieldRuntime.clearAllEncodedFields(reactTag); + if (clear !== "ACCEPTED") throw new Error(`clearAllEncodedFields expected ACCEPTED, got ${clear}`); + const color = await FieldRuntime.setEncodedColorBinding(reactTag, v4Dataset.colorBinding); + if (color !== "ACCEPTED") throw new Error(`ColorBinding expected ACCEPTED, got ${color}`); + const particle = v4Dataset.particleBinding == null ? "N/A" : await FieldRuntime.setEncodedParticleBinding(reactTag, v4Dataset.particleBinding); + if (particle !== "N/A" && particle !== "ACCEPTED") throw new Error(`ParticleBinding expected ACCEPTED, got ${particle}`); + setV4Bindings(`clearAll=${clear}; color=${color}; particle=${particle}`); + const localFrames = await Promise.all(v4Dataset.frames.map(materializeV4Frame)); + for (const frame of localFrames) { + const submit = await FieldRuntime.submitEncodedField(reactTag, v4Dataset.fieldKey, frame.metadata, v4Dataset.profileContract, frame.qbinFileUri); + setV4Frames((current) => ({ ...current, [frame.label]: submit })); + if (submit !== "ACCEPTED_INSERTED") throw new Error(`${frame.label} expected ACCEPTED_INSERTED, got ${submit}`); + } + const midpoint = await FieldRuntime.setEncodedForecastTime(reactTag, v4Dataset.midpointMs); + if (midpoint !== "ACCEPTED") throw new Error(`midpoint seek expected ACCEPTED, got ${midpoint}`); + await refresh(reactTag); + setV4Transport("READY: T0/T1 ACCEPTED_INSERTED; midpoint command ACCEPTED (mix=0.5 is not exposed)"); + setResult(`${v4Dataset.title}: V4 raw QBIN load accepted`); + } catch (error) { + setV4Transport(`ERROR: ${String(error)}`); + setResult(`LOAD V4 ${v4Dataset.id}: ${String(error)}`); + } + }, [reactTag, refresh, v4Dataset]); + const updateVisual = useCallback((key: keyof typeof initialVisual, value: string) => setVisual((current) => ({ ...current, [key]: value })), []); const applyVisual = useCallback(() => void command("particle visual", async (tag) => { const results = await Promise.all([ @@ -83,6 +118,11 @@ export default function App() {