{"id":5127,"date":"2026-03-17T04:23:59","date_gmt":"2026-03-17T04:23:59","guid":{"rendered":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=5127"},"modified":"2026-03-17T04:24:00","modified_gmt":"2026-03-17T04:24:00","slug":"scythe_cmd-android-app","status":"publish","type":"post","link":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=5127","title":{"rendered":"SCYTHE_CMD Android App"},"content":{"rendered":"\n<p><strong>#<\/strong><strong> Scythe Command \u2014 Standalone Android App Plan<\/strong><\/p>\n\n\n\n<p><strong>##<\/strong><strong> Status: Phase 9 \u2014 Fresh Android App (ATAK Plugin Retired)<\/strong><\/p>\n\n\n\n<p><strong>###<\/strong><strong> Decision<\/strong><\/p>\n\n\n\n<p>ATAK plugin retired due to: CotPortListActivity crash (pre-existing ATAK bug), complex MOBAC tile API, GDAL legacy stack, JNI hook maintenance burden.<\/p>\n\n\n\n<p><strong>**<\/strong><strong>New approach<\/strong><strong>**<\/strong>: Standalone `ScytheCommandApp` \u2014 thin Android WebView shell loading `command-ops-visualization.html` directly from the RF Scythe orchestrator. All 35K lines of Cesium + RF viz reused immediately.<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Current Architecture<\/strong><\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p>ScytheCommandApp (APK on Pixel 7 Pro)<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2514\u2500\u2500 WebView \u2192 http:\/\/192.168.1.185:5001\/command-ops-visualization.html<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 All \/api\/* calls \u2192 same origin (orchestrator:5001)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 SSE streams \u2192 same origin (no CORS issues)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 Cesium 3D globe (already working)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 Recon Entity markers (1185 entities)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 RF Hypergraph visualization<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u2500 ScytheBridge JS interface (GPS, settings, toast)<\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Architecture (fully operational)<\/strong><\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p>ATAK Plugin (APK on Pixel 7 Pro)<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u251c\u2500\u2500 CoT MapGroup listeners \u2192 entity.spawn \/ entity.move \/ entity.remove<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u251c\u2500\u2500 SSE entity callback \u2192 rf.detection (sensor, freq, bearing, emitter_id)<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2502 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2192 entity.move (non-RF entities)<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2514\u2500\u2500 EventStreamer \u2192 HTTP POST every 5s &nbsp;(buffer 200 events)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; target: 10.107.190.84:8080 &nbsp;\u2190 avf_tap_fixed network<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u25bc<\/p>\n\n\n\n<p>Debian VM (crosvm, CID 2049, 8-core Tensor G2, 3.8GB RAM, 70GB disk)<\/p>\n\n\n\n<p>&nbsp; &nbsp; \u2514\u2500\u2500 scythe-analytics.service &nbsp;(systemd, enabled, auto-restart)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u2500 scythe_vm_server.py v2 (Flask :8080)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 ScytheDuckStore + ParquetPipeline &nbsp;(persistent DuckDB)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 EventHypergraph (incremental, background rebuild 30s)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 SpaceTimeCube &nbsp; (0.01\u00b0 geo \u00d7 5s time voxels)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 POST \/api\/events\/ingest<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/events\/query?sql=SELECT&#8230;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/events\/stats<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 POST \/api\/events\/flush \u2192 Parquet blocks<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/hypergraph\/summary<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/hypergraph\/swarms &nbsp; &nbsp; &nbsp; &nbsp; (MEMBER_OF_SWARM edges)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/hypergraph\/rf_triangulation (TRIANGULATED_FROM edges)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/hypergraph\/co_movement &nbsp; &nbsp;(CO_MOVED_WITH edges)<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/hypergraph\/entity\/&lt;id&gt;<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/spacetime\/query?lat&amp;lon&amp;radius_m&amp;t0&amp;t1<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u251c\u2500\u2500 GET &nbsp;\/api\/spacetime\/stats<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2502<\/p>\n\n\n\n<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \u2514\u2500\u2500 GET &nbsp;\/api\/swarms &nbsp;(unified: DuckDB density + hypergraph)<\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p><strong>###<\/strong><strong> VM Access<\/strong><\/p>\n\n\n\n<p>&#8211; SSH: `ssh -i \/tmp\/avf_scythe_key -o ProxyCommand=&#8217;adb -s &#8230; shell nc 10.107.190.84 22&#8242; droid@avf-vm`<\/p>\n\n\n\n<p>&#8211; Port forward: `adb forward tcp:8181 tcp:8080` \u2192 `curl localhost:8181\/health`<\/p>\n\n\n\n<p>&#8211; VM: Debian 13 Trixie, Python 3.13.5, DuckDB 1.5.0, PyArrow 23.0.1<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Systemd service<\/strong><\/p>\n\n\n\n<p>&#8211; Unit: `\/etc\/systemd\/system\/scythe-analytics.service`<\/p>\n\n\n\n<p>&#8211; `ExecStartPre`: frees port 8080 via `fuser -k` before each start<\/p>\n\n\n\n<p>&#8211; `Restart=on-failure`, `RestartSec=5`<\/p>\n\n\n\n<p>&#8211; `MemoryMax=1G`, `CPUQuota=400%`<\/p>\n\n\n\n<p>&#8211; `enabled` + `active`, NRestarts=0 \u2705<\/p>\n\n\n\n<p><strong>##<\/strong><strong> What was built<\/strong><\/p>\n\n\n\n<p><strong>###<\/strong><strong> ATAK Plugin (ATAKScythePlugin\/)<\/strong><\/p>\n\n\n\n<p>&#8211; 4-tab UI: CONNECT \/ RF INTEL \/ MISSIONS \/ SWARMS<\/p>\n\n\n\n<p>&#8211; RF node map layer + animated swarm layer (pulsing rings, velocity arrows)<\/p>\n\n\n\n<p>&#8211; OkHttp REST + SSE client connecting to rf_scythe_api_server.py<\/p>\n\n\n\n<p>&#8211; 16KB ELF compliant: extractNativeLibs=false, useLegacyPackaging=false<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>EventStreamer.java<\/strong><strong>**<\/strong> \u2014 buffers 200 events, flushes to \/api\/events\/ingest every 5s<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>APK: app\/build\/outputs\/apk\/debug\/app-debug.apk (6.1 MB)<\/strong><strong>**<\/strong><\/p>\n\n\n\n<p>&#8211; APK installed on device \u2705 \u2014 `android:exported=true` + `&lt;queries&gt;` block (Android 11+ visibility fix)<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Python Intelligence Modules<\/strong><\/p>\n\n\n\n<p>&#8211; scene_event_schema.py \u2014 15-event canonical schema \u2705<\/p>\n\n\n\n<p>&#8211; scene_event_log.py \u2014 SQLite-WAL ledger, .atakrec export \u2705<\/p>\n\n\n\n<p>&#8211; scene_replay_engine.py \u2014 deterministic replay, scrub, fork \u2705<\/p>\n\n\n\n<p>&#8211; scene_event_compressor.py \u2014 7.9\u00d7 columnar compression, fixed dict-encode bug \u2705<\/p>\n\n\n\n<p>&#8211; scene_hypergraph.py \u2014 RF triangulation, swarm, co-movement edges \u2705<\/p>\n\n\n\n<p>&#8211; scene_spacetime_cube.py \u2014 0.14ms radius+time query \u2705<\/p>\n\n\n\n<p>&#8211; cluster_swarm_engine.py \u2014 geo-bucket cluster detection \u2192 CoT \u2705<\/p>\n\n\n\n<p>&#8211; tak_swarm_emitter.py \u2014 PyTAK CoT emitter \u2705<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>scene_duckdb_store.py<\/strong><strong>**<\/strong> \u2014 DuckDB event store, 54ms bulk insert \/ 10ms scrub \u2705<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>scene_parquet_pipeline.py<\/strong><strong>**<\/strong> \u2014 ZSTD Parquet cold store, 3\u00d7 compression, 18ms read \u2705<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Server (rf_scythe_api_server.py)<\/strong><\/p>\n\n\n\n<p>&#8211; Swarm routes: \/api\/clusters\/swarms, \/stream, \/cot<\/p>\n\n\n\n<p>&#8211; Replay routes: \/api\/replay\/session\/start|end|snapshot|events|state|export<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>DuckDB routes:<\/strong><strong>**<\/strong><\/p>\n\n\n\n<p>&nbsp; &#8211; POST \/api\/events\/ingest \u2014 bulk Android\u2192DuckDB ingestion<\/p>\n\n\n\n<p>&nbsp; &#8211; GET|POST \/api\/events\/query?sql=&#8230; \u2014 arbitrary SELECT analytics<\/p>\n\n\n\n<p>&nbsp; &#8211; GET \/api\/events\/stats \u2014 store statistics<\/p>\n\n\n\n<p>&nbsp; &#8211; GET \/api\/events\/export\/parquet \u2014 stream Parquet file to client<\/p>\n\n\n\n<p>&nbsp; &#8211; GET \/api\/events\/blocks \u2014 list Parquet cold-storage inventory<\/p>\n\n\n\n<p>&nbsp; &#8211; POST \/api\/events\/flush \u2014 partition hot store into Parquet blocks<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Phase 7: Plugin Loaded \u2705 \u2014 End-to-end pipeline validation<\/strong><\/p>\n\n\n\n<p><strong>###<\/strong><strong> Next steps<\/strong><\/p>\n\n\n\n<p>&#8211; [ ] Tap RF Scythe toolbar icon \u2192 verify 4-tab UI opens (CONNECT \/ RF INTEL \/ MISSIONS \/ SWARMS)<\/p>\n\n\n\n<p>&#8211; [ ] Verify EventStreamer connects to VM 10.107.190.84:8080 (CONNECT tab shows green)<\/p>\n\n\n\n<p>&#8211; [ ] Generate RF detection events \u2192 confirm \/api\/events\/stats count increases<\/p>\n\n\n\n<p>&#8211; [ ] Test RF signal dot map layer renders on ATAK map<\/p>\n\n\n\n<p>&#8211; [ ] Surface \/api\/hypergraph\/rf_triangulation results back to RF INTEL tab<\/p>\n\n\n\n<p>&#8211; [ ] Emit swarm.update events from SwarmLayer on cluster membership changes<\/p>\n\n\n\n<p>&#8211; [ ] Surface \/api\/spacetime\/query for operator &#8220;query area&#8221; map gesture<\/p>\n\n\n\n<p><strong>&#8212;<\/strong><\/p>\n\n\n\n<p><strong>##<\/strong><strong> Known Blocker (RESOLVED): ATAK CIV 4.6.0 on Android 16<\/strong><\/p>\n\n\n\n<p>&#8211; ATAK CIV crashes at startup on Pixel 7 Pro (Android 16 \/ SDK 36)<\/p>\n\n\n\n<p>&#8211; Root cause: libsqlite3.so removed from Android 16 system image; commoncommo NDK<\/p>\n\n\n\n<p>&nbsp; binaries linked against pre-bionic-hardening ABI; FORTIFY pthread_mutex crash<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>Our plugin installs and is visible<\/strong><strong>**<\/strong> \u2014 AppsFilter BLOCKED resolved<\/p>\n\n\n\n<p>&#8211; <strong>**<\/strong><strong>Plugin cannot load because ATAK itself crashes before loading any plugins<\/strong><strong>**<\/strong><\/p>\n\n\n\n<p>&#8211; Solutions:<\/p>\n\n\n\n<p>&nbsp; 1. Android emulator API 30-33 (immediate, no hardware needed)<\/p>\n\n\n\n<p>&nbsp; 2. Second physical device with Android 11-14<\/p>\n\n\n\n<p>&nbsp; 3. Recompile ATAK from source (atak\/ATAK\/) with bundled SQLite + NDK r26+<\/p>\n\n\n\n<p>&nbsp; 4. Wait for TAK.gov to release ATAK CIV 5.x with Android 15\/16 support<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Next Steps<\/strong><\/p>\n\n\n\n<p><strong>###<\/strong><strong> Immediate (map stability)<\/strong><\/p>\n\n\n\n<p>&#8211; [x] Fix GLMapView.inverseImpl() NPE \u2014 null guard on `lastsm.displayModel`<\/p>\n\n\n\n<p>&#8211; [x] Fix MapTouchController.onGestureStart() + onScale() NPE \u2014 same null guard pattern<\/p>\n\n\n\n<p>&#8211; [ ] Confirm map tap no longer crashes (install test pending)<\/p>\n\n\n\n<p>&#8211; [ ] Check if any other touch controllers have the same `sm.displayModel` access pattern<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Short-term (pipeline validation)<\/strong><\/p>\n\n\n\n<p>&#8211; [ ] Verify EventStreamer connects to VM (CONNECT tab shows green)<\/p>\n\n\n\n<p>&#8211; [ ] Generate RF detection events \u2192 confirm \/api\/events\/stats count increases<\/p>\n\n\n\n<p>&#8211; [ ] Test RF signal dot map layer renders on ATAK map<\/p>\n\n\n\n<p>&#8211; [ ] Surface \/api\/hypergraph\/rf_triangulation results back to RF INTEL tab<\/p>\n\n\n\n<p>&#8211; [ ] Emit swarm.update events from SwarmLayer on cluster membership changes<\/p>\n\n\n\n<p>&#8211; [ ] Surface \/api\/spacetime\/query for operator &#8220;query area&#8221; map gesture<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Medium-term strategic: Plugin-level Cesium renderer override<\/strong><\/p>\n\n\n\n<p>The native TAK engine has fundamental fragility (null displayModel NPEs, GDAL bloat,<\/p>\n\n\n\n<p>no first-class 3D\/WebXR path). The recommended path forward:<\/p>\n\n\n\n<p>1. <strong>**<\/strong><strong>Prove the override hook<\/strong><strong>**<\/strong> \u2014 in ScytheMapComponent, grab the GLSurfaceView from<\/p>\n\n\n\n<p>&nbsp; &nbsp;ATAKActivity and set a custom GLSurfaceView.Renderer that wraps Cesium Native renders<\/p>\n\n\n\n<p>&nbsp; &nbsp;into the existing GL context. ATAK UI stays untouched; overlays migrate to Cesium primitives.<\/p>\n\n\n\n<p>2. <strong>**<\/strong><strong>RF volumes + swarm viz<\/strong><strong>**<\/strong> \u2014 Cesium Native handles volumetric RF propagation domes,<\/p>\n\n\n\n<p>&nbsp; &nbsp;sensor cones, animated swarm centroids\/particles natively via 3D Tiles + Entity API.<\/p>\n\n\n\n<p>3. <strong>**<\/strong><strong>Long-term<\/strong><strong>**<\/strong> \u2014 intercept EngineLibrary native lib load, drop libtakengine_cesium.so<\/p>\n\n\n\n<p>&nbsp; &nbsp;stub (~20 critical JNI methods \u2192 Cesium). Full 3D\/streaming, no GDAL, smaller APK.<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Phase 8 Strategy: Staged JNI Hook Rollout<\/strong><\/p>\n\n\n\n<p><strong>**<\/strong><strong>Why staged:<\/strong><strong>**<\/strong> The JNI hooking approach is powerful but requires careful C++ implementation. &nbsp;<\/p>\n\n\n\n<p>Instead of integrating Cesium immediately, we&#8217;re building the foundation layer-by-layer.<\/p>\n\n\n\n<p><strong>**<\/strong><strong>Stage 1 (\u2705 COMPLETE):<\/strong><strong>**<\/strong> RF Scythe plugin stable + HookManager integrated &nbsp;<\/p>\n\n\n\n<p><strong>**<\/strong><strong>Stage 2 (NEXT):<\/strong><strong>**<\/strong> JNI hook library (libscythe_hook.so) \u2014 dlsym interception &nbsp;<\/p>\n\n\n\n<p><strong>**<\/strong><strong>Stage 3 (FUTURE):<\/strong><strong>**<\/strong> Full Cesium Native renderer integration &nbsp;<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Phase 8.1 Complete \u2705<\/strong><\/p>\n\n\n\n<p>&#8211; [x] RF Scythe plugin loaded and running (CONNECT\/RF INTEL\/MISSIONS\/SWARMS tabs functional)<\/p>\n\n\n\n<p>&#8211; [x] HookManager.java created with static initializer<\/p>\n\n\n\n<p>&#8211; [x] ScytheLifecycle integrated to trigger HookManager.ensureLoaded() early<\/p>\n\n\n\n<p>&#8211; [x] Plugin lifecycle logs confirm hook system attempting to load (graceful fail if .so missing)<\/p>\n\n\n\n<p>&#8211; [x] No crashing when libscythe_hook.so unavailable \u2014 continues with ATAK rendering<\/p>\n\n\n\n<p>&#8211; [x] Build: lint disabled (Instantiatable), assemble succeeds, APK installed \u2705<\/p>\n\n\n\n<p>1. <strong>**<\/strong><strong>Basemap not rendering:<\/strong><strong>**<\/strong> `DatasetDescriptorFactory2.create()` fails when generating MOBAC XML<\/p>\n\n\n\n<p>&nbsp; &nbsp;&#8211; Legacy ATAK raster stack is fragile<\/p>\n\n\n\n<p>&nbsp; &nbsp;&#8211; This is exactly why JNI hooking is the right approach<\/p>\n\n\n\n<p>2. <strong>**<\/strong><strong>Native build complexity:<\/strong><strong>**<\/strong> Android NDK + CMake + Cesium dependencies require careful setup &nbsp;<\/p>\n\n\n\n<p>&nbsp; &nbsp;&#8211; Created: `tak_engine_hook.cpp`, `cesium_renderer_wrapper.cpp`, `HookManager.java` &nbsp;<\/p>\n\n\n\n<p>&nbsp; &nbsp;&#8211; Will integrate into build pipeline in Phase 8.2<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Immediate Fallback (Phase 8.1)<\/strong><\/p>\n\n\n\n<p>Keep RF Scythe plugin working without basemap for now:<\/p>\n\n\n\n<p>&#8211; Plugin connects to VM \u2705<\/p>\n\n\n\n<p>&#8211; CONNECT tab shows &#8220;STREAMING&#8221; \u2705<\/p>\n\n\n\n<p>&#8211; RF INTEL, MISSIONS, SWARMS tabs functional \u2705<\/p>\n\n\n\n<p>&#8211; RF signal dots + swarm rings render (in 2D, no globe) \u2705<\/p>\n\n\n\n<p>&#8211; Map canvas is blank but not crashing \u2705<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Path to 3D Globe (Phase 8.2-8.3)<\/strong><\/p>\n\n\n\n<p>Once JNI hook layer is complete:<\/p>\n\n\n\n<p>&#8211; Every ATAK map call \u2192 redirects to Cesium<\/p>\n\n\n\n<p>&#8211; 3D globe renders, streaming 3D Tiles work<\/p>\n\n\n\n<p>&#8211; RF overlays composite on top<\/p>\n\n\n\n<p>&#8211; No MOBAC\/XML\/tile-reader fragility<\/p>\n\n\n\n<p><strong>###<\/strong><strong> JNI Hook Layer Architecture<\/strong><\/p>\n\n\n\n<p><strong>**<\/strong><strong>Interception chain:<\/strong><strong>**<\/strong><\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p>Java: GLMapView.drawFrame()<\/p>\n\n\n\n<p>&nbsp; \u2193 JNI call<\/p>\n\n\n\n<p>JVM: dlsym(&#8220;Java_com_atakmap_map_opengl_GLMapView_drawFrame&#8221;)<\/p>\n\n\n\n<p>&nbsp; \u2193 our hook intercepts<\/p>\n\n\n\n<p>dlsym hook: is_critical_symbol() \u2192 true<\/p>\n\n\n\n<p>&nbsp; \u2193 returns our implementation<\/p>\n\n\n\n<p>Our handler: generic_handler \u2192 Java_com_atakmap_map_opengl_GLMapView_drawFrame_impl<\/p>\n\n\n\n<p>&nbsp; \u2193 delegates to Cesium<\/p>\n\n\n\n<p>Cesium: CesiumRenderer::drawFrame()<\/p>\n\n\n\n<p>&nbsp; \u2193 renders 3D globe + overlays<\/p>\n\n\n\n<p>OpenGL framebuffer<\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p><strong>**<\/strong><strong>Load order critical:<\/strong><strong>**<\/strong><\/p>\n\n\n\n<p>1. `HookManager.initialize()` calls `System.loadLibrary(&#8220;scythe_hook&#8221;)` early (static block)<\/p>\n\n\n\n<p>2. This must happen <strong>**<\/strong><strong>before<\/strong><strong>**<\/strong> ATAK&#8217;s ATAKActivity loads `libtakengine.so`<\/p>\n\n\n\n<p>3. Once libscythe_hook.so is loaded, our dlsym hook is active<\/p>\n\n\n\n<p>4. When ATAK tries to load libtakengine.so and resolve its JNI symbols, we intercept<\/p>\n\n\n\n<p><strong>###<\/strong><strong> Symbol list (~20 critical):<\/strong><\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p>Globe lifecycle: &nbsp; &nbsp;create, destroy, resize, setDisplayMode<\/p>\n\n\n\n<p>Rendering: &nbsp; &nbsp; &nbsp; &nbsp; drawFrame (core)<\/p>\n\n\n\n<p>Projection: &nbsp; &nbsp; &nbsp; &nbsp;forward, inverse, getProjection<\/p>\n\n\n\n<p>Layer rendering: &nbsp; DatasetRasterLayer2_drawImpl, TileClient_getTile<\/p>\n\n\n\n<p>Coordinate xform: &nbsp;CoordsUtility_forward\/inverse, WebMercatorProjection_*<\/p>\n\n\n\n<p>&#8220;`<\/p>\n\n\n\n<p>Each symbol \u2192 routes to Cesium Native implementation in `cesium_renderer_wrapper.cpp`.<\/p>\n\n\n\n<p><strong>##<\/strong><strong> ATAK Build Notes<\/strong><\/p>\n\n\n\n<p>&#8211; Source: AndroidTacticalAssaultKit-CIV-main\/atak\/ATAK\/<\/p>\n\n\n\n<p>&#8211; Build: `JAVA_HOME=\/opt\/amazon-corretto-11.0.30.7.1-linux-x64 .\/gradlew assembleCivSmallDebug -PskipNativeBuild=true`<\/p>\n\n\n\n<p>&nbsp; (Gradle 6.9.1 requires Java 11 \u2014 Java 21 causes Groovy ASM class version 65 error)<\/p>\n\n\n\n<p>&#8211; local.properties requires absolute paths for sdk.dir and takDebugKeyFile<\/p>\n\n\n\n<p>&#8211; NOP patch at 0xbfe280 in libtakengine.so \u2014 survives rebuild (pre-compiled .so in jniLibs)<\/p>\n\n\n\n<p>&#8211; Patched files (source patches, applied once):<\/p>\n\n\n\n<p>&nbsp; &#8211; GLMapView.java: null guard for lastsm.displayModel in inverseImpl() (line 909)<\/p>\n\n\n\n<p>&nbsp; &#8211; MapTouchController.java: null guards in onGestureStart() + onScale()<\/p>\n\n\n\n<p><strong>##<\/strong><strong> Build fixes applied (for reference)<\/strong><\/p>\n\n\n\n<p>&#8211; AGP 8.3.2 + Gradle 8.6 + Java 21<\/p>\n\n\n\n<p>&#8211; Manifest: android:exported=true on component activity; &lt;queries&gt; block for ATAK CIV<\/p>\n\n\n\n<p>&#8211; ic_scythe.xml: replaced &lt;circle&gt; with path (AAPT2 compatibility)<\/p>\n\n\n\n<p>&#8211; GLMapView.forward(GeoPoint,float[]) \u2192 forward(GeoPoint) returns PointF<\/p>\n\n\n\n<p>&#8211; GLMapView.getBounds()\/getHeight() \u2192 northBound\/southBound\/_top\/_bottom fields<\/p>\n\n\n\n<p>&#8211; AbstractLayer.dispatchOnLayerVisible() \u2192 dispatchOnVisibleChangedNoSync()<\/p>\n\n\n\n<p>&#8211; PluginTool \u2192 AbstractPluginTool (constructor takes Drawable, no override needed)<\/p>\n\n\n\n<p>&#8211; SseStreamClient: InterruptedException not thrown by connect() \u2192 catch Exception<\/p>\n","protected":false},"excerpt":{"rendered":"<p># Scythe Command \u2014 Standalone Android App Plan ## Status: Phase 9 \u2014 Fresh Android App (ATAK Plugin Retired) ### Decision ATAK plugin retired due to: CotPortListActivity crash (pre-existing ATAK bug), complex MOBAC tile API, GDAL legacy stack, JNI hook maintenance burden. **New approach**: Standalone `ScytheCommandApp` \u2014 thin Android WebView shell loading `command-ops-visualization.html` directly from&hellip;&nbsp;<a href=\"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?p=5127\" rel=\"bookmark\"><span class=\"screen-reader-text\">SCYTHE_CMD Android App<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3445,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"categories":[7],"tags":[],"class_list":["post-5127","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-the-truben-show"],"_links":{"self":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/5127","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5127"}],"version-history":[{"count":1,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/5127\/revisions"}],"predecessor-version":[{"id":5128,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/posts\/5127\/revisions\/5128"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=\/wp\/v2\/media\/3445"}],"wp:attachment":[{"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5127"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5127"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5127"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}