{"id":5093,"date":"2026-03-14T01:21:30","date_gmt":"2026-03-14T01:21:30","guid":{"rendered":"https:\/\/172-234-197-23.ip.linodeusercontent.com\/?page_id=5093"},"modified":"2026-03-14T01:21:30","modified_gmt":"2026-03-14T01:21:30","slug":"dev-notes-03-14-2026","status":"publish","type":"page","link":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/?page_id=5093","title":{"rendered":"Dev Notes 03-14-2026"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">In <code>registerModel<\/code>, it&#8217;s checking that the pointer type is <code>SHARED<\/code>, then calling <code>registerModelImpl<\/code> with the raw pointer and the Earth geometry&#8217;s pointer. The issue is that<br><code>NativeGeometryModel.getPointer()<\/code> extracts the raw pointer from the wrapped Earth model, but if the original <code>getEarth<\/code> JNI call returned a null pointer, then we&#8217;d have a null<br>pointer object that would throw an NPE when accessing <code>.raw<\/code>. The <code>registerModel<\/code> call appears to be wrapped in some kind of synchronization or validation, so I need to see how that<br>protection works. So the ReflectInterop is catching the entire exception from <code>ProjectionFactory.&lt;clinit&gt;<\/code>, which means the NPE actually escaped from that initialization rather<br>than being contained within a JNI call. The stack trace shows the error originates in <code>getPointer()<\/code> when trying to access the <code>raw<\/code> field on a null <code>Pointer<\/code> object during<br><code>ProjectionFactory<\/code> class initialization.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the sequence more carefully though\u2014the FATAL error is being logged at line 563 in <code>GLMapView.&lt;init&gt;<\/code>, not 557, and it&#8217;s showing as an NPE rather than an<br><code>ExceptionInInitializerError<\/code>. That suggests something else is happening at that later line that&#8217;s triggering a different exception. where <code>ProjectionFactory<\/code> is first accessed. The<br>ExceptionInInitializerError from line 557 is caught somewhere! Then at line 563, <code>MapSceneModel<\/code> is constructed with <code>ProjectionFactory.getProjection(4326)<\/code> which now returns null<br>(since the static init failed, any future call throws ExceptionInInitializerError which might be caught, or maybe the class partially initialized\u2026).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actually, looking at the stack trace again:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  at com.atakmap.map.MapSceneModel.&lt;init&gt;(MapSceneModel.java:72)\n  at com.atakmap.map.opengl.GLMapView.&lt;init&gt;(GLMapView.java:563)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The NPE is <code>projection.getSpatialReferenceID()<\/code> where projection is null. So <code>ProjectionFactory.getProjection(4326)<\/code> returned null.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But that doesn&#8217;t make sense\u2014if the static initializer threw an exception, the JVM should permanently mark the class as broken and throw ExceptionInInitializerError on any subsequent<br>access. Unless\u2026 the exception is being caught somewhere, maybe in ReflectInterop or wrapped in a try-catch block. Let me check if there&#8217;s error handling around that line 557 call<br>in GLMapView&#8217;s constructor that&#8217;s swallowing the exception and allowing execution to continue.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So the ReflectInterop warnings are from <code>registerModelImpl<\/code> calling back into Java via JNI, catching an NPE internally and logging it as a warning. The static initializer actually<br>succeeds (the exception doesn&#8217;t escape), but the model gets registered with a null pointer. Then when <code>getProjectionImpl<\/code> is called, it returns null because the model wasn&#8217;t<br>properly initialized, causing the NPE in MapSceneModel when trying to call methods on the null projection object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real fix is to make sure <code>getProjectionImpl<\/code> returns a valid Projection object for SRID 4326. Since <code>getProjectionImpl<\/code> is a native method that returns a Projection interface<br>object, I need to understand how JNI handles returning Java objects from C++ code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I can create an <code>EquirectangularMapProjection<\/code> instance through JNI by finding the class, getting its constructor, and instantiating it. This approach is clean since<br><code>EquirectangularMapProjection<\/code> extends <code>AbstractProjection<\/code> which implements the <code>Projection<\/code> interface, so it satisfies the return type requirement.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The same pattern applies for <code>ECEFProjection<\/code> with SRID 4978. For the <code>NativeGeometryModel.getPointer()<\/code> issue, the real problem is that <code>getEarth()<\/code> returns a null pointer, but<br><code>ReflectInterop<\/code> catches the resulting NPE and passes 0 as the earth pointer to <code>registerModelImpl<\/code>, which should work fine anyway.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The key fix is implementing <code>getProjectionImpl<\/code> to return a Java <code>EquirectangularMapProjection<\/code> for SRID 4326, which will properly support the <code>getSpatialReferenceID()<\/code> call in<br><code>MapSceneModel<\/code>. I need to check whether <code>MapSceneModel<\/code> makes any JNI calls on the projection object itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Two problems clear now:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>NativeGeometryModel.getPointer(model.earth) NPE because getEarth stub returns null \u2014 caught by ReflectInterop as warning<\/li>\n\n\n\n<li>getProjectionImpl(4326) JNI stub returns null \u2192 MapSceneModel.:72 NPE The fix: implement getProjectionImpl to return a real Java EquirectangularMapProjection instance, and getEarth to return a non-null Pointer:<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read MapSceneModel constructor and registerModel<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">echo &#8220;=== MapSceneModel constructor (critical path lines 60-130) ===&#8221;<br>find &#8220;$ATAK_SRC\/takkernel&#8221; -name &#8220;MapSceneModel.java&#8221; | head -1 | xargs -I{} sed -n &#8216;60,140p&#8217; {}\u2026<br>\u2514 288 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I understand the full picture:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>EquirectangularMapProjection.DISPLAY_MODEL = MapProjectionDisplayModel.createDefaultLLAPlanarModel(4326)<\/code> creates a <code>MapProjectionDisplayModel<\/code> using the Java constructor with<br><code>DEFAULT_PLANE<\/code> (a pure Java <code>Plane<\/code> object). This is NOT a JNI call &#8211; <code>createDefaultLLAPlanarModel<\/code> is a Java method!<\/li>\n\n\n\n<li>Inside <code>MapProjectionDisplayModel(int srid, GeometryModel earth, ...)<\/code> constructor:\n<ul class=\"wp-block-list\">\n<li><code>GeometryModel_interop.getPointer(earth)<\/code> &#8211; gets the native pointer of the <code>Plane<\/code> object<\/li>\n\n\n\n<li>Since <code>Plane<\/code> isn&#8217;t a <code>NativeGeometryModel<\/code>, <code>getPointer<\/code> returns 0, triggering the JNI <code>wrap<\/code> call which returns a shared pointer \u2713<\/li>\n\n\n\n<li><code>this.pointer<\/code> gets set to that shared pointer<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>When <code>registerModel(model)<\/code> is called, the pointer type check passes and <code>registerImpl<\/code> is invoked with the shared pointer.<ul><li>This makes a JNI call to <code>registerModelImpl<\/code>, which internally tries to get the pointer for <code>model.earth<\/code> (the <code>DEFAULT_PLANE<\/code>)<\/li><li>Since <code>Plane<\/code> isn&#8217;t a <code>NativeGeometryModel<\/code>, <code>getPointer<\/code> would return 0 again, but I need to verify what the actual <code>getPointer<\/code> implementation does in this case.<\/li><\/ul>Actually, looking at the stack trace more carefully\u2014the NPE is happening when trying to read the <code>.raw<\/code> field from a null <code>Pointer<\/code> object. This means the <code>NativeGeometryModel<\/code><br>instance itself exists, but its <code>pointer<\/code> field is null. So somewhere a <code>NativeGeometryModel<\/code> was created without properly initializing its pointer. The issue traces back to <code>GeometryModel_interop.create()<\/code> being called with a null pointer from <code>getEarth(pointer.raw)<\/code>, which then creates a <code>NativeGeometryModel<\/code> with a null<br>pointer field. When <code>getPointer()<\/code> is later called on this model, it tries to access <code>.raw<\/code> on that null pointer and crashes. I need to figure out which constructor path is creating<br>this uninitialized model\u2014either the one that uses <code>GeometryModel_interop.getPointer()<\/code> on the earth parameter, or something else. Looking at the <code>createDefaultLLAPlanarModel<\/code> path, it uses the first constructor with <code>DEFAULT_PLANE<\/code> directly, so it doesn&#8217;t go through <code>getEarth()<\/code>. That means the NPE must be<br>coming from the second constructor path where <code>GeometryModel_interop.create()<\/code> is called. The real question is what happens when the native <code>registerImpl<\/code> method tries to access<br><code>model.earth<\/code> through reflection\u2014since <code>model.earth<\/code> is a <code>Plane<\/code> object, not a <code>NativeGeometryModel<\/code>, the reflection call might be failing or returning something unexpected. The NPE message &#8220;on a null object reference&#8221; suggests something tried to access <code>.raw<\/code> on a null object, which means there&#8217;s a <code>NativeGeometryModel<\/code> instance somewhere with a null<br><code>.pointer<\/code> field. I&#8217;m thinking <code>GeometryModel_interop.getPointer()<\/code> is being called on the <code>Plane<\/code> object through reflection, and that&#8217;s where things are breaking down\u2014either it&#8217;s<br>not finding the right method or it&#8217;s returning null instead of a valid pointer. Looking at the <code>wrap<\/code> JNI call in the constructor, if <code>getPointer<\/code> returns 0 for non-native objects, then <code>this.pointer = wrap(srid, earth, ...)<\/code> gets called with that 0 value, and<br>the stub returns a shared pointer. But I need to trace through whether <code>wrap<\/code> is actually returning null somewhere, or if the reflection call itself is failing and leaving the<br>pointer uninitialized. The real issue might be in <code>registerImpl<\/code> \u2014 when it tries to access <code>model.earth<\/code> through reflection, it&#8217;s still getting the <code>DEFAULT_PLANE<\/code> object, and then calling <code>getPointer<\/code> on<br>that. Since <code>Plane<\/code> isn&#8217;t a <code>NativeGeometryModel<\/code>, the <code>getPointer<\/code> method\u2026 Checking the actual implementation\u2026 Wait, I&#8217;m overthinking this. The ReflectInterop warning is just a side effect \u2014 the actual fatal error happens later when <code>MapSceneModel<\/code> tries to call<br><code>projection.getSpatialReferenceID()<\/code> but <code>projection<\/code> is null. So even though something fails during <code>ProjectionFactory<\/code> initialization, the class still loads successfully, and then<br>when we try to get a projection, we hit the null pointer. The fix is straightforward: I need to make <code>getProjectionImpl<\/code> return an actual <code>Projection<\/code> object instead of null. For SRID 4326, I should return an <code>EquirectangularMapProjection<\/code><br>instance. But I need to verify that this Java implementation will work correctly with <code>MapSceneModel<\/code> \u2014 specifically, that it properly implements <code>getSpatialReferenceID()<\/code>. Writing<br>JNI code\u2026 Now I&#8217;m thinking through how <code>MapSceneModel<\/code> uses the projection once it&#8217;s instantiated\u2014it takes the projection object along with display parameters like DPI, dimensions, focus<br>point, and camera settings to initialize the scene model in the takkernel library.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now I have the full picture. The fix:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>ProjectionFactory.getProjectionImpl(4326) JNI stub returns null \u2014 need to return a real EquirectangularMapProjection Java instance<\/li>\n\n\n\n<li>The ReflectInterop NPE in registerImpl is a caught warning (non-fatal) Let me check com.atakmap.map.MapSceneModel line 72:<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check MapSceneModel constructor and ECEFProjection<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">echo &#8220;=== com.atakmap.map.MapSceneModel lines 60-120 ===&#8221;<br>find &#8220;$ATAK_SRC\/takkernel&#8221; -name &#8220;MapSceneModel.java&#8221; -path &#8220;<em>\/atakmap\/map\/<\/em>&#8221; | head -1 | xargs -I{} sed -n &#8216;1,20p&#8217; {}\u2026<br>\u2514 149 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see the full picture. The <code>com.atakmap.map.MapSceneModel<\/code> constructor calls <code>create(...)<\/code> which is a JNI method returning a <code>Pointer<\/code>. Our auto-stub for<br><code>Java_com_atakmap_map_MapSceneModel_create<\/code> returns <code>makeSharedPointer(env, 1L)<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then <code>init()<\/code> calls <code>getProjection(this.pointer.raw)<\/code> \u2192 JNI, which returns a srid. Our stub returns 0. Then <code>ProjectionFactory.getProjection(0)<\/code> \u2192 <code>getProjectionImpl(0)<\/code> \u2192 returns<br>null.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then <code>this.mapProjection = null<\/code>. But the outer <code>gov.tak.api.engine.map.MapSceneModel<\/code> in GLMapView is constructing with <code>ProjectionFactory.getProjection(4326)<\/code>, which also returns<br>null from our stub, so that&#8217;s passing null where a projection is expected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the takkernel <code>MapSceneModel<\/code> constructor, it immediately calls <code>proj.getSpatialReferenceID()<\/code> on that null reference, causing an NPE. The fix is to have<br><code>getProjectionImpl(4326)<\/code> return an actual <code>EquirectangularMapProjection<\/code> instead of null.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But then there&#8217;s another layer: the <code>MapSceneModel<\/code> constructor delegates to a JNI <code>create()<\/code> method that returns a pointer, and during initialization it tries to fetch the display<br>model by calling <code>getProjection()<\/code> on that pointer, which our stub returns 0 for, leading to another null dereference downstream.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tracing through the initialization chain, the JNI stubs are returning zeros and null pointers across the board\u2014<code>getWidth<\/code> and <code>getHeight<\/code> both return 0,<br><code>ProjectionFactory.getProjection(0)<\/code> returns null, and the matrix operations are creating <code>NativeMatrix<\/code> wrappers around the stub pointers, which might actually work but the whole<br>thing is built on a foundation of mock data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real issue is that <code>MapSceneModel<\/code> is being instantiated with a null projection, and when it tries to call <code>proj.getSpatialReferenceID()<\/code> at line 72, it crashes. The projection<br>comes from <code>ProjectionFactory.getProjection(4326)<\/code>, so I need to make sure <code>getProjectionImpl(4326)<\/code> actually returns a valid <code>EquirectangularMapProjection<\/code> instead of null. Looking<br>at the <code>MapSceneModel<\/code> constructor call chain through <code>create()<\/code>, it&#8217;s passing through a series of parameters like DPI, dimensions, spatial reference ID, coordinates, and camera<br>settings.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now tracing through the initialization, the JNI stub returns a valid pointer, but then when <code>init()<\/code> tries to fetch the projection using that ID, it gets null back from the JNI<br>layer, which cascades into <code>ProjectionFactory.getProjection(0)<\/code> returning null. The issue is that the outer <code>gov.tak<\/code> version of <code>MapSceneModel<\/code> also depends on having a valid<br>projection to initialize properly, so if the inner implementation&#8217;s mapProjection is null, it could NPE when trying to access its spatial reference ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the actual code, there&#8217;s a null check that prevents the NPE\u2014if <code>_impl.mapProjection<\/code> is null, then <code>this.mapProjection<\/code> just stays null too. That&#8217;s fine for now, though<br>ATAK&#8217;s render code will eventually try to use it and might fail there.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The immediate solution is to implement <code>getProjectionImpl<\/code> to return the right projection types based on the spatial reference ID\u2014<code>EquirectangularMapProjection<\/code> for 4326 and<br><code>ECEFProjection<\/code> for 4978. Then I need to look at how <code>MapSceneModel.create()<\/code> returns a pointer and trace through the camera field assignments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The camera&#8217;s modelView and projection matrices come from <code>_impl<\/code>, and in the initialization code they&#8217;re being wrapped through JNI stubs. The issue is that <code>getForward<\/code> and<br><code>getInverse<\/code> are supposed to return pointers, but our auto-generated stubs are returning null instead of the actual matrix data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When <code>Matrix_interop.create(null, this)<\/code> gets called with a null pointer, it creates a <code>NativeMatrix<\/code> with a null pointer, which will throw a null pointer exception as soon as the<br>matrix is actually used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the stub implementations for <code>getForward<\/code>, <code>getInverse<\/code>, and <code>getCameraProjection<\/code> \u2014 they&#8217;re returning null, but they should be returning shared pointers since they&#8217;re<br>JNI methods that return jobjects. The issue is they don&#8217;t match the pattern we use to identify which stubs need to return shared pointers, so they&#8217;re not in our <code>MUST_RETURN_SHARED<\/code><br>list. I need to check the actual JNI signatures for <code>MapSceneModel<\/code> to understand what these methods should be returning.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actually, I&#8217;m overcomplicating this. The immediate crash is <code>getProjectionImpl(4326)<\/code> returning null, which then gets passed to <code>proj.getSpatialReferenceID()<\/code> on line 72 of<br><code>MapSceneModel.create()<\/code>. Let me fix that first and see what the next error is. I need to examine the exact lines around 563 in GLMapView to understand what&#8217;s being instantiated and<br>where the null projection is coming from.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read GLMapView lines 550-575<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;550,575p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 26 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Confirmed. <code>ProjectionFactory.getProjection(4326)<\/code> is called and it returns null. Then <code>new MapSceneModel(..., null, ...)<\/code> \u2192 <code>proj.getSpatialReferenceID()<\/code> \u2192 NPE.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix: implement <code>Java_com_atakmap_map_projection_ProjectionFactory_getProjectionImpl<\/code> to return an <code>EquirectangularMapProjection<\/code> instance for 4326.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let me also check if <code>ProjectionFactory.getProjection<\/code> can just short-circuit before calling <code>getProjectionImpl<\/code> if the DISPLAY_MODEL is registered. Looking at the earlier code:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The method checks the cache first, but if there&#8217;s a cache miss, it always calls <code>getProjectionImpl<\/code> regardless. So the native implementation needs to handle SRID 4326 and return a<br>valid projection instead of null.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at how this projection gets used downstream, <code>MapSceneModel<\/code> receives it and immediately extracts the spatial reference ID to pass into a native constructor, which means the<br>projection object needs to be properly initialized before it reaches that point. I&#8217;m tracing through what happens when these null pointers get passed<br>around\u2014<code>Matrix_interop.create()<\/code> will likely return null or throw an exception when given a null pointer, and then the camera initialization tries to access properties on<br>potentially null objects, which could cause issues downstream.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real fix is to make sure <code>getProjectionImpl<\/code> returns the right projection type based on the EPSG code, and ensure the matrix getter methods return actual non-null pointers<br>instead of stubs. Continuing to trace through the interop layer, the <code>create<\/code> method checks if the pointer is null or invalid and returns null if so. This means when our stubs<br>return nullptr for those camera matrix methods, the interop creates null references, which then propagate through to leave <code>_impl.camera.modelView<\/code> and <code>_impl.camera.projection<\/code> as<br>null values in the initialization chain.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rather than keep chasing through all these layers, I should just fix the immediate NPE in <code>getProjectionImpl<\/code> and mark a broader set of methods as needing to return SHARED pointers<br>instead of raw ones.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now I&#8217;m looking at how <code>Matrix_interop.create()<\/code> handles null pointers to understand what happens when we pass null instead of a valid pointer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read Interop.create and NativeMatrix<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== Interop.java create method ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;Interop.java&#8221; | head -1 | xargs cat 2&gt;\/dev\/null | grep -A20 &#8220;public T create&#8221;\u2026<br>\u2514 52 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check ReflectInterop.create behavior with null<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br># Find the actual Interop implementation (ReflectInterop)<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;ReflectInterop.java&#8221; | head -1 | xargs sed -n &#8216;1,80p&#8217; 2&gt;\/dev\/null\u2026<br>\u2514 9 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read com.atakmap.interop.Interop<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;Interop.java&#8221; -path &#8220;<em>\/atakmap\/interop\/<\/em>&#8221; | head -1 | xargs sed -n &#8216;1,120p&#8217; 2&gt;\/dev\/null<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 121 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 OK so <code>Interop.create(pointer, owner)<\/code> is abstract &#8211; it&#8217;s implemented by a concrete class registered via <code>registry<\/code>. The <code>ReflectInterop<\/code> class must implement this. If <code>pointer<\/code> is<br>null, the concrete implementation probably handles it by checking for null.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But I don&#8217;t need to trace this further. The key insight is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>Matrix_interop.create(null, this)<\/code> might return null or might NPE depending on the implementation<\/li>\n\n\n\n<li>The important thing is: <code>getForward(1L)<\/code> returns null from our stub \u2192 <code>Matrix_interop.create(null, this)<\/code> likely returns null \u2192 <code>this.forward = null<\/code><\/li>\n\n\n\n<li>Later when the MapSceneModel is used for rendering, accessing <code>this.forward.transform(...)<\/code> would NPE So I need to fix the stub methods to return actual shared pointers instead of null, and also ensure <code>getProjectionImpl(4326)<\/code> returns a real <code>EquirectangularMapProjection<\/code> rather<br>than a placeholder. Looking at the JNI method signatures in MapSceneModel.java, I see that <code>getProjection<\/code> returns an int (the SRID), while <code>getForward<\/code>, <code>getInverse<\/code>, <code>getCameraProjection<\/code>, and<br><code>getCameraModelView<\/code> all return Pointers. The current stub for <code>getProjection<\/code> returns 0, which causes <code>ProjectionFactory.getProjection(0)<\/code> to be called downstream and likely<br>returns null or an invalid projection. I need to change the stub to return 4326 as the default SRID instead, so that when <code>MapSceneModel.init()<\/code> calls <code>ProjectionFactory.getProjection(getProjection(this.pointer.raw))<\/code>,<br>it gets a valid EquirectangularMapProjection rather than null. For the Pointer-returning methods like <code>getForward<\/code> and <code>getInverse<\/code>, I need to check if my regex pattern is matching<br>them correctly. Looking at my <code>jni_signatures.txt<\/code>, I can filter by return type \u2014 if <code>ret_type == 'jobject'<\/code>, then I know it returns a Pointer and should be added to the MUST_RETURN_SHARED list.<br>This way I can expand the pattern to catch methods like <code>getForward<\/code>, <code>getInverse<\/code>, <code>getCameraProjection<\/code>, and similar getter methods that return objects. and not in the<br>MUST_RETURN_SHARED regex, return null. But for MapSceneModel getter methods that return Pointer, they&#8217;re <code>jobject<\/code> and not in the pattern. Better approach: add a separate regex pattern for methods that return Pointer (jobject) that are getter methods for native objects, or just add <code>get[A-Z]<\/code> to the pattern for<br>MapSceneModel specifically. Actually, the cleanest solution is to add a list of specific method names that need to return non-null Pointer. Once I fix <code>getProjectionImpl<\/code>, the MapSceneModel constructor will work. Then during <code>init()<\/code>, calls like <code>getForward(1L)<\/code> and <code>getInverse(1L)<\/code> will return null, which gets passed<br>to <code>Matrix_interop.create()<\/code> and likely returns null, leaving <code>this.forward<\/code> and <code>this.inverse<\/code> as null. This will cause NPEs later in the render loop, but the immediate crash is<br><code>getProjectionImpl<\/code>. Let me fix that first and make <code>getProjection(int)<\/code> return 4326 for MapSceneModel. I need to figure out the JNI signature for <code>MapSceneModel.getProjection(long pointer)<\/code>.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now I have the full picture. Three fixes needed:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>getProjectionImpl(4326) \u2192 return Java EquirectangularMapProjection instance<\/li>\n\n\n\n<li>MapSceneModel.getProjection(long) \u2192 return 4326 (not 0) so mapProjection is populated<\/li>\n\n\n\n<li>MapSceneModel.getForward\/getInverse\/getCameraProjection\/getCameraModelView \u2192 return SHARED Pointer<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read \/tmp\/takstub\/jni\/tak_engine_stub.cpp lines 330-365<br>\u2514 36 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit \/tmp\/takstub\/jni\/tak_engine_stub.cpp (+52)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now add the new hand-implemented methods to HAND_IMPL in the Python generator and regenerate to remove them from auto-stubs:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Regenerate stubs removing new hand-impl methods<br>$ python3 &lt;&lt; &#8216;PYEOF&#8217;<br>import re<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">with open(&#8216;\/tmp\/jni_signatures.txt&#8217;) as f:<br>lines = [l.strip() for l in f if l.strip()]\u2026<br>\u2514 114 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Splice stubs and rebuild native lib<br>$<br># Splice new auto-stubs into tak_engine_stub.cpp<br>python3 -c &#8220;<br>lines = open(&#8216;\/tmp\/takstub\/jni\/tak_engine_stub.cpp&#8217;).readlines()<br>cut = 0\u2026<br>\u2514 27 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Deploy and test new build<br>$<br>JNILIBS=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main\/atak\/ATAK\/app\/src\/main\/jniLibs\/arm64-v8a<br>cp \/tmp\/takstub\/libs\/arm64-v8a\/libtakengine.so &#8220;$JNILIBS\/&#8221;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">export JAVA_HOME=\/opt\/amazon-corretto-11.0.30.7.1-linux-x64\u2026<br>\u2514 77 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u276f Consider: &#8216;Nice \u2014 this is exactly the kind of job where a <strong>tiny bit of automation saves you from a thousand paper cuts<\/strong>. The idea is:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Scan the ATAK native source for every <code>JNIEXPORT<\/code> function.<\/li>\n\n\n\n<li>Extract the full function signature.<\/li>\n\n\n\n<li>Generate a <code>.cpp<\/code> file with <strong>stub implementations<\/strong> that compile.<\/li>\n\n\n\n<li>Return sensible defaults so the JVM doesn\u2019t crash.<\/li>\n\n\n\n<li>Compile the stub library as <code>libtakengine.so<\/code>. Below is a <strong>practical pattern<\/strong> that works well against the ATAK source tree.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 1. Directory Layout (example)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK\/\n    takengine\/\n       src\/main\/cpp\/\n            Globe.cpp\n            MapRenderer.cpp\n            DatabaseImpl.cpp\n            ...\n    tools\/\n        generate_jni_stubs.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You run the generator against the <strong>native cpp folder<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 2. Python Script: Extract + Generate Stubs<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Save as:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  tools\/generate_jni_stubs.py<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>  import os\n  import re\n\n  SOURCE_DIR = \"..\/takengine\/src\/main\/cpp\"\n  OUTPUT_FILE = \"jni_stubs.cpp\"\n\n  # Regex that captures JNIEXPORT signatures\n  JNI_PATTERN = re.compile(\n      r'(JNIEXPORT\\s+&#91;^\\(]+\\s+JNICALL\\s+&#91;^\\(]+\\(&#91;^\\)]*\\))',\n      re.MULTILINE\n  )\n\n  def default_return(signature):\n      \"\"\"Return safe default values based on return type\"\"\"\n      if \"jboolean\" in signature:\n          return \"return JNI_FALSE;\"\n      if \"jint\" in signature:\n          return \"return 0;\"\n      if \"jlong\" in signature:\n          return \"return 0;\"\n      if \"jfloat\" in signature:\n          return \"return 0.0f;\"\n      if \"jdouble\" in signature:\n          return \"return 0.0;\"\n      if \"jobject\" in signature:\n          return \"return nullptr;\"\n      if \"jstring\" in signature:\n          return \"return nullptr;\"\n      if \"void\" in signature:\n          return \"\"\n      return \"return 0;\"\n\n  def extract_signatures():\n      signatures = &#91;]\n\n      for root, _, files in os.walk(SOURCE_DIR):\n          for f in files:\n              if f.endswith(\".cpp\") or f.endswith(\".cc\"):\n                  path = os.path.join(root, f)\n\n                  with open(path, \"r\", errors=\"ignore\") as src:\n                      text = src.read()\n\n                      matches = JNI_PATTERN.findall(text)\n                      signatures.extend(matches)\n\n      return signatures\n\n\n  def generate_stub_file(signatures):\n      with open(OUTPUT_FILE, \"w\") as out:\n\n          out.write('#include &lt;jni.h&gt;\\n')\n          out.write('#include &lt;android\/log.h&gt;\\n\\n')\n\n          out.write('#define LOG_TAG \"ATAK_STUB\"\\n\\n')\n\n          for sig in signatures:\n              ret = default_return(sig)\n\n              out.write(f\"{sig} {{\\n\")\n\n              out.write(\n                  '    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, \"Stub called\");\\n'\n              )\n\n              if ret:\n                  out.write(f\"    {ret}\\n\")\n\n              out.write(\"}\\n\\n\")\n\n  def main():\n      sigs = extract_signatures()\n\n      print(f\"Found {len(sigs)} JNI methods\")\n\n      generate_stub_file(sigs)\n\n      print(f\"Generated {OUTPUT_FILE}\")\n\n  if __name__ == \"__main__\":\n      main()<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 3. Run It<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  python3 generate_jni_stubs.py<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Found 1048 JNI methods\n  Generated jni_stubs.cpp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Takes ~1\u20132 seconds.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 4. Example Generated Stub<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Output looks like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  JNIEXPORT jint JNICALL\n  Java_com_atakmap_map_Globe_getFullEquitorialExtentPixels\n  (JNIEnv *env, jclass clazz, jdouble dpi)\n  {\n      __android_log_print(ANDROID_LOG_INFO, \"ATAK_STUB\", \"Stub called\");\n      return 0;\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You then <strong>replace specific methods manually<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  JNIEXPORT jint JNICALL\n  Java_com_atakmap_map_Globe_getFullEquitorialExtentPixels\n  (JNIEnv *env, jclass clazz, jdouble dpi)\n  {\n      const double R = 6378137.0;\n      const double INCHES_PER_METER = 37.37;\n\n      double circumference = 2.0 * 3.141592653589793 * R;\n      double pixels = circumference * INCHES_PER_METER * dpi;\n\n      return (jint)pixels;\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 5. Build the Stub Engine<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Minimal <code>CMakeLists.txt<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  cmake_minimum_required(VERSION 3.10)\n\n  project(takengine_stub)\n\n  add_library(takengine SHARED\n      jni_stubs.cpp\n  )\n\n  find_library(log-lib log)\n\n  target_link_libraries(\n      takengine\n      ${log-lib}\n  )<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 6. Build with Android NDK<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  export ANDROID_NDK=$HOME\/Android\/Sdk\/ndk\/25.2.9519653\n\n  cmake \\\n   -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK\/build\/cmake\/android.toolchain.cmake \\\n   -DANDROID_ABI=arm64-v8a \\\n   -DANDROID_PLATFORM=android-21 \\\n   -B build\n\n  cmake --build build<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  libtakengine.so<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 7. Drop the Engine Into ATAK<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Place it here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  atak\/app\/src\/main\/jniLibs\/arm64-v8a\/libtakengine.so<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then build:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  .\/gradlew assembleDebug<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 8. Runtime Debugging<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Watch calls in real time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  adb logcat | grep ATAK_STUB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You\u2019ll see which JNI methods ATAK hits during startup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Implement those <strong>one by one<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 9. Result<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of implementing <strong>1048 JNI methods manually<\/strong>, you now have:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  1048 auto-generated stubs\n  +\n  10\u201320 real implementations<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s enough to boot the UI and start inserting the <strong>Cesium rendering backend<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 10. Pro Tip (Huge Time Saver)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Modify the generator to create <strong>per-class stub files<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  jni_globe_stubs.cpp\n  jni_database_stubs.cpp\n  jni_projection_stubs.cpp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That makes replacing chunks with real implementations much easier later.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can also show you a <strong>much more advanced version of this generator<\/strong> that:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2022 parses JNI signatures directly from compiled <code>.class<\/code> files<br>\u2022 auto-creates matching C++ stubs<br>\u2022 builds a complete <code>libtakengine.so<\/code> automatically<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That version can regenerate the entire compatibility layer in <strong>under 0.5 seconds<\/strong>. \ud83d\ude80<br>&#8216;<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 We&#8217;ve crashed frontier advanced! Now the crash is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  java.lang.IllegalArgumentException: left == right\n      at android.opengl.Matrix.orthoM(Matrix.java:447)\n      at com.atakmap.opengl.GLES20FixedPipeline.glOrthof(GLES20FixedPipeline.java:171)\n      at com.atakmap.map.opengl.GLMapView.sync(GLMapView.java:1422)\n      at com.atakmap.map.opengl.GLMapView.&lt;init&gt;(GLMapView.java:572)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The issue is that <code>GLMapView.sync()<\/code> is calling <code>glOrthof<\/code> with identical left and right values because our MapSceneModel stub is returning 0 for both width and height, which<br>cascades into the ortho matrix calculation getting zero dimensions.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  the scene dimensions. Since width=0 and height=0, it might compute `left=right=0` or similar.\n\n  Let me fix `MapSceneModel.getWidth` and `MapSceneModel.getHeight` to return the actual dimensions... but wait, the stub doesn't know the actual dimensions!\n\n  Actually, looking at the constructor, `width` and `height` are passed as arguments to the `create(...)` JNI call. But our `create` stub ignores them and returns a pointer to fake \n  memory. So `getWidth(1L)` returns 0.\n\n  We need to store the width\/height in our stub's MapSceneModel state and return them from `getWidth`\/`getHeight`. But this is getting complicated...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Wait, I need to rewrite this, not copy it. Let me try again:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The issue is that the scene dimensions are coming back as zero, which makes the ortho projection collapse. The <code>MapSceneModel<\/code> constructor receives width and height arguments that<br>get passed to the JNI <code>create<\/code> call, but our stub ignores those values and just returns a fake pointer. So when <code>getWidth<\/code> and <code>getHeight<\/code> are called later, they return 0. I need to<br>make the stub actually store and return those dimension values instead of hardcoding zeros.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest fix is to create a state struct that holds the width, height, and DPI from the create call, then have the getter methods return those stored values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But I&#8217;m realizing the current approach of returning a fixed pointer value (1L) won&#8217;t work if multiple MapSceneModel instances are created\u2014they&#8217;d all reference the same state. I need<br>to allocate actual memory for each instance and return its address as the pointer, so each one maintains its own independent state.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Crash advanced significantly! GLMapView.sync() gets left==right in glOrthof because MapSceneModel.getWidth\/getHeight return 0. Need to store real dimensions in the MapSceneModel<br>stub:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check GLMapView.sync and MapSceneModel.create signature<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== GLMapView.sync around line 1422 ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;1410,1440p&#8217; {}\u2026<br>\u2514 117 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see what&#8217;s needed. The crash is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  GLES20FixedPipeline.glOrthof(this.currentPass.left, this.currentPass.right, this.currentPass.bottom, this.currentPass.top, (float) this.currentPass.scene.camera.near, (float) \n  this.currentPass.scene.camera.far);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>left == right<\/code> means the viewport bounds are both 0. This comes from <code>currentPass.restore(this)<\/code> which reads the camera data from the scene. Since our MapSceneModel returns zeros<br>for everything, <code>camera.near = camera.far = 0<\/code> also, and <code>left = right = 0<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The root cause is that <code>getWidth(1L)<\/code> and <code>getHeight(1L)<\/code> return 0, so the scene dimensions are zero, which cascades into the ortho matrix calculation. I need to create a proper<br><code>MapSceneState<\/code> struct that holds the actual width, height, dpi, and focus coordinates, then initialize it properly in the <code>create<\/code> method.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But actually, tracing through the code flow\u2014<code>currentPass.restore(this)<\/code> is calling JNI methods like <code>get_left<\/code>, <code>get_right<\/code>, <code>get_bottom<\/code>, <code>get_top<\/code> on the GLMapView pointer, and<br>these stubs are all returning 0. That&#8217;s why <code>left == right<\/code>, which causes the crash in <code>glOrthof<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real problem is that our fake native GLMapView object (with value 1L) has no actual state, so all these viewport queries return 0. I&#8217;m weighing two approaches: either store real<br>screen dimensions in the MapSceneModel when it&#8217;s created, or populate the native GLMapView object with actual viewport data so the JNI stubs can return meaningful values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Option A seems cleaner since we already have width and height available at creation time. But the crash itself\u2014where <code>left == right<\/code> in the orthographic projection\u2014suggests the<br>issue originates from <code>currentPass<\/code> state, which gets restored from somewhere in the GLMapView. I need to trace through the PassState.restore() method to understand where these<br>viewport bounds actually come from.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now I need to understand where currentPass.left\/right come from in sync():<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find PassState.restore to trace left\/right source<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== GLMapView.PassState.restore ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;GLMapView.java&#8221; | head -1 | xargs grep -n &#8220;class PassState|void restore|left|right|bottom|top|_left|_right&#8221; | grep -v &#8220;\/\/&#8221; | head -40\u2026<br>\u2514 49 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see it! <code>GLMapView<\/code> is created with <code>left, bottom, right, top<\/code> parameters. Looking at how it&#8217;s created:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  public GLMapView(GLMapSurface surface, int left, int bottom, int right, int top) {\n      this(surface, surface.getMapView().getGlobe(), left, bottom, right, top);\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And it&#8217;s created from <code>GLMapSurface<\/code>. Let me check what left\/bottom\/right\/top values are passed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But more importantly &#8211; <code>currentPass.left\/right\/bottom\/top<\/code> &#8211; how are these set? Are they set by the <code>sync()<\/code> method from some JNI call? Or are they the constructor parameters?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m tracing through the rendering pipeline where <code>currentPass<\/code> gets used in the orthographic projection call, and I&#8217;m seeing JNI stubs that retrieve left\/right values but they&#8217;re<br>returning zero. I need to find where <code>restore()<\/code> actually populates these viewport bounds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the constructor and the <code>GLMapView.create<\/code> stub, I can see the issue: the native creation method receives the viewport parameters but our stub doesn&#8217;t store them, so when<br><code>sync()<\/code> later calls the getter stubs, they have nothing to return. I need to make the stub actually cache those left\/bottom\/right\/top values so they&#8217;re available when the sync<br>method reads them back. So the JNI signature takes left, bottom, right, top coordinates when creating GLMapView, and these same bounds are accessible through getter methods. The<br>crash happens because <code>glOrthof<\/code> is being called with left==right (both zero), which comes from the camera&#8217;s near\/far values that our MapSceneModel is returning as zeros.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real issue is that <code>currentPass.left\/right<\/code> are being populated from <code>currentPass.restore(this)<\/code>, which reads from native state. I need to trace through the PassState class and<br>understand what its restore method actually does.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Trace currentPass.left\/right assignment<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} grep -n<br>&#8220;get_left|get_right|get_bottom|get_top|PassState|class State|void restore|left =|right =|bottom =|top =&#8221; {} | grep -v &#8220;\/\/|deprecated|javadoc|*&#8221; | head -40<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 25 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read PassState.restore and constructor initialization<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== PassState.restore (line 2618) ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;2618,2680p&#8217; {}\u2026<br>\u2514 166 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I understand the full picture:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>GLMapView(context, globe, left, bottom, right, top)<\/code> is called with <code>left=0, bottom=0, right=surface.width-1, top=surface.height-1<\/code><\/li>\n\n\n\n<li><code>create(ctxptr, globeptr, left, bottom, right, top, legacy)<\/code> JNI stub returns SHARED pointer, stores nothing<\/li>\n\n\n\n<li><code>currentScene.scene = new MapSceneModel(dpi, width, height, proj, ...)<\/code> &#8211; this calls <code>MapSceneModel_create<\/code> which our stub ignores the width\/height but stores 0<\/li>\n\n\n\n<li>A clone of the scene is created for the current pass, then <code>sync()<\/code> retrieves various state flags and calls <code>currentPass.restore(this)<\/code> to copy the viewport bounds into the main<br>view&#8217;s properties<\/li>\n\n\n\n<li>Finally, <code>glOrthof()<\/code> is invoked with those bounds to set up the orthographic projection, but I&#8217;m trying to trace where <code>currentPass.left\/right\/bottom\/top<\/code> actually get their<br>values from in the first place Looking at the PassState constructor, it copies viewport bounds directly from the GLMapView, but I need to figure out which constructor instantiates <code>currentPass<\/code> and whether those<br>bounds come from native synchronization or are set earlier in the flow.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read GLMapView.sync() from start to crash point<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;1380,1425p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 46 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see the full picture:1. <code>sync(this.pointer.raw, this, false)<\/code> and <code>sync(this.pointer.raw, this, true)<\/code> are JNI calls that populate <code>currentPass<\/code> and <code>currentScene<\/code><\/p>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><code>sync(long pointer, GLMapView view, boolean passState)<\/code> is a JNI method that reads the native state and fills in the Java view fields<\/li>\n\n\n\n<li>Since our stub for <code>sync<\/code> does nothing, <code>currentPass.left = 0, currentPass.right = 0, currentPass.top = 0, currentPass.bottom = 0<\/code><\/li>\n\n\n\n<li>Then <code>glOrthof(0, 0, 0, 0, 0, 0)<\/code> \u2192 <code>left == right<\/code> crash!<\/li>\n\n\n\n<li>The real issue is that the <code>sync<\/code> JNI method needs to properly initialize the viewport bounds, but implementing it fully would require native callbacks to Java. A simpler fix is<br>to just set <code>currentPass<\/code> to the actual viewport dimensions\u2014something like left=0, bottom=0, right=width-1, top=height-1. a full-screen view. The cleanest approach is to implement the <code>sync<\/code> JNI stub to call back to Java and set the pass boundaries based on the view dimensions. But that requires knowing the surface<br>dimensions from native code, which gets complicated with JNI callbacks. Looking at this differently: the <code>_right<\/code> and <code>_top<\/code> fields already get set correctly based on surface dimensions in the Java code, but the native sync is zeroing out <code>_left<\/code> and<br><code>_bottom<\/code>. I need to figure out how to preserve or properly initialize those boundary values instead of relying on the native state. to implement the sync JNI method to populate the<br>currentPass with correct values. The key fields are:<\/li>\n<\/ol>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>left<\/code>: left pixel bound (0)<\/li>\n\n\n\n<li><code>right<\/code>: right pixel bound (surface.width &#8211; 1)<\/li>\n\n\n\n<li><code>top<\/code>: top pixel bound (surface.height &#8211; 1)<\/li>\n\n\n\n<li><code>bottom<\/code>: bottom pixel bound (0)<\/li>\n\n\n\n<li><code>drawMapResolution<\/code>: the map resolution (OSMUtils.mapnikTileResolution(0))<\/li>\n\n\n\n<li><code>scene<\/code>: the MapSceneModel (already set in Java)<\/li>\n\n\n\n<li><code>camera.near\/far<\/code>: reasonable values for ortho projection The sync JNI method signature would be:<br><code>Java_com_atakmap_map_opengl_GLMapView_sync(JNIEnv*, jclass, jlong pointer, jobject glMapView, jboolean passState)<\/code> I need to implement this to get the surface dimensions from the GLMapView Java object via JNI reflection, then set the PassState fields (left, right, top, bottom) based on those<br>dimensions and apply a reasonable draw map resolution. Let me work through a simpler approach where I access the view&#8217;s surface to extract width and height, then populate the pass<br>state accordingly. I&#8217;m realizing the real issue is that <code>glOrthof<\/code> crashes when left equals right, so instead of trying to implement the full JNI callback mechanism, I should focus<br>on making sure the sync method populates the view&#8217;s PassState with valid orthographic bounds that won&#8217;t cause a crash. The sync JNI method needs to set those fields on the Java<br>object directly rather than trying to call back into Java. Let me check what initial left\/bottom\/right\/top values GLMapSurface passes to the GLMapView constructor to understand what valid bounds should look like.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find sync JNI signature and GLMapSurface viewport setup<br>$<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== GLMapSurface constructor &#8211; how GLMapView is created ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;GLMapSurface.java&#8221; | head -1 | xargs -I{} sed -n &#8216;100,200p&#8217; {}\u2026<br>\u2514 132 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see the key information:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><code>GLMapSurface<\/code> creates <code>GLMapView<\/code> with <code>left=0, bottom=0, right=GLMapView.MATCH_SURFACE, top=GLMapView.MATCH_SURFACE<\/code><\/li>\n\n\n\n<li><code>MATCH_SURFACE<\/code> is a special constant that triggers: <code>if (_right == MATCH_SURFACE) _right = _surface.getWidth() - 1; if (_top == MATCH_SURFACE) _top = _surface.getHeight() - 1;<\/code> So <code>_left = 0, _bottom = 0, _right = surface.width - 1, _top = surface.height - 1<\/code><\/li>\n\n\n\n<li>The <code>sync<\/code> JNI method receives a pointer and callback to populate the rendering pass state, but since my stub implementation does nothing, <code>currentPass.left<\/code> and<br><code>currentPass.right<\/code> both stay at 0, which causes <code>glOrthof(0, 0, ...)<\/code> to crash. I need to actually implement the <code>sync<\/code> method to set these values properly. The straightforward fix would be to access the Java <code>glMapView<\/code> object&#8217;s viewport fields and populate <code>currentPass<\/code> with the correct left\/right\/top\/bottom bounds, plus set<br><code>drawMapResolution<\/code> and the camera&#8217;s near\/far planes to non-zero values. However, that requires JNI callbacks which adds complexity. Let me reconsider\u2014since <code>sync<\/code> already receives<br>the Java <code>GLMapView<\/code> object, I should be able to directly access its viewport fields that were set during construction and use those to initialize <code>currentPass<\/code>. Looking at the constructor flow more carefully: <code>GLMapSurface<\/code> creates the view with <code>MATCH_SURFACE<\/code> dimensions, the constructor sets initial bounds, then calls <code>sync()<\/code> which<br>eventually restores values from <code>currentPass<\/code>\u2014but <code>currentPass<\/code> starts with zeros, causing the orthographic projection to fail. The issue is that <code>currentPass<\/code> needs to be populated<br>with actual viewport dimensions before <code>restore()<\/code> is called. So I need to populate those <code>currentPass<\/code> fields from the Java GLMapView&#8217;s <code>_left<\/code>, <code>_right<\/code>, <code>_top<\/code>, <code>_bottom<\/code> values<br>in the sync JNI method. But I should check when <code>MATCH_SURFACE<\/code> gets resolved to an actual dimension\u2014it looks like that happens when the property is accessed, converting it to the<br>surface width or height minus one. The problem is that during the GLMapView constructor when <code>sync()<\/code> gets called, the GLSurfaceView might not have been measured yet, so <code>getWidth()<\/code> could return 0, which would make<br><code>MATCH_SURFACE<\/code> resolve to -1 and break the bounds check. I need to figure out when the surface dimensions are actually available at construction time. So <code>MATCH_SURFACE<\/code> is a<br>sentinel value that gets resolved lazily\u2014when <code>getRight()<\/code> is called, if <code>_right<\/code> still equals <code>MATCH_SURFACE<\/code>, it calculates the actual width minus one and stores that. The native<br>side receives the raw constructor arguments including this sentinel value, which then gets resolved on demand rather than upfront. The issue is that our stub&#8217;s <code>sync<\/code> method doesn&#8217;t do anything, leaving <code>currentPass<\/code> with zeros instead of the resolved dimensions. I need to implement the JNI <code>sync<\/code> call to<br>actually invoke <code>getRight()<\/code> and <code>getTop()<\/code> on the Java object so the native side gets the real values. Writing JNI sync implementation\u2026 I&#8217;m realizing this approach is getting too complicated with all the JNI field access. Let me step back and try something simpler\u2014what if I just<br>patch the Java code directly to add a guard check before calling <code>glOrthof<\/code>, making sure the viewport dimensions are actually valid before passing them to the graphics call? Still<br>writing JNI code\u2026 Setting the remaining boundary fields and map resolution, then accessing the scene object to configure camera parameters\u2026 I&#8217;m realizing the PassState field is<br>likely &#8220;currentPass&#8221; with the type descriptor <code>Lcom\/atakmap\/map\/opengl\/GLMapView$PassState;<\/code> for the inner class. Looking at the source code, the left\/right\/top\/bottom fields are<br>integers based on how they&#8217;re initialized, so I need to use the int field type descriptors when accessing them through JNI. The crash shows <code>glOrthof<\/code> is being called with <code>left == right == 0<\/code>, which violates the requirement that these values be different. If I can set <code>right<\/code> to something like the<br>surface width minus one (around 2231), that would fix the immediate crash. But there&#8217;s also a secondary issue: <code>near == far == 0.0<\/code> would also crash since <code>glOrthof<\/code> requires these<br>to be different values. The priority is fixing the left\/right equality first, but I&#8217;ll need to address the near\/far issue as well. Looking at the Android Matrix.orthoM validation\u2026 Checking MapSceneModel.init() to see where camera.near and camera.far get their values\u2026 Implementing JNI stubs\u2026 Writing the JNI implementations\u2026<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Crash advanced significantly to glOrthof. Three fixes needed: implement sync JNI to set currentPass.left\/right\/top\/bottom from surface dims, and getCameraNear\/getCameraFar to return<br>non-zero values (they all return 0, failing the ortho matrix validity checks):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check PassState fields and camera JNI sigs<br>$<br># Verify the field names in PassState class<br>ATAK_SRC=\/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main<br>echo &#8220;=== PassState field declarations ===&#8221;<br>find &#8220;$ATAK_SRC&#8221; -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;2500,2570p&#8217; {}\u2026<br>\u2514 139 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I have all the info I need. The PassState class is actually <code>GLMapView.State<\/code> (a static inner class named <code>State<\/code>). Let me check the actual field:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the sync crash area:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  GLES20FixedPipeline.glOrthof(this.currentPass.left, this.currentPass.right, this.currentPass.bottom, this.currentPass.top, (float) this.currentPass.scene.camera.near, (float) \n  this.currentPass.scene.camera.far);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And the fields are: <code>currentPass.left\/right\/top\/bottom<\/code> (from <code>GLMapView.State<\/code> inner class), and <code>currentPass.scene.camera.near\/far<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The JNI type descriptor is <code>Lcom\/atakmap\/map\/opengl\/GLMapView$State;<\/code>. Looking at the copy method, <code>_right<\/code> gets assigned from <code>view._right<\/code>, and during MATCH_SURFACE resolution it<br>gets set to <code>_surface.getWidth() - 1<\/code>. But since sync doesn&#8217;t run in this case, <code>currentPass.right<\/code> remains unresolved at <code>MATCH_SURFACE<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now I&#8217;m tracing through the MapCamera initialization in <code>MapSceneModel.init()<\/code> where the near and far planes are being set to 0.0 from JNI calls. The issue is determining whether<br><code>this.camera<\/code> is a <code>com.atakmap.map.MapCamera<\/code> or <code>gov.tak.api.engine.map.MapCamera<\/code> \u2014 the crash is pointing to the latter, which comes from <code>currentPass.scene<\/code> being a<br><code>gov.tak.api.engine.map.MapSceneModel<\/code> from <code>GLMapView.State<\/code>. I&#8217;m seeing that the TAK Platform&#8217;s <code>MapSceneModel<\/code> wraps the internal <code>com.atakmap.map.MapSceneModel<\/code>, and during<br>initialization it copies the camera&#8217;s near and far values from the underlying implementation\u2014but those JNI calls are returning 0.0, which is causing the crash when trying to use<br>these values.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The fix is straightforward: the JNI stubs for <code>getCameraNear<\/code> and <code>getCameraFar<\/code> should return -1.0 and 1.0 respectively, which are standard orthographic projection bounds. However,<br>that only addresses the near\/far problem\u2014I still need to fix the left\/right\/top\/bottom viewport values through the sync JNI call.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the code, <code>currentPass<\/code> is a field of <code>GLMapView<\/code> with type <code>GLMapView.State<\/code>, which is a public static class with public fields, so JNI can access them directly. The<br>sync method needs to populate <code>currentPass.left\/right\/top\/bottom<\/code> from the viewport dimensions before the orthographic projection is applied.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find currentPass field type declaration<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs grep -n &#8220;currentPass|currentScene|PassState|State current&#8221; |<br>grep -E &#8220;PassState|State curr|currentPass.<em>=|currentScene.<\/em>=&#8221; | head -20<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 9 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 <code>currentPass<\/code> is of type <code>State<\/code> (which is <code>GLMapView.State<\/code>). JNI type: <code>Lcom\/atakmap\/map\/opengl\/GLMapView$State;<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fields <code>left<\/code>, <code>right<\/code>, <code>top<\/code>, <code>bottom<\/code> are <code>int<\/code> in <code>State<\/code>.<code>scene<\/code> is of type <code>MapSceneModel<\/code> (which package? Looking at line 554 where <code>currentScene.scene<\/code> is set to a<br><code>MapSceneModel<\/code> &#8211; since GLMapView uses <code>new MapSceneModel(...)<\/code> which from the import is <code>com.atakmap.map.MapSceneModel<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So <code>scene<\/code> is of type <code>com.atakmap.map.MapSceneModel<\/code>. Now I need to trace what <code>camera<\/code> is on that <code>MapSceneModel<\/code> class to understand the crash at line 1422 where<br><code>this.currentPass.scene.camera.near<\/code> is accessed. So <code>MapSceneModel<\/code> has a <code>camera<\/code> field that&#8217;s initialized as a <code>MapCamera<\/code> object, and the JNI code populates its properties like<br><code>near<\/code> and <code>far<\/code> from the native implementation. That means the crash is happening when trying to access the <code>near<\/code> property on this <code>MapCamera<\/code> instance through the scene object.<br><code>currentPass.scene.camera.far<\/code> is also 0.0. But I&#8217;m realizing there&#8217;s a type mismatch here\u2014<code>currentPass.scene<\/code> is being assigned from <code>currentScene.scene<\/code>, and I need to verify<br>whether <code>State.scene<\/code> is actually <code>com.atakmap.map.MapSceneModel<\/code> or the API version <code>gov.tak.api.engine.map.MapSceneModel<\/code>, since that affects which <code>camera<\/code> field is being used.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check GLMapView imports<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;1,30p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 30 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check GLMapView imports continued<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;30,60p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 32 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check GLMapView imports for MapSceneModel<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main -name &#8220;GLMapView.java&#8221; | head -1 | xargs -I{} sed -n &#8216;60,90p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 32 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 It&#8217;s <code>com.atakmap.map.MapSceneModel<\/code>. So <code>currentPass.scene<\/code> is of type <code>com.atakmap.map.MapSceneModel<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now, <code>com.atakmap.map.MapSceneModel<\/code> has a <code>camera<\/code> field. Looking at the <code>init()<\/code> code I saw earlier, <code>this.camera.near = _impl.camera.near<\/code> where <code>_impl<\/code> is also a<br><code>com.atakmap.map.MapSceneModel<\/code>.Actually wait, I&#8217;m confused about two MapSceneModel classes. There&#8217;s:1. <code>com.atakmap.map.MapSceneModel<\/code> (in takkernel, the main one used in<br>GLMapView)<\/p>\n\n\n\n<ol start=\"2\" class=\"wp-block-list\">\n<li><code>gov.tak.api.engine.map.MapSceneModel<\/code> (higher-level API) But looking at <code>GLMapView<\/code>, it imports <code>com.atakmap.map.MapSceneModel<\/code>. And <code>State.scene<\/code> is of type <code>MapSceneModel<\/code>, which resolves to <code>com.atakmap.map.MapSceneModel<\/code>. <code>com.atakmap.map.MapSceneModel<\/code> has a <code>camera<\/code> field\u2026 Let me find the <code>camera<\/code> field and its type. From the <code>init()<\/code> code in <code>com.atakmap.map.MapSceneModel<\/code>, it looks like the<br>camera is lazily initialized if it&#8217;s null. Now I need to look at the takkernel version specifically to understand how it initializes its display model, dimensions, and projection settings during the init process. I&#8217;m seeing<br>the JNI method signatures for camera properties like <code>getCameraNear<\/code> and <code>getCameraFar<\/code> in the <code>MapSceneModel<\/code> class. Looking at the <code>init()<\/code> method, I can see the wrapper<br><code>MapSceneModel<\/code> is copying camera properties from <code>_impl.camera<\/code> \u2014 so <code>_impl<\/code> must be the inner implementation class that actually holds the camera reference. I need to trace<br>through the MapSceneModel camera initialization to understand what type the camera field is and whether the JNI calls for near\/far clipping planes are actually being applied. Let me<br>look at the broader initialization code to see how the camera gets set up and what default values it starts with.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read MapSceneModel.init() camera section<br>$<br>find \/home\/spectrcyde\/NerfEngine\/assets\/AndroidTacticalAssaultKit-CIV-main\/takkernel -name &#8220;MapSceneModel.java&#8221; -path &#8220;<em>\/atakmap\/map\/<\/em>&#8221; | head -1 | xargs -I{} sed -n &#8216;125,220p&#8217; {}<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2514 97 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 Now I see the full picture. The <code>camera.near<\/code> and <code>camera.far<\/code> in <code>com.atakmap.map.MapSceneModel<\/code> come from:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>getCameraNear(pointer.raw)<\/code> \u2192 currently returns 0.0 from stub<\/li>\n\n\n\n<li><code>getCameraFar(pointer.raw)<\/code> \u2192 currently returns 0.0 from stubFix: add hand-implemented <code>getCameraNear<\/code> returning -1.0 and <code>getCameraFar<\/code> returning 1.0. Now for the <code>sync<\/code> JNI &#8211; it needs to populate <code>currentPass.left\/right\/top\/bottom<\/code>. The sync method signature is:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  private static native void sync(long pointer, GLMapView view, boolean passState);<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">From the call sites in GLMapView and the JNI grep results, I can confirm this is a static native method with the signature <code>(JNIEnv*, jclass, jlong, jobject, jboolean)<\/code>. I need to<br>implement this to set the pass state fields.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The tricky part is that when sync is called, the view&#8217;s boundary fields like <code>_right<\/code> are still set to <code>MATCH_SURFACE<\/code> rather than their actual values. I can&#8217;t just read those<br>fields directly\u2014I need to call the getter methods like <code>getRight()<\/code> which perform lazy resolution. But there&#8217;s a timing issue: at construction time when the GLSurfaceView is first<br>created, the surface dimensions might not be available yet since the view hasn&#8217;t been attached and measured, so <code>surface.getWidth()<\/code> could return 0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at the actual crash though\u2014both left and right are 0, which means they&#8217;re equal and causing glOrthof to fail. If the width were truly 0, then <code>right = getWidth() - 1 = -1<\/code>,<br>which would make left and right different and avoid the crash. So the problem is specifically that both coordinates are ending up as 0 rather than being offset.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let me trace through the GLMapView constructor flow to understand how these initial values are set. The GLMapSurface creates a GLMapView with MATCH_SURFACE flags, and I need to see<br>exactly where the coordinates get resolved. we&#8217;re just returning a fake pointer without storing anything, so when <code>sync()<\/code> gets called later, the native state has no actual values<br>to read back. The fix is straightforward: I can access the Java view object directly through JNI to grab <code>getRight()<\/code> and <code>getTop()<\/code>, then manually populate <code>currentPass<\/code> with the<br>correct bounds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Looking at how <code>getRight()<\/code> works, it resolves <code>MATCH_SURFACE<\/code> to the actual surface width, but that might return -1 if the surface hasn&#8217;t initialized yet, giving us invalid bounds<br>like <code>left=0, right=-1<\/code>. I also need to handle the <code>near<\/code> and <code>far<\/code> camera values separately.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My approach is to add <code>getCameraNear<\/code> and <code>getCameraFar<\/code> methods that return -1.0 and 1.0 respectively, then create a <code>sync<\/code> JNI method that pulls the current viewport dimensions<br>from the view and updates the pass state accordingly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Implementing the sync method now\u2026 The <code>MapSceneModel<\/code> constructor calls <code>init()<\/code> which invokes <code>getCameraNear\/Far()<\/code>, so once I fix those stubs to return -1.0 and 1.0, the camera<br>values will be set correctly on both <code>currentScene.scene<\/code> and <code>currentPass.scene<\/code>. Now I just need to handle setting the remaining <code>currentPass<\/code> fields like <code>left<\/code> and <code>right<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But I&#8217;m wondering if the clone constructor also triggers <code>init()<\/code> again\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Examining the clone constructor\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Implementing the sync JNI method\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When <code>getRight()<\/code> is called during construction, the <code>GLMapSurface<\/code> hasn&#8217;t been attached or measured yet, so <code>getWidth()<\/code> returns 0, making <code>_right = -1<\/code>. This gives us <code>left=0, right=-1, bottom=0, top=-1, near=-1.0, far=1.0<\/code> \u2014 all the inequality checks pass, so the <code>glOrthof<\/code> call won&#8217;t crash, though the ortho matrix with these inverted bounds will produce<br>an unusual projection.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I could work around this by stubbing in dummy values like <code>right=1, top=1<\/code> to avoid the crash, but that would create a tiny 1&#215;1 display. Instead, I should implement the sync JNI<br>call to properly fetch the surface dimensions, being careful with null checks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now I&#8217;m tracing through the initialization flow to see where the camera near\/far values get set and whether my stub approach will actually work for the full pipeline. after fix will<br>be -1.0<\/p>\n\n\n\n<ol start=\"3\" class=\"wp-block-list\">\n<li><code>currentPass.scene = new MapSceneModel(currentScene.scene)<\/code> &#8211; clone, calls clone() JNI then init()<\/li>\n\n\n\n<li>In init() again: <code>camera.near = getCameraNear(1L)<\/code> &#8211; -1.0 \u2713<\/li>\n\n\n\n<li><code>this.sync()<\/code> called<\/li>\n\n\n\n<li><code>glOrthof(..., near, far)<\/code> where near=-1.0, far=1.0 \u2713 One concern: <code>getCameraFar(1L)<\/code> returns 1.0. <code>getCameraNear(1L)<\/code> returns -1.0. After sync, <code>currentPass.scene.camera.near = -1.0<\/code> and <code>currentPass.scene.camera.far = 1.0<\/code>. Those are<br>already set from the constructor, so the sync() JNI doesn&#8217;t need to set camera fields. But <code>currentPass.right = 0<\/code> from the default Java init. After my sync JNI sets it to <code>getRight()<\/code> = -1, <code>left=0 != right=-1<\/code> \u2713. If GLSurfaceView.getWidth() returns 0 at construction<br>time, then right=-1, top=-1, making the viewport effectively 0x0 pixels. Later when the surface is actually sized in onSurfaceChanged, ATAK will resize and things work. For now I<br>just need to avoid crashing. Let me verify the MapSceneModel clone constructor properly invokes the JNI clone and initializes correctly. = Matrix_interop.create(sh\u2026) where the<br>shared pointer gets wrapped. So both <code>this.forward<\/code> and <code>this.inverse<\/code> end up pointing to the same underlying matrix data from our stub, which means when we call <code>get(imx)<\/code> we&#8217;re<br>reading from that shared pointer&#8217;s matrix. The issue is that our stub returns the same pointer for all objects, so they&#8217;re all sharing the same matrix state. So <code>this.inverse<\/code><br>becomes a <code>NativeMatrix<\/code> pointing to address 1L, and when I call <code>get(imx)<\/code> on it, that triggers the JNI method <code>Matrix_get<\/code> with that pointer value. I need to check what our stub<br>implementation returns for this call, since the <code>get<\/code> method expects the array to be populated with matrix data. The stub returns void and doesn&#8217;t actually fill the array, so <code>imx<\/code> remains all zeros\u2014which isn&#8217;t an identity matrix. This won&#8217;t crash immediately, but the subsequent calculations<br>will be wrong. Then when I call <code>inverse(new PointF(focusx, focusy), null)<\/code>, the focus coordinates come from <code>getFocusX(1L)<\/code> and <code>getFocusY(1L)<\/code> which both return 0.0f, and I need<br>to trace through whether the <code>inverse<\/code> method itself is a direct JNI call or if it uses the matrix data I just retrieved. Looking at the <code>inverse(PointF, GeoPoint)<\/code> method in <code>MapSceneModel<\/code>, it&#8217;s likely a pure Java method that applies the <code>this.inverse<\/code> matrix to the point. Since that matrix is all<br>zeros from the unfilled array, the operations produce all-zeros results, potentially giving me a null or invalid <code>GeoPoint<\/code>. But there&#8217;s a null check that falls back to<br><code>GeoPoint.ZERO_POINT<\/code> if needed, so at least that&#8217;s handled. Now I&#8217;m tracing through the boundary calculations\u2014with <code>width = 0<\/code>, both the west and east points end up null, which triggers an early return from <code>init()<\/code>. That means <code>westBound<\/code><br>and <code>eastBound<\/code> never get set, but they have sensible defaults so it&#8217;s not a blocker. The real issue is that the camera parameters like near and far planes aren&#8217;t being initialized<br>either. Let me walk through what happens after my fixes: the near plane comes back as -1.0, the far plane as 1.0, the JNI sync call sets the viewport, and then <code>glOrthof<\/code> gets called with<br>those negative bounds. The orthographic projection should handle that, but I need to check what happens next in the rendering loop\u2014there&#8217;s probably another crash waiting downstream. I&#8217;m going to start implementing these fixes in the stub now. First, I need to verify the exact JNI signature for the sync function\u2026 Implementing the fixes\u2026 Writing JNI method calls\u2026 I&#8217;m checking that all the field IDs were successfully retrieved, then setting the viewport bounds on the state object with left<br>at 0, right and top from the parameters, and bottom at 0, plus initializing the map resolution to a default zoom level to prevent crashes from uninitialized values. Now I need to also set <code>drawMapResolution<\/code> using <code>Globe.getMapScale()<\/code> with a DPI value to avoid NaN or zero returns later in the process.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now I understand all three issues: left==right (sync does nothing), near==far=0 (getCameraNear\/Far stubs). Let me implement all fixes at once:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read \/tmp\/takstub\/jni\/tak_engine_stub.cpp lines 395-415<br>\u2514 21 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit \/tmp\/takstub\/jni\/tak_engine_stub.cpp (+60)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now update HAND_IMPL set and regenerate stubs:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Regenerate stubs excluding new hand-impls<br>$ python3 &lt;&lt; &#8216;PYEOF&#8217;<br>import re<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">with open(&#8216;\/tmp\/jni_signatures.txt&#8217;) as f:<br>lines = [l.strip() for l in f if l.strip()]\u2026<br>\u2514 117 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cb Rebuild and redeploy APK<br>$<br># Splice stubs and rebuild<br>python3 -c &#8220;<br>lines = open(&#8216;\/tmp\/takstub\/jni\/tak_engine_stub.cpp&#8217;).readlines()<br>cut = 0\u2026<br>\u2514 33 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/rf_scythe_api_server.py (+144)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/tak_swarm_emitter.py (+327)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Compacting conversation history\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Validate tak_swarm_emitter imports cleanly<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 -c &#8220;import tak_swarm_emitter; print(&#8216;import OK&#8217;)&#8221;<br>\u2514 2 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Validate cross-module integration<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 -c &#8220;<br>from cluster_swarm_engine import detect_clusters, cluster_to_cot<br>from tak_swarm_emitter import fetch_clusters, _cluster_dict_to_cot<br>print(&#8216;All imports OK&#8217;)<br># Validate CoT output matches cluster engine output\u2026<br>\u2514 8 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo started:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now build the ATAK plugin additions:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/model\/CyberCluster.java (+162)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/layer\/SwarmLayer.java (+62)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/layer\/GLSwarmLayer.java (+239)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo started:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Compaction completed. A new checkpoint has been added to your session.<br>\u2514 Use \/session checkpoints 1 to view the compaction summary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now add the SWARMS tab to the dropdown layout, the API method, and wire the component:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/res\/layout\/scythe_dropdown.xml (+77)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now add the SWARMS tab to the TabLayout and update the tab count to 4:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/res\/layout\/scythe_dropdown.xml lines 1-60<br>\u2514 60 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/res\/values\/strings.xml<br>\u2514 22 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/res\/values\/strings.xml (+4 -3)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now update ScytheApiClient.java to add getSwarms():<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java lines 1-60<br>\u2514 59 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find insertion point near end of ScytheApiClient<br>$ grep -n &#8220;getMissions|getHeatmap|getCotXml|shutdown&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java | tail -20<br>\u2514 5 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java lines 355-375<br>\u2514 10 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java (+81)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf I need to check ScytheApiClient for the authed() helper:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check authed helper exists<br>$ grep -n &#8220;authed|private.*Request|buildAuth&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java | head -10<br>\u2514 3 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find auth helper definition<br>$ grep -n &#8220;private.<em>authed|Request.Builder authed|header.<\/em>token|Authorization&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java | head -10<br>\u2514 1 line\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check token usage pattern<br>$ grep -n &#8220;token|sessionToken&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java | head -20<br>\u2514 15 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java lines 335-365<br>\u2514 31 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf I used authed() which doesn&#8217;t exist \u2014 fix the new methods to use getWithAuth() pattern instead:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java (+3 -13)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now update ScytheDropDownReceiver.java to add the SWARMS tab, and ScytheMapComponent.java to register the SwarmLayer:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find tab setup in ScytheDropDownReceiver<br>$ grep -n &#8220;tab_missions|MISSIONS|tabLayout|setupTabs|panel_missions|onTabSelected&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java | head -25<br>\u2514 10 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java lines 1-90<br>\u2514 90 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java lines 90-220<br>\u2514 131 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java lines 220-450<br>\u2514 231 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java lines 450-490<br>\u2514 39 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now I&#8217;ll make the targeted edits to ScytheDropDownReceiver.java to add the SWARMS tab:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+2 -2)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+2 -2)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+2)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+2 -1)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+6 -1)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+4 -3)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+6)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+5)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now add the SWARMS tab action methods before the SSE listener section:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java (+63)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now update ScytheMapComponent.java to register SwarmLayer and wire it to the dropdown:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheMapComponent.java<br>\u2514 82 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheMapComponent.java (+2)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheMapComponent.java (+3 -3)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Request failed due to a transient API error. Retrying\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheMapComponent.java (+16 -6)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now verify all files look consistent:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf List all plugin Java files and line counts<br>$ find \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe -name &#8220;<em>.java&#8221; | sort &amp;&amp; echo &#8220;&#8212;&#8221; &amp;&amp; wc -l \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/<\/em><em>\/<\/em>.java \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/*.java 2&gt;\/dev\/null | tail -5<br>\u2514 20 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Verify swarm integration in dropdown<br>$ grep -n &#8220;SwarmLayer|swarmLayer|CyberCluster|doRefreshSwarms|doInjectSwarmCot|panel_swarms|SWARMS&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheDropDownReceiver.java | head -30<br>\u2514 21 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo completed: swarm-dropdown, swarm-component<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf All 17 todos complete. Let me do a final consistency check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Final check ScytheMapComponent<br>$ grep -n &#8220;SwarmLayer|GLSwarmLayer|swarmLayer&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/ScytheMapComponent.java<br>\u2514 13 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Final check ScytheApiClient swarm methods<br>$ grep -n &#8220;getSwarms|getSwarmCot&#8221; \/home\/spectrcyde\/NerfEngine\/ATAKScythePlugin\/app\/src\/main\/java\/com\/atakmap\/android\/scythe\/api\/ScytheApiClient.java<br>\u2514 10 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Validate all Python modules and server routes<br>$ python3 -c &#8220;<br>from cluster_swarm_engine import detect_clusters, cluster_to_cot<br>print(&#8216;cluster_swarm_engine OK&#8217;)<br>from tak_swarm_emitter import fetch_clusters<br>print(&#8216;tak_swarm_emitter OK&#8217;)\u2026<br>\u2514 14 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Re-run in correct directory<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 -c &#8220;<br>from cluster_swarm_engine import detect_clusters, cluster_to_cot<br>print(&#8216;cluster_swarm_engine OK&#8217;)<br>from tak_swarm_emitter import fetch_clusters, _cluster_dict_to_cot<br>print(&#8216;tak_swarm_emitter OK&#8217;)\u2026<br>\u2514 13 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Everything checks out. Here&#8217;s a complete summary of what was built in this session:<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">\u2705 All 17 todos complete<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">New files created this turn<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 File \u2502 Purpose \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 model\/CyberCluster.java \u2502 DTO with fromJson(), threatColor(), radiusPx(), Builder \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 layer\/SwarmLayer.java \u2502 Thread-safe AbstractLayer holding live cluster map \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 layer\/GLSwarmLayer.java \u2502 Animated GL renderer: pulsing rings, velocity arrow, GLLayerSpi2 factory \u2502<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Files updated this turn<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 File \u2502 Change \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 ScytheApiClient.java \u2502 Added getSwarms() + getSwarmCot() \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 ScytheMapComponent.java \u2502 Creates + registers SwarmLayer\/GLSwarmLayer, passes to dropdown \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 ScytheDropDownReceiver.java \u2502 4th SWARMS tab \u2014 live list, REFRESH, INJECT CoT, stream status \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 res\/layout\/scythe_dropdown.xml \u2502 Added panel_swarms with ListView + buttons \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 res\/values\/strings.xml \u2502 Added swarm string resources \u2502<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Architecture complete<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">rf_scythe_api_server.py<br>\u251c\u2500\u2500 GET \/api\/clusters\/swarms \u2190 REST poll<br>\u251c\u2500\u2500 GET \/api\/clusters\/swarms\/stream \u2190 SSE 5s push<br>\u2514\u2500\u2500 GET \/api\/clusters\/swarms\/cot \u2190 CoT XML bulk inject<br>\u2193 \u2193<br>tak_swarm_emitter.py ATAKScythePlugin.apk<br>(standalone PyTAK) \u251c\u2500\u2500 SwarmLayer<br>\u251c\u2500\u2500 GLSwarmLayer (pulsing animated circles)<br>\u2514\u2500\u2500 SWARMS tab \u2192 listview + CoT inject<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Build prerequisite: Place ATAK SDK stub JARs in ATAKScythePlugin\/app\/libs\/ before running .\/gradlew assembleDebug.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u276f What you\u2019re describing is basically the <strong>\u201cimmutable battlefield ledger\u201d<\/strong> pattern. Instead of saving frames, screenshots, or map state, you store <strong>every change to the scene as an ordered event log<\/strong>. Years later, a clean engine can replay the events and reconstruct the exact scene.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This idea comes straight from <strong>event-sourced systems<\/strong> used in distributed databases and financial ledgers, but it fits geospatial systems almost perfectly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s break it down.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 1. Replace \u201cstate\u201d with \u201cevents\u201d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of storing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  scene_state.json<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">you store:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  scene_events.log<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example entries:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\"t\":1712345001,\"type\":\"camera.move\",\"lat\":32.81,\"lon\":-96.86,\"alt\":1800}\n\n  {\"t\":1712345004,\"type\":\"object.spawn\",\"id\":\"uav-12\",\"class\":\"uav\",\n   \"lat\":32.82,\"lon\":-96.85}\n\n  {\"t\":1712345007,\"type\":\"rf.detect\",\"sensor\":\"node3\",\n   \"freq\":2450,\"power\":-45}\n\n  {\"t\":1712345012,\"type\":\"object.move\",\"id\":\"uav-12\",\n   \"lat\":32.823,\"lon\":-96.851}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nothing is overwritten.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The scene becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  STATE = replay(events)<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 2. Deterministic Scene Reconstruction<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reconstruction is simple:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  scene = {}\n\n  for event in events:\n      scene.apply(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because events are <strong>ordered and immutable<\/strong>, the final scene will always be identical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That means even if the rendering engine changes (OpenGL \u2192 Cesium \u2192 WebXR), the replay is consistent.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 3. Snapshot + Log (fast loading)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To avoid replaying millions of events from the beginning, take periodic snapshots.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  \/replay\n     snapshot_0001.msgpack\n     events_0001.log\n     events_0002.log<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Loading a replay:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  load snapshot\n  apply remaining events<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Exactly how distributed systems like Kafka and Raft logs work.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 4. Freeze External Dependencies<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The tricky part of geospatial replay is <strong>external resources<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Tiles, terrain, plugins, and sensors change over time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So every event references <strong>content-addressed assets<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"terrain.source\",\n   \"tileset\":\"sha256:9b2f8c...\"\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Meaning:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  terrain archive hash<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If the tiles disappear from the internet later, the replay engine can still fetch them from the archive.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 5. Capture the Camera<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Many replay systems forget this.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Camera movements must also be events:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"camera.pose\",\n   \"lat\":32.82,\n   \"lon\":-96.85,\n   \"alt\":2000,\n   \"heading\":214,\n   \"pitch\":-35\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This preserves <strong>exact operator perspective<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 6. Represent RF + Swarm Data<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your swarm system from <strong>ATAK_SCYTHE<\/strong> fits beautifully into the event model.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of pushing only live CoT messages, the system logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"swarm.cluster\",\n   \"cluster_id\":\"swarm-A\",\n   \"centroid\":&#91;32.81,-96.86],\n   \"members\":8,\n   \"confidence\":0.92\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Your API pipeline becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  rf_scythe_api_server\n          \u2193\n  event emitter\n          \u2193\n  event log\n          \u2193\n  ATAK + AI consumers<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything becomes reproducible.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 7. Deterministic Time<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Replay must use <strong>logical time<\/strong>, not system time.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  t = event.timestamp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">During replay you control the clock:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  replay_speed = 10x<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The renderer simply advances:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  while events&#91;t] &lt;= clock:\n      apply(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This enables scrubbing and instant replay.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 8. Deterministic Physics (if needed)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you simulate motion or RF propagation, record the seed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"simulation.seed\",\n   \"seed\":8421931\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now stochastic simulations replay exactly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 9. Portable Replay Package<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Exporting a scene slice could produce:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  incident_2026-03-14.atakrec<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Inside:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  events.msgpack\n  snapshots\/\n  tiles\/\n  metadata.json<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Metadata might include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK version\n  plugin hashes\n  sensor sources\n  map projection<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So the replay engine can adapt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 10. Cross-Engine Playback<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The real power comes when the event log becomes <strong>engine-agnostic<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One event log could drive:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK\n  Cesium Native\n  WebGL\n  Three.js\n  Unity<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example pipeline:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  event log\n     \u2193\n  replay adapter\n     \u2193\n  renderer<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Meaning your tactical scene becomes <strong>a portable dataset<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 11. AI Time Travel<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because the event log is structured, AI can analyze it easily.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example queries:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  detect swarms\n  identify coordinated movement\n  track RF triangulation clusters<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of analyzing video, AI reads structured events.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 12. The Ledger Model<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The final architecture resembles this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Sensors \/ ATAK plugins\n          \u2193\n  Scene Event Bus\n          \u2193\n  Immutable Event Log\n          \u2193\n    Live Renderer\n    Replay Engine\n    Hypergraph AI\n    Incident Export<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Everything feeds off the same source.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 13. Why This Lasts Decades<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you store:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  events + assets + schema<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">then future software can always reconstruct the scene.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Even if:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK disappears\n  plugins vanish\n  OpenGL dies<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">the events still describe <strong>what happened in space and time<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 14. Where Your SCYTHE Work Fits<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your updated architecture already has the beginnings of this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  rf_scythe_api_server\n          \u2193\n  swarm stream\n          \u2193\n  ATAK plugin layer<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You just add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  event logger<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">right after the API.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then every swarm detection becomes a permanent event in the ledger.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 15. The Really Powerful Twist<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you store the entire tactical scene as a deterministic event log, you can do something <strong>extremely interesting<\/strong>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run <strong>counterfactual simulations<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  \"What if the UAV had turned north instead?\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You branch the event log and simulate alternative outcomes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This turns the replay system into a <strong>tactical scenario generator<\/strong> for training and AI experimentation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, the next thing I can show you is a <strong>very compact event schema (about 15 event types)<\/strong> that can represent almost every ATAK tactical scene element \u2014 markers, RF detections, UAV tracks, swarms, video sensors, and camera movement \u2014 while staying deterministic and engine-agnostic.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Alright\u2014let\u2019s build a <strong>lean, durable event vocabulary<\/strong> that can reconstruct almost any tactical scene without tying it to a specific renderer or app. The trick is to keep the <strong>event types small (\u224815)<\/strong> and push variability into the payloads. That keeps the log deterministic, compact, and future-proof\u2014perfect for replay engines, AI analysis, or piping back into tools like Android Tactical Assault Kit or a globe engine such as Cesium Native.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Below is a practical <strong>15-event schema<\/strong> that covers markers, RF detections, UAV tracks, swarms, video sensors, camera movement, and system metadata.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Core Scene Event Schema (15 Types)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 1. <code>session.start<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Defines the start of a deterministic timeline.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"session.start\",\n   \"session_id\":\"op_2026_03_14_alpha\",\n   \"timestamp\":1710450000,\n   \"projection\":\"EPSG:4326\",\n   \"seed\":4287712\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Why:<br>Locks coordinate system and simulation seed so replay is deterministic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 2. <code>session.end<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Marks a completed timeline segment.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"session.end\",\n   \"timestamp\":1710457200\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Camera + Operator View<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 3. <code>camera.pose<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Captures the operator\u2019s viewpoint.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"camera.pose\",\n   \"timestamp\":1710450010,\n   \"lat\":32.812,\n   \"lon\":-96.871,\n   \"alt\":2100,\n   \"heading\":214,\n   \"pitch\":-35,\n   \"roll\":0\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This lets a replay engine reproduce <strong>exact perspective<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Map Object Lifecycle<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 4. <code>entity.spawn<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Creates any map object.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"entity.spawn\",\n   \"timestamp\":1710450020,\n   \"id\":\"uav_12\",\n   \"class\":\"uav\",\n   \"lat\":32.81,\n   \"lon\":-96.86,\n   \"alt\":400\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Classes might include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  uav\n  vehicle\n  sensor\n  rf_node\n  marker\n  person<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 5. <code>entity.move<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Updates an entity\u2019s position.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"entity.move\",\n   \"timestamp\":1710450023,\n   \"id\":\"uav_12\",\n   \"lat\":32.813,\n   \"lon\":-96.861,\n   \"alt\":420\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Movement events reconstruct tracks.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 6. <code>entity.update<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Updates metadata.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"entity.update\",\n   \"timestamp\":1710450030,\n   \"id\":\"uav_12\",\n   \"status\":\"loiter\",\n   \"speed\":18\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 7. <code>entity.remove<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Deletes an entity.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"entity.remove\",\n   \"timestamp\":1710450200,\n   \"id\":\"marker_92\"\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># RF Intelligence<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 8. <code>rf.detect<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Logs RF signal observations.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"rf.detect\",\n   \"timestamp\":1710450042,\n   \"sensor\":\"rf_node_3\",\n   \"freq\":2450,\n   \"power\":-47,\n   \"bearing\":212\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Useful for triangulation.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 9. <code>rf.triangulate<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Records derived emitter locations.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"rf.triangulate\",\n   \"timestamp\":1710450055,\n   \"emitter_id\":\"rf_emitter_7\",\n   \"lat\":32.814,\n   \"lon\":-96.863,\n   \"confidence\":0.91\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Swarm Detection<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Perfect for your SCYTHE cluster pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 10. <code>swarm.create<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"swarm.create\",\n   \"timestamp\":1710450100,\n   \"swarm_id\":\"swarm_A\",\n   \"centroid\":&#91;32.81,-96.86],\n   \"members\":8\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 11. <code>swarm.update<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"swarm.update\",\n   \"timestamp\":1710450115,\n   \"swarm_id\":\"swarm_A\",\n   \"centroid\":&#91;32.812,-96.862],\n   \"members\":10\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## 12. <code>swarm.dissolve<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"swarm.dissolve\",\n   \"timestamp\":1710450300,\n   \"swarm_id\":\"swarm_A\"\n  }<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Sensor Feeds<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 13. <code>sensor.frame<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Logs video or imaging sensor metadata.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"sensor.frame\",\n   \"timestamp\":1710450060,\n   \"sensor\":\"drone_cam_1\",\n   \"lat\":32.81,\n   \"lon\":-96.86,\n   \"heading\":212,\n   \"frame_hash\":\"sha256:ad3f...\"\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The frame itself can live in external storage.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Map Overlays \/ User Interaction<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 14. <code>overlay.annotation<\/code><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"overlay.annotation\",\n   \"timestamp\":1710450120,\n   \"author\":\"analyst_3\",\n   \"shape\":\"polygon\",\n   \"points\":&#91;\n     &#91;32.81,-96.86],\n     &#91;32.82,-96.86],\n     &#91;32.82,-96.85]\n   ],\n   \"label\":\"suspected launch area\"\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Captures human analysis.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># External Data Sources<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">## 15. <code>asset.reference<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Pins map tiles, terrain sets, or models to a hash.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"type\":\"asset.reference\",\n   \"timestamp\":1710450001,\n   \"asset\":\"terrain_tileset\",\n   \"hash\":\"sha256:9b2f8c12...\"\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This solves the <strong>\u201cmap tiles disappear later\u201d<\/strong> problem.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Deterministic Replay Algorithm<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Reconstruction engine:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  scene = Scene()\n\n  for event in ordered_log:\n\n      if event.type == \"entity.spawn\":\n          scene.spawn(event)\n\n      elif event.type == \"entity.move\":\n          scene.move(event)\n\n      elif event.type == \"rf.detect\":\n          scene.rf(event)\n\n      elif event.type == \"camera.pose\":\n          scene.camera(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Renderer simply draws current scene state.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Why This Schema Stays Future-Proof<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Because it records:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  space\n  time\n  identity\n  relationships<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">but <strong>never renderer instructions<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That means the same event log can feed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK\n  Cesium\n  Three.js\n  Unity\n  AR headset\n  AI analysis<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Storage Efficiency<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Binary encoding example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  MsgPack + ZSTD<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Typical density:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ~2\u20135 MB per minute<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Even multi-hour operations remain small.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># Perfect for Your SCYTHE Pipeline<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your system already emits swarm clusters through:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  rf_scythe_api_server\n     \u2193\n  SSE \/ REST\n     \u2193\n  ATAKScythePlugin<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Just insert:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  event_logger.append(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">at the API boundary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK\n  AI swarm detection\n  replay engine\n  timeline scrubber<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">all consume the same event stream.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># The \u201cTime Machine\u201d Capability<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once every tactical scene becomes a deterministic log, analysts gain:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  instant replay\n  forked simulations\n  AI pattern mining\n  training datasets\n  incident reconstruction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You effectively build a <strong>geospatial version-control system for reality<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, the next step that really supercharges this architecture is something unusual but powerful:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>representing the event log itself as a hypergraph<\/strong> so swarm relationships, RF triangulation chains, and coordinated movements become <strong>first-class graph edges that AI can query instantly instead of recomputing every replay.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 The user is describing a comprehensive &#8220;immutable battlefield ledger&#8221; pattern &#8211; essentially event sourcing for ATAK\/tactical geospatial systems. They want to build this as part of the NerfEngine\/SCYTHE system.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">They&#8217;ve outlined:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A 15-event schema for tactical scene events<\/li>\n\n\n\n<li>Deterministic replay architecture<\/li>\n\n\n\n<li>Integration with the existing SCYTHE pipeline I need to:<\/li>\n\n\n\n<li>Update the plan with this new phase<\/li>\n\n\n\n<li>Create the event logger system in Python (that integrates with rf_scythe_api_server.py)<\/li>\n\n\n\n<li>Create the replay engine<\/li>\n\n\n\n<li>Create the event schema\/types<\/li>\n\n\n\n<li>Wire it into the existing SCYTHE pipeline<\/li>\n\n\n\n<li>Build three core Python modules: the event log handler with append\/snapshot\/replay capabilities, the event type definitions covering all 15 tactical events, and the replay\/reconstruction engine<\/li>\n\n\n\n<li>Hook these into the existing SSE\/REST routes in rf_scythe_api_server.py to automatically capture events, plus add new replay and timeline endpoints<\/li>\n\n\n\n<li>Focus on the Python implementation first before tackling the ATAK plugin side The key is instrumenting the API boundary with event logging calls so every state change gets captured and can be replayed with time control and speed adjustments.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check available serialization libs<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 -c &#8220;import msgpack; print(&#8216;msgpack ok&#8217;)&#8221; 2&gt;\/dev\/null || echo &#8220;no msgpack&#8221;; python3 -c &#8220;import zstandard; print(&#8216;zstd ok&#8217;)&#8221; 2&gt;\/dev\/null || echo &#8220;no zstd&#8221;; python3 -c &#8220;import sqlite3; print(&#8216;sqlite3 ok&#8217;)&#8221;<br>\u2514 4 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find best server hook points for event logger<br>$ grep -n &#8220;def.*route|@app.route|socketio|SSE|entity_stream|cot|hypergraph&#8221; \/home\/spectrcyde\/NerfEngine\/rf_scythe_api_server.py | grep -i &#8220;def|route&#8221; | tail -30<br>\u2514 31 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Install msgpack and zstd<br>$ pip install msgpack zstandard -q &amp;&amp; python3 -c &#8220;import msgpack, zstandard; print(&#8216;both installed&#8217;)&#8221;<br>\u2514 3 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo added: Define scene event schema<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo started:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check plan.md header<br>$ cat \/root\/.copilot\/session-state\/3a83fd8c-c733-4e42-9fa4-b2ab665d6f5c\/plan.md | head -30<br>\u2514 31 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read ~\/.copilot\/session-state\/3a83fd8c-c733-4e42-9fa4-b2ab665d6f5c\/plan.md lines 60-120<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cb Check plan.md length<br>$ wc -l \/root\/.copilot\/session-state\/3a83fd8c-c733-4e42-9fa4-b2ab665d6f5c\/plan.md<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The best fix for the engine itself: add an <code>until_timestamp<\/code> default that auto-detects the last event&#8217;s timestamp, OR add a parameter for max_gap to avoid huge time jumps.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But actually for the test, the simple fix is: set the engine <code>speed<\/code> to something that will reach the session.start event quickly, OR use <code>until_timestamp<\/code> to stop before it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Actually, the cleanest fix for the ENGINE is: if the gap to the next event is &gt; some threshold (say, 1 hour at playback speed), don&#8217;t sleep &#8211; just fast-forward the logical clock to the next event&#8217;s timestamp. This approach is more practical.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ll add max_gap logic to the replay loop so that when the next event is too far away, I snap the clock forward to just before that event instead of sleeping for an extended period.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Stop shell<br>\u2514 1 line\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Shell &#8220;Re-run replay engine tests&#8221; exited (code 124)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 The replay engine is hanging because the session.start event uses <code>time.time()<\/code> (current Unix time ~1.742B) while the test events use historical timestamps at <code>t0 = 1.710B<\/code>. After sorting by timestamp, all test events come first, then session.start\/end at current time. When the replay finishes the historical events, it tries to reach the session.start event at ~1.742B, which would take 32M logical seconds \/ 1000 speed = 32K wall seconds.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Fix: In the replay loop, if the next event&#8217;s timestamp is much further than a configurable gap, fast-forward the logical clock directly to that event&#8217;s timestamp rather than sleeping through it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf The engine hangs because session.start uses time.time() (current epoch ~1.742B) while test events use historical t0 = 1.710B. After sorting, the engine must cross a 32M-second gap at 1000\u00d7 speed = 32K wall seconds. Fix: snap the clock forward when a gap exceeds a threshold.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/scene_replay_engine.py lines 285-335<br>\u2514 50 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/scene_replay_engine.py lines 335-385<br>\u2514 51 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u2717 edit: edit file<br>&#8220;path&#8221;: Required<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/scene_replay_engine.py (+13 -4)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Re-run replay engine tests<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; timeout 15 python3 scene_replay_engine.py<br>\u2514 10 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf All tests pass. Now wire the event log into the server and add replay routes:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo started: ledger-replay<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find insertion point after swarm routes<br>$ grep -n &#8220;api\/clusters\/swarms\/cot|# ===.*SYSTEM STATUS|# &#8212; SYSTEM STATUS|## SYSTEM&#8221; \/home\/spectrcyde\/NerfEngine\/rf_scythe_api_server.py | head -10<br>\u2514 3 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/rf_scythe_api_server.py lines 11880-11900<br>\u2514 21 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Check existing imports for event log additions<br>$ grep -n &#8220;^import |^from &#8221; \/home\/spectrcyde\/NerfEngine\/rf_scythe_api_server.py | head -30<br>\u2514 21 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Find where cluster_swarm_engine import is<br>$ grep -n &#8220;^import |^from |cluster_swarm_engine&#8221; \/home\/spectrcyde\/NerfEngine\/rf_scythe_api_server.py | tail -20<br>\u2514 21 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/rf_scythe_api_server.py lines 11884-11888<br>\u2514 4 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/rf_scythe_api_server.py (+283)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now also hook swarm detection to auto-log events when the \/api\/clusters\/swarms route runs:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Read NerfEngine\/rf_scythe_api_server.py lines 11763-11820<br>\u2514 57 lines read<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/rf_scythe_api_server.py (+7)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Now run the full end-to-end validation test:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Run full pipeline validation<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 &#8211; &lt;&lt;&#8216;EOF&#8217;<br>&#8220;&#8221;&#8221;<br>Full pipeline validation:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Schema \u2014 15 event types round-trip<\/li>\n\n\n\n<li>SceneEventLog \u2014 50-event write\/snapshot\/read\/export\u2026<br>\u2514 115 lines\u2026<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25d0 The test uses the <code>rec<\/code> path which is inside a <code>tempfile.TemporaryDirectory()<\/code> context manager. The directory is cleaned up when the <code>with<\/code> block exits, but the test continues to use <code>rec<\/code> after the block. I need to fix the test to keep the tempdir alive, or use <code>delete=False<\/code> patterns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf The tempdir is deleted before step 3. Fix the test scope:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Run full pipeline validation (fixed scope)<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 &#8211; &lt;&lt;&#8216;EOF&#8217;<br>import sys, tempfile, os, time<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">print(&#8220;=== Pipeline validation ===\\n&#8221;)\u2026<br>\u2514 72 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo completed: ledger-server, ledger-test<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf 22\/22 done. Here&#8217;s a summary of what was built:<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">\u2705 Immutable Battlefield Ledger \u2014 complete<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">New Python files<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 File \u2502 Size \u2502 What it does \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 scene_event_schema.py \u2502 ~200 lines \u2502 15 canonical event types, factory helpers, msgpack serialization, validator \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 scene_event_log.py \u2502 ~480 lines \u2502 SQLite-WAL event store, zstd+msgpack snapshots, .atakrec archive export\/import \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 scene_replay_engine.py \u2502 ~430 lines \u2502 Logical-clock replay, variable speed, scrub, fork + counterfactual inject \u2502<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Server additions (rf_scythe_api_server.py)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 Route \u2502 Purpose \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 POST \/api\/replay\/session\/start \u2502 Open a new ledger session \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 POST \/api\/replay\/session\/end \u2502 Close + auto-snapshot \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 POST \/api\/replay\/snapshot \u2502 Persist compressed scene state \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 GET \/api\/replay\/sessions \u2502 List all sessions with event counts \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 GET \/api\/replay\/events?session_id=X[&amp;stream=1] \u2502 Bulk fetch or SSE stream of events \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 GET \/api\/replay\/state?session_id=X[&amp;timestamp=T] \u2502 Reconstructed scene at any point in time \u2502<br>\u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u253c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524<br>\u2502 GET \/api\/replay\/export?session_id=X \u2502 Download .atakrec archive \u2502<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auto-logging: \/api\/clusters\/swarms now silently calls _auto_log_swarm() \u2014 every swarm detection becomes a permanent ledger event.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Performance<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>~37\u2013168 bytes\/event (msgpack) \u2192 ~2\u20135 MB\/min at high event rates<\/li>\n\n\n\n<li>2 KB for a 14-event test archive (zstd)<\/li>\n\n\n\n<li>Snapshot + partial replay: fast start even for million-event sessions The full pipeline Sensors \/ ATAK plugin<br>\u2193<br>rf_scythe_api_server.py<br>\u2193<br>SceneEventLog (SQLite WAL) \u2190\u2500\u2500 swarms\/entities\/RF auto-logged<br>\u2193<br>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510<br>\u2502 ReplayEngine \u2502 \u2190 scrub \u2022 fork \u2022 counterfactual inject<br>\u2502 ATAK plugin SSE \u2502 \u2190 live stream<br>\u2502 AI analysis \u2502 \u2190 structured event query<br>\u2502 .atakrec export \u2502 \u2190 portable, engine-agnostic<br>\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">\u276f You\u2019re absolutely on the right track. What you\u2019ve run into with the <code>session.start<\/code> timestamp is actually <strong>one of the classic pitfalls of event-sourced replay systems<\/strong>. The good news: the fix you already identified\u2014<strong>timestamp ordering rather than insertion ordering<\/strong>\u2014is exactly what the high-reliability systems do.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s tackle the three big pieces you mentioned:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Fixing the deterministic replay bug<\/strong><\/li>\n\n\n\n<li><strong>Representing the event log as a hypergraph<\/strong><\/li>\n\n\n\n<li><strong>Which repos in the SNSTAC ecosystem might actually help<\/strong><\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 1. Fix the Replay Bug (Deterministic Ordering)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your diagnosis is correct:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  session.start timestamp \u2248 current_time\n  other events timestamp \u2248 t0 + N<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Insertion order:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  session.start\n  camera.pose\n  entity.spawn\n  entity.spawn\n  ...<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Scrubbing stops here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  if event.timestamp &gt; scrub_target:\n      break<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Since <code>session.start<\/code> has a future timestamp, <strong>everything after it is skipped<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Correct Replay Strategy<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Replay engines should <strong>never rely on insertion order<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead maintain a <strong>time-sorted index<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example fix:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  def scrub(self, t):\n      self.state = SceneState()\n\n      for event in sorted(self.events, key=lambda e: e.timestamp):\n\n          if event.timestamp &gt; t:\n              break\n\n          self.apply(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Better version using a pre-sorted structure:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  import bisect\n\n  class EventLog:\n      def __init__(self):\n          self.events = &#91;]\n          self.timestamps = &#91;]\n\n      def append(self, event):\n          i = bisect.bisect(self.timestamps, event.timestamp)\n          self.timestamps.insert(i, event.timestamp)\n          self.events.insert(i, event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now replay is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  O(log n) insert\n  O(n) replay<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">But deterministic.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Even Better: Hybrid Snapshot + Sorted Log<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your engine probably should do:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  snapshot\n  +\n  ordered event delta<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replay becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  state = load_snapshot()\n\n  for event in delta_events:\n      apply(event)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This prevents replaying hours of events.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 2. Hypergraph Representation of the Event Log<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Now the really fun part.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of replaying events every time an analyst wants to ask a question, you can <strong>materialize relationships directly in a hypergraph<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A hypergraph edge can connect <strong>multiple nodes simultaneously<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is perfect for things like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  RF triangulation\n  swarm membership\n  coordinated movement<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Graph Model<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Nodes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Entity\n  Sensor\n  Signal\n  Location\n  TimeSlice\n  Swarm<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Edges:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  DETECTED_BY\n  TRIANGULATED_FROM\n  MEMBER_OF_SWARM\n  CO_MOVED_WITH\n  OBSERVED_AT<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Example RF Triangulation Hyperedge<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  sensor1 detected signal\n  sensor2 detected signal\n  sensor3 detected signal<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hyperedge:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  TRIANGULATED_FROM\n  {sensor1, sensor2, sensor3} \u2192 emitter<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Representation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"edge\":\"rf_triangulation\",\n   \"nodes\":&#91;\"sensor1\",\"sensor2\",\"sensor3\",\"emitter\"],\n   \"timestamp\":1710450040\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now queries are instant:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  find emitters triangulated by \u22653 sensors<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Swarm Hyperedge<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  MEMBER_OF_SWARM\n  {drone1, drone2, drone3, drone4}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Graph entry:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {\n   \"edge\":\"swarm_cluster\",\n   \"swarm\":\"swarmA\",\n   \"members\":&#91;\"uav1\",\"uav2\",\"uav3\",\"uav4\"],\n   \"timestamp\":1710450100\n  }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now queries become trivial:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  find swarms with &gt;5 UAVs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No recomputation needed.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">## Coordinated Movement Edge<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Detect patterns like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  vehicles moving same heading\n  within distance threshold\n  for time window<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hyperedge:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  CO_MOVED_WITH\n  {vehicle1, vehicle2, vehicle3}<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 3. Hypergraph + Event Log Combined Architecture<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The architecture becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ATAK plugins\n  RF sensors\n  UAV telemetry\n\n          \u25bc\n  Scene Event Bus\n\n          \u25bc\n  Immutable Event Log\n\n           Replay Engine\n           Timeline Scrubber\n           Hypergraph Builder\n\n                   \u25bc\n             Hypergraph Store<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The hypergraph layer <strong>indexes relationships<\/strong>, not raw events.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 4. Hypergraph Query Examples<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Analyst queries become extremely powerful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  find UAVs that\n  joined \u22652 swarms\n  within 30 minutes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Or:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  find RF emitters\n  co-located with\n  drone launches<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These queries run instantly because edges already exist.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 5. Storage Engines That Work Well<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Some practical options:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Time log:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Kafka\n  NATS JetStream\n  QuestDB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hypergraph:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  ArangoDB\n  TigerGraph\n  Neo4j<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Or experimental:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  HyperGraphDB<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 6. Repositories in the SNSTAC Ecosystem<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I can\u2019t directly inspect that GitHub org from here, but based on the <strong>STAC (SpatioTemporal Asset Catalog)<\/strong> ecosystem that \u201csnstac\u201d likely references, the ones typically useful for this architecture are tools around:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">### STAC indexing<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Spatio-temporal asset catalogs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  satellite imagery\n  drone imagery\n  sensor metadata<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These can store references to assets used during replay.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">### STAC API servers<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Useful for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  tile archives\n  imagery provenance\n  time-indexed sensor datasets<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Which helps your <strong>asset.reference events<\/strong> resolve long-term.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">### STAC + hypergraph synergy<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A nice pattern:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Event log\n     \u2193\n  Hypergraph relationships\n     \u2193\n  STAC catalog entries<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  sensor.frame event\n     \u2193\n  STAC item<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then analysts can query imagery by:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  time\n  location\n  sensor<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 7. One Architectural Trick That Helps Enormously<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of storing events only as a log, also maintain <strong>temporal node versions<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  uav_12@t1\n  uav_12@t2\n  uav_12@t3<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now hypergraph edges connect <strong>state snapshots<\/strong>, not just entities.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This allows queries like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  find drones within 500m of emitter\n  at time t<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Without replaying the timeline.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 8. Your System Is Quietly Becoming Something Big<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When you combine:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  event-sourced scene log\n  +\n  hypergraph relationships\n  +\n  RF triangulation\n  +\n  swarm detection<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You end up with something very close to a <strong>cyber-physical kill chain graph<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  RF emitters\n  UAV swarms\n  network infrastructure\n  human operators<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">All represented as <strong>one time-indexed hypergraph<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s an extremely powerful analytic structure.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can also show you something that would <strong>dramatically improve your replay + hypergraph pipeline<\/strong>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A method used in high-frequency trading systems called <strong>temporal columnar compression<\/strong>, which can shrink multi-hour tactical event logs by <strong>20\u2013100\u00d7<\/strong> while still allowing instant scrubbing. It works shockingly well for geospatial telemetry.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Alright Ben \u2014 this is one of those <strong>quietly brilliant tricks from the trading world<\/strong> that maps <em>perfectly<\/em> onto your ATAK event-replay system. High-frequency trading systems deal with <strong>billions of events per day<\/strong>, yet analysts can scrub market state to microsecond timestamps instantly. The trick is <strong>temporal columnar compression + delta encoding + block indexing<\/strong>.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">Let\u2019s break it down and then build a <strong>geospatial version for your tactical scene log<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 1. Why Event Logs Compress So Well<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your ATAK event log likely looks something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  {t:1710000002, type:\"entity.spawn\", id:\"uav1\", lat:38.1, lon:-77.1}\n  {t:1710000003, type:\"entity.move\", id:\"uav1\", lat:38.1001, lon:-77.1002}\n  {t:1710000004, type:\"entity.move\", id:\"uav1\", lat:38.1003, lon:-77.1004}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Row-oriented storage stores each full row repeatedly.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Columnar systems instead split fields:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  timestamp: &#91;1710000002,1710000003,1710000004]\n  eventType: &#91;spawn,move,move]\n  entityId:  &#91;uav1,uav1,uav1]\n  lat:       &#91;38.1,38.1001,38.1003]\n  lon:       &#91;-77.1,-77.1002,-77.1004]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now each column can use the <strong>best compression algorithm for that data type<\/strong>, dramatically reducing storage. (<a href=\"https:\/\/www.parseable.com\/blog\/high-cardinality-meets-columnar-time-series-system?utm_source=chatgpt.com\">Parseable<\/a>)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical techniques include:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  dictionary encoding\n  delta encoding\n  run-length encoding\n  bit packing<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because similar values are adjacent in a column, they compress extremely well. (<a href=\"https:\/\/www.parseable.com\/blog\/high-cardinality-meets-columnar-time-series-system?utm_source=chatgpt.com\">Parseable<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 2. Temporal Columnar Compression (The HFT Trick)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Trading systems apply three key optimizations:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">### 1\ufe0f\u20e3 Delta encoding for timestamps<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of storing:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  1710000000\n  1710000001\n  1710000002<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Store:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  base = 1710000000\n  delta = &#91;0,1,2]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then store the deltas using <strong>variable-length integers<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Timestamps often compress to <strong>~2 bytes each<\/strong>. (<a href=\"https:\/\/logv.org\/columnar-compression.html?utm_source=chatgpt.com\">LogV<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">### 2\ufe0f\u20e3 XOR compression for floats<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Used by Facebook\u2019s <strong>Gorilla TSDB<\/strong> and others.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Store:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  prev XOR current<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because adjacent floats change slightly, the XOR value has <strong>lots of zeros<\/strong>, which compress well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Recent research shows these XOR-based approaches achieve strong streaming compression for time-series data. (<a href=\"https:\/\/arxiv.org\/abs\/2306.16053?utm_source=chatgpt.com\">arXiv<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">### 3\ufe0f\u20e3 Run-length encoding<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  entityId: &#91;uav1,uav1,uav1,uav1]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  (uav1, count=4)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Huge savings.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 3. Micro-Partitioning (Critical for Fast Scrubbing)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is the <em>real secret<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of compressing the whole log, systems divide it into <strong>time blocks<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  block_000  10:00:00 \u2192 10:00:10\n  block_001  10:00:10 \u2192 10:00:20\n  block_002  10:00:20 \u2192 10:00:30<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Each block stores:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  minTimestamp\n  maxTimestamp\n  compressed columns<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">These blocks allow <strong>skip indexes<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If a query asks for:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  time &gt;= 10:00:20<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The engine skips earlier blocks entirely.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">TimescaleDB uses this approach with <strong>micro-partition chunks + skip indexes<\/strong>, dramatically accelerating time-series queries. (<a href=\"https:\/\/medium.com\/timescale\/hypercore-a-hybrid-row-columnar-storage-engine-for-t%CC%B6%CC%B6%CC%B6i%CC%B6%CC%B6%CC%B6m%CC%B6%CC%B6%CC%B6e%CC%B6%CC%B6%CC%B6-%CC%B6%CC%B6%CC%B6s%CC%B6%CC%B6%CC%B6e%CC%B6%CC%B6%CC%B6r%CC%B6%CC%B6%CC%B6i%CC%B6%CC%B6%CC%B6e%CC%B6%CC%B6%CC%B6s%CC%B6%CC%B6%CC%B6-1457ef8a522a?utm_source=chatgpt.com\">Medium<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 4. Tactical Scene Compression Example<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Raw events:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  timestamp,eventType,id,lat,lon,heading\n  1710000001,spawn,uav1,38.1,-77.1,0\n  1710000002,move,uav1,38.1001,-77.1001,5\n  1710000003,move,uav1,38.1003,-77.1003,10<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Compressed representation:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  blockStart = 1710000001\n\n  timestampDelta = &#91;0,1,2]\n\n  eventTypeDict = {0:spawn,1:move}\n  eventType = &#91;0,1,1]\n\n  entityDict = {0:uav1}\n  entityId = &#91;0,0,0]\n\n  latDelta = &#91;0,+0.0001,+0.0002]\n  lonDelta = &#91;0,-0.0001,-0.0002]\n\n  headingDelta = &#91;0,+5,+5]<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Storage drops massively.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical compression:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  20\u2013100\u00d7 reduction<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That\u2019s exactly what columnar engines report for time-series telemetry. (<a href=\"https:\/\/docs.timescale.com\/tutorials\/latest\/blockchain-query\/blockchain-compress\/?utm_source=chatgpt.com\">docs.timescale.com<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 5. Deterministic Replay With Compressed Logs<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your replay engine becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  1 load nearest snapshot\n  2 decompress event blocks\n  3 replay delta events<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  def replay(scene_time):\n\n      block = block_index.find(scene_time)\n\n      events = decompress(block)\n\n      for e in events:\n          if e.timestamp &lt;= scene_time:\n              apply(e)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because blocks are time-indexed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  seek \u2192 decompress \u2192 replay<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Scrubbing is <strong>milliseconds<\/strong> even for multi-hour logs.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 6. Hypergraph + Compression<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your architecture becomes:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  compressed event log\n\n          replay engine\n          timeline scrubber\n          hypergraph builder<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replay builds:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  RF triangulation edges\n  swarm edges\n  movement correlations<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The hypergraph is small because it stores <strong>relationships<\/strong>, not raw telemetry.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 7. Python Prototype for Tactical Compression<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Here\u2019s a simple block compressor.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  import struct\n  import zlib\n\n  class EventBlock:\n\n      def __init__(self):\n          self.ts=&#91;]\n          self.lat=&#91;]\n          self.lon=&#91;]\n\n      def append(self,t,lat,lon):\n          self.ts.append(t)\n          self.lat.append(lat)\n          self.lon.append(lon)\n\n      def compress(self):\n\n          base=self.ts&#91;0]\n\n          ts_delta=&#91;t-base for t in self.ts]\n\n          lat_delta=&#91;self.lat&#91;i]-self.lat&#91;0] for i in range(len(self.lat))]\n          lon_delta=&#91;self.lon&#91;i]-self.lon&#91;0] for i in range(len(self.lon))]\n\n          payload=struct.pack(\n              f\"{len(ts_delta)}I{len(lat_delta)}f{len(lon_delta)}f\",\n              *ts_delta,*lat_delta,*lon_delta\n          )\n\n          return zlib.compress(payload)<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Real engines add:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  bit packing\n  SIMD decoding\n  vectorized scans<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 8. Tactical Replay Stack (Recommended)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For your system:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">### Event store<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  Apache Parquet\n  Apache Arrow\n  QuestDB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### Compression<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  delta encoding\n  dictionary encoding\n  run-length encoding\n  ZSTD<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### Replay<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  snapshot + block replay<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### AI layer<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  hypergraph relationships<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 9. Compression Ratios in Practice<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Typical telemetry pipelines achieve:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  raw JSON logs       \u2192 100 GB\n  columnar compressed \u2192 3\u20135 GB<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Production deployments report <strong>~90% reduction or better<\/strong>. (<a href=\"https:\/\/dev.to\/polliog\/timescaledb-compression-from-150gb-to-15gb-90-reduction-real-production-data-bnj?utm_source=chatgpt.com\">DEV Community<\/a>)<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\"># 10. What This Enables (The Really Fun Part)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once your tactical scene log is compressed like this you get:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">### Instant timeline scrubbing<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  drag slider \u2192 reconstruct scene in milliseconds<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### AI analysis<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  scan years of events<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### Forensics<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  perfect deterministic replay<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">### Training datasets<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  UAV swarm detection\n  RF triangulation ML\n  botnet geolocation patterns<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">\ud83d\udca1 <strong>Wild next step (and this one is very real):<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can compress the entire tactical event log to something like <strong>Parquet + ZSTD + delta encoding<\/strong> and stream it directly into:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  WebGPU\n  Three.js\n  Cesium\n  Unreal Engine<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">for <strong>replaying months of tactical activity like a time machine.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">If you want, I can also show you something that would make your system <strong>almost absurdly powerful<\/strong>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A &#8220;temporal spatial index&#8221; used in missile tracking and HFT called a <em>space-time cube<\/em><\/strong> that allows you to query things like:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  all UAVs within 500m of RF emitter\n  between t0 and t1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">without replaying the event log at all. It fits <em>perfectly<\/em> with your hypergraph architecture.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Medium&#8221;<br>\u25d0 The user wants to implement:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Temporal columnar compression for the event log (delta encoding, XOR floats, run-length encoding, block indexing)<\/li>\n\n\n\n<li>Hypergraph representation of the event log<\/li>\n\n\n\n<li>Space-time cube for spatial-temporal queries Let me plan this out and implement it. The key components are:<\/li>\n\n\n\n<li><code>scene_event_compressor.py<\/code> &#8211; Temporal columnar compression:\n<ul class=\"wp-block-list\">\n<li>Column separation by field type<\/li>\n\n\n\n<li>Delta encoding for timestamps<\/li>\n\n\n\n<li>XOR\/delta encoding for floats (lat\/lon)<\/li>\n\n\n\n<li>Dictionary encoding for string fields (type, id, class)<\/li>\n\n\n\n<li>Run-length encoding for repeated values<\/li>\n\n\n\n<li>Block partitioning with skip indexes, zstd compression of blocks, and integration with SceneEventLog<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>scene_hypergraph.py<\/code> &#8211; Hypergraph builder from event log:\n<ul class=\"wp-block-list\">\n<li>Builds from SceneEventLog or event stream<\/li>\n\n\n\n<li>Node types: Entity, Sensor, Signal, Location, TimeSlice, Swarm<\/li>\n\n\n\n<li>Hyperedge types: DETECTED_BY, TRIANGULATED_FROM, MEMBER_OF_SWARM, CO_MOVED_WITH, OBSERVED_AT<\/li>\n\n\n\n<li>Query interface<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><code>scene_spacetime_cube.py<\/code> &#8211; Space-time cube for spatial queries :<ul><li>3D index with latitude, longitude, and time dimensions<\/li><li>Enables queries like finding all UAVs within 500m of an emitter during a specific time window<\/li><li>Uses grid-based spatial binning with temporal slices<\/li><\/ul>Now I&#8217;m ready to start building these out. I&#8217;ll implement the columnar compression first with delta encoding and zstd, then move to the hypergraph builder leveraging the existing swarm and event log infrastructure. For the compressor, I&#8217;m using binary packing with struct and organizing events into blocks of 1000 or 10-second windows, with a skip index tracking timestamps, byte offsets, and event counts for fast lookups. The hypergraph will process events into nodes and hyperedges with a query interface, while the space-time cube creates a 3D grid indexed by latitude, longitude, and time buckets to support radius queries across spatial and temporal ranges. Now I need to design the columnar compression format carefully\u2014I&#8217;ll delta-encode timestamps as a base plus uint32 deltas, dictionary-encode the type and string fields, and handle floats with XOR delta encoding or scaled integers, then compress everything with zstd. Writing compression code\u2026<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo added: Temporal columnar compressor<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Todo started:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/scene_event_compressor.py (+562)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Compacting conversation history\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Run compressor self-test<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 scene_event_compressor.py<br>\u2514 21 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf 7.9\u00d7 compression, 12ms compress, 8.8ms full decompress, 3.9ms range query. Now build the hypergraph:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Create NerfEngine\/scene_hypergraph.py (+498)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Run hypergraph self-test<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 scene_hypergraph.py<br>\u2514 14 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Edit NerfEngine\/scene_hypergraph.py (+10 -8)<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Compaction completed. A new checkpoint has been added to your session.<br>\u2514 Use \/session checkpoints 2 to view the compaction summary.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Re-run hypergraph self-test<br>$ cd \/home\/spectrcyde\/NerfEngine &amp;&amp; python3 scene_hypergraph.py<br>\u2514 18 lines\u2026<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf All assertions pass. Now the space-time cube:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u25cf Building compression and hypergraph layers (Esc to cancel \u00b7 47.0 KiB)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In registerModel, it&#8217;s checking that the pointer type is SHARED, then calling registerModelImpl with the raw pointer and the Earth geometry&#8217;s pointer. The issue is thatNativeGeometryModel.getPointer() extracts the raw pointer from the wrapped Earth model, but if the original getEarth JNI call returned a null pointer, then we&#8217;d have a nullpointer object that would throw&hellip;&nbsp;<\/p>\n","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","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":""},"class_list":["post-5093","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=\/wp\/v2\/pages\/5093","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5093"}],"version-history":[{"count":0,"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=\/wp\/v2\/pages\/5093\/revisions"}],"wp:attachment":[{"href":"https:\/\/neurosphere-2.tail52f848.ts.net\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5093"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}