Skip to content

memory engine occlusion#83

Open
jmalegankar wants to merge 32 commits into
devfrom
claude/memory-engine-occlusion-4T55m
Open

memory engine occlusion#83
jmalegankar wants to merge 32 commits into
devfrom
claude/memory-engine-occlusion-4T55m

Conversation

@jmalegankar
Copy link
Copy Markdown
Collaborator

No description provided.

jmalegankar and others added 4 commits April 27, 2026 14:15
…ngine

Implements the abstraction described in #8 so storage backends can be reused
across engines. Adds StoreType, refines IStore/IMemoryEngine, and ships
MemoryStore/SqliteStore backends. Wires the memory engine into the internal
context so ctx.ictx.memory is available after gamms.create_context().

Extends IGraphEngine with add_polygon/get_polygon/get_polygons/remove_polygon
which lazily back onto a polygon store from the memory engine. This is the
foundation for the occlusion work in #82.
Polygons are extracted from OSM via extract_osm_polygons / populate_polygons_from_osm
and registered with the graph engine's polygon store. Each polygon is treated
as a vertical prism: the footprint is extruded between base and base+height,
creating trapezoidal lateral faces. When height is missing the OSM tags are
consulted (height, building:height, building:levels) and otherwise defaults to
a two-storey building (~6 m) so the API stays usable without metadata.

Adds the occlusion module with segment_blocked_by_polygon /
segment_blocked_by_polygons that test 3D ray segments against the prism faces
and the polygon top (relevant for drones flying over).

Adds OCCLUDED_* SensorType entries and matching sensor variants
(OccludedMapSensor, OccludedAgentSensor, OccludedAerialSensor,
OccludedAerialAgentSensor) that filter their parent sensors' results by
testing each visibility ray against the polygon store. Ground sensors use a
configurable observer_height (eye level) for the ray origin, while aerial
sensors use the agent's 3D position.
memory_engine_test.py covers MemoryStore CRUD, SQLite-backed store
persistence (round-trip via load_store), error paths for duplicate
names/keys, and the polygon store wired onto GraphEngine.

occlusion_test.py covers the geometric primitives (low/high rays,
top-face intersection, multi-polygon iteration, degenerate input) and
end-to-end OCCLUDED_RANGE / OCCLUDED_AGENT_RANGE / OCCLUDED_AERIAL
sensor behaviour against polygons registered with the graph engine.
@jmalegankar jmalegankar linked an issue Apr 27, 2026 that may be closed by this pull request
bridgesign and others added 9 commits April 29, 2026 19:26
Rewrites MemoryStore and SqliteStore against the schema-based IStore
without leaking graph-specific concerns into the storage layer. Both
graph backends keep their own optimizations and consume the store via a
small generic extension API plus an honest backend handle. Polygons now
live in the same engine-typed store as the graph and gain a bbox-indexed
range query; occluded sensors use it to pre-filter polygon candidates.
Sensor module split by behavioral group; occluded sensors collapse to
one general class per axis with ARC/RANGE wired as factory presets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Realign stores, graphs, sensors with new IStore abstraction.
* Replace per-layer surfaces with single render surface + per-artist RGBA cache

* Reuse graph cache across camera moves via offset blit and zoom stretch

* Substitute short edges and skip sub-pixel edges and nodes in graph rendering

* Clean up docstring formatting in default_drawers

* Move cached artist rendering behind a RenderManager callback

* Merge get_culling_bounds and world_to_screen_scale into get_viewport

* Skip edge.linestring access for short or cached edges in graph rendering

* Move constants to init

* Revert graph render cache to lazy edge_line_points dict

* Apply skip/short edge optimization to render_map_sensor

* Avoid strip re-render and bilinear cost on zoom-out

* Clarify cached artist handler docstring



* render agent optimization



* get_edges in a box



* edge caching in waiting_simulation

* edge finding optimizations

* Fix projection updates on window resize

* Remove will_draw from artist interfaces and use ArtistType for static cache routing

* Replace per-artist pixel caches with per-layer surfaces

* Guard render projection against zero screen size



* Change layer cache artist_names from set to tuple

* cache mismatch bug fix

* Remove unused _static_layers from RenderManager

* Clamp render dimensions on resize

---------

Co-authored-by: Jinmin Lee <90895797+jinmin111@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mehul Sinha <mehulsinha73@gmail.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Mehul Sinha <72033137+mehulsinha73@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new structured MemoryEngine (in-memory + SQLite backends), adds occlusion-aware sensors and occlusion geometry helpers, and reworks the Pygame visualization pipeline (viewport exposure, static-layer caching, and obstacle rendering from OSM-derived faces).

Changes:

  • Add a structured MemoryEngine API with MemoryStore/SqliteStore implementations and corresponding tests.
  • Add occlusion geometry + occlusion-aware sensor variants, plus end-to-end occlusion tests.
  • Rework visualization rendering/caching and add obstacle-face extraction + rendering utilities.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/occlusion_test.py Adds geometry + end-to-end occluded sensor tests (currently assumes polygon-store APIs).
tests/memory_engine_test.py Adds store contract tests for MemoryStore/SqliteStore + polygon-store tests (currently mismatched to implementation).
gamms/VisualizationEngine/render_manager.py Adds cached-layer handler hook, viewport bounds helpers, and projection/bounds recalculation helpers.
gamms/VisualizationEngine/pygame_engine.py Reworks rendering to a unified render surface with per-layer caching and exposes get_viewport().
gamms/VisualizationEngine/no_engine.py Adds get_viewport() stub returning None.
gamms/VisualizationEngine/default_drawers.py Adds viewport-aware pixel-threshold skipping and obstacle rendering from obstacle faces.
gamms/VisualizationEngine/artist.py Removes will-draw flag plumbing from Artist implementation.
gamms/VisualizationEngine/init.py Adds visualization constants for caching and pixel thresholds.
gamms/typing/visualization_engine.py Adds get_viewport() to the visualization engine interface; simplifies color typing.
gamms/typing/sensor_engine.py Adds occlusion sensor enum values.
gamms/typing/memory_engine.py Replaces legacy “artifact store” style interface with structured store/map API.
gamms/typing/graph_engine.py Adds obstacle-face API to graph engine typing.
gamms/typing/artist.py Removes will-draw methods from the artist typing interface.
gamms/typing/init.py Re-exports new memory engine types and obstacle face type.
gamms/SensorEngine/sensors_occluded.py Adds occlusion-aware sensor implementations (currently depends on polygon-store APIs).
gamms/SensorEngine/sensors_basic.py Extracts basic ground sensors into a dedicated module.
gamms/SensorEngine/sensors_aerial.py Extracts aerial sensors + quaternion helpers into a dedicated module.
gamms/SensorEngine/sensor_engine.py Refactors factory to use extracted sensor modules and adds occluded sensor creation paths.
gamms/SensorEngine/occlusion.py Adds prism occlusion geometry routines.
gamms/osm.py Adds OSM obstacle face extraction helpers and obstacle-from-place/xml functions.
gamms/osm_constants.py Adds obstacle tag filters, height estimates, and color maps.
gamms/MemoryEngine/store.py Implements MemoryStore and SqliteStore with schema + CBOR-encoded complex fields.
gamms/MemoryEngine/memory_engine.py Implements MemoryEngine store registry and lifecycle.
gamms/MemoryEngine/init.py Exposes MemoryEngine public API.
gamms/internal_context.py Makes internal engines optional and terminates them conditionally.
gamms/GraphEngine/graph_engine.py Refactors graph storage onto MemoryEngine stores and adds obstacle-face storage/query.
gamms/init.py Wires MemoryEngine into context creation via InternalContext.
examples/base/config.py Changes example defaults (NO_VIS, different location).
Comments suppressed due to low confidence (6)

tests/memory_engine_test.py:63

  • This test expects ValueError on duplicate primary-key insert, but MemoryStore.insert_data/SqliteStore.insert_data currently raise KeyError for duplicate keys. Update the test expectation (or change the stores) to ensure a consistent error contract.
    def test_duplicate_key_raises(self):
        self.store.create_map('m', {'id': int, 'value': int}, 'id')
        self.store.insert_data('m', {'id': 1, 'value': 10})
        with self.assertRaises(ValueError):
            self.store.insert_data('m', {'id': 1, 'value': 20})

tests/memory_engine_test.py:124

  • bulk_insert, count, and create_index are used here but are not implemented on IStore, MemoryStore, or SqliteStore in this branch. This will fail at runtime. Either implement these methods (and add them to the typing interface) or remove/replace these tests with the APIs that actually exist.
    def test_bulk_insert_and_count(self):
        self.store.create_map('m', {'id': int, 'value': int}, 'id')
        self.store.bulk_insert('m', [
            {'id': 1, 'value': 1},
            {'id': 2, 'value': 2},
            {'id': 3, 'value': 3},
        ])
        self.assertEqual(self.store.count('m'), 3)
        self.assertEqual(set(self.store.query_keys('m')), {1, 2, 3})

    def test_create_index(self):
        self.store.create_map('m', {'id': int, 'x': float, 'y': float}, 'id')
        self.store.create_index('m', ('x', 'y'))

tests/memory_engine_test.py:135

  • MemoryStoreTest calls self.store.get_map('m'), but MemoryStore does not expose get_map in this branch (and it is not part of IStore). This test will fail unless the method is added or the test is updated to assert via the public IStore APIs.
    def test_get_map_backend_handle(self):
        self.store.create_map('m', {'id': int, 'name': str}, 'id')
        self.store.insert_data('m', {'id': 1, 'name': 'foo'})
        backing = self.store.get_map('m')
        self.assertEqual(backing[1], {'id': 1, 'name': 'foo'})

tests/memory_engine_test.py:223

  • This suite assumes a polygon API on ctx.graph (add_polygon, get_polygons, get_polygon, remove_polygon), but the current IGraphEngine/GraphEngine interfaces in this branch only define obstacle-face operations. Unless polygon support is added back, these tests will fail with AttributeError.
    def test_add_get_remove_polygon(self):
        coords = [(0, 0), (10, 0), (10, 10), (0, 10)]
        self.ctx.graph.add_polygon(0, coords)
        self.assertEqual(list(self.ctx.graph.get_polygons()), [0])
        record = self.ctx.graph.get_polygon(0)
        self.assertEqual(record['id'], 0)
        self.assertEqual(record['height'], 6.0)
        self.assertEqual(record['category'], 'building')

        self.ctx.graph.add_polygon(1, coords, height=12.5, category='foliage')
        self.assertEqual(set(self.ctx.graph.get_polygons()), {0, 1})
        self.assertEqual(self.ctx.graph.get_polygon(1)['height'], 12.5)
        self.assertEqual(self.ctx.graph.get_polygon(1)['category'], 'foliage')

        self.ctx.graph.remove_polygon(0)
        self.assertEqual(list(self.ctx.graph.get_polygons()), [1])

tests/memory_engine_test.py:76

  • MemoryStore/SqliteStore raise IndexError when a map name doesn’t exist (via _require_map / _require_schema), but this test expects KeyError. Update the test to match the current store contract (or change the stores to raise KeyError) so missing-map behavior is consistent.
    def test_unknown_map_raises(self):
        with self.assertRaises(KeyError):
            self.store.insert_data('nope', {'id': 1})
        with self.assertRaises(KeyError):
            self.store.get_data('nope', 1)
        with self.assertRaises(KeyError):
            list(self.store.query_keys('nope'))

tests/memory_engine_test.py:83

  • SqliteStore.delete_map currently raises IndexError when the map is missing, while this test expects KeyError. Decide on a single exception type for “unknown map” across store backends and tests (and align the typing docs accordingly).
    def test_delete_map(self):
        self.store.create_map('m', {'id': int}, 'id')
        self.store.delete_map('m')
        self.assertNotIn('m', self.store.list_maps())
        with self.assertRaises(KeyError):
            self.store.delete_map('m')


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/occlusion_test.py Outdated
Comment thread tests/memory_engine_test.py Outdated
Comment on lines 447 to 452
def load(self, path: str) -> IGraph:
"""
Loads a graph from a file.
"""
self._graph.load(path)
return self.graph
Comment thread gamms/GraphEngine/graph_engine.py
Comment thread gamms/GraphEngine/graph_engine.py
Comment thread gamms/MemoryEngine/store.py
Comment thread gamms/MemoryEngine/store.py
Comment thread gamms/SensorEngine/sensors_occluded.py Outdated
Comment thread gamms/osm.py Outdated
AGENT_ARC = 7
AERIAL = 8
AERIAL_AGENT = 9
OCCLUDED_MAP = 10
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only keep occluded_map occluded_agent occluded_aerial occluded_aerial_agent

Comment thread gamms/SensorEngine/sensors_occluded.py Outdated
return faces


def _is_blocked(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not assume trapezoid. Do it for general quadrilateral

Comment thread gamms/SensorEngine/sensors_occluded.py Outdated
super().sense(node_id)
current_node = self.ctx.graph.graph.get_node(node_id)
origin = self._origin(current_node)
faces = _faces_in_range(self.ctx, origin[0], origin[1], self.range)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a standard api call. Given initial output from map sensor, create a numpy array and do blocking checks on that. Plus, batch the faces based on how the graph engine iterator yields

Comment thread gamms/SensorEngine/sensors_occluded.py Outdated
super().sense(node_id)
current_node = self.ctx.graph.graph.get_node(node_id)
origin = self._origin(current_node)
faces = _faces_in_range(self.ctx, origin[0], origin[1], self.range)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not enumerate all faces. Even if one face blocks, it is blocked

Comment thread gamms/VisualizationEngine/default_drawers.py Outdated
Comment thread gamms/VisualizationEngine/default_drawers.py Outdated
@jmalegankar jmalegankar changed the title Claude/memory engine occlusion 4 t55m memory engine occlusion May 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate occlusion from buildings using OSM data

5 participants