Mastodon Politics, Power, and Science: Architectural Report: The Regularization of Map Controllers

Wednesday, December 10, 2025

Architectural Report: The Regularization of Map Controllers

J. Rogers, SE Ohio





1. Executive Summary

The Codex Engine has undergone a significant architectural refactor to regularize the behavior of its two primary map views: Geographical (World/Local) and Tactical (Dungeons/Buildings). Previously, these views were managed by two distinct controllers (GeoController, TacticalController) with divergent logic for input handling, rendering, and state management. This inconsistency created a brittle and unpredictable user experience, making feature extension difficult and error-prone.

The core of the refactor was to enforce a strict, unified interface through a BaseController abstract class. This forced both controllers to implement an identical set of behaviors for core functionalities like marker interaction, navigation, and rendering.

This regularization had a profound and immediate benefit: it made the implementation of a multi-process Player View window trivial. Because the main application could now treat any active controller as an interchangeable "black box," the logic to generate a player-facing image became a single, universal function call, completely decoupled from the complexity of the underlying map type.

2. The Problem: Divergent and Brittle Architectures

The previous implementation suffered from several critical design flaws:

  • Inconsistent Input Handling: Mouse and keyboard actions behaved differently depending on the view. For example, marker dragging and context menus were fully implemented in the GeoController but were completely absent in the TacticalController. This forced the user to remember two separate control schemes.

  • Special-Cased Navigation: The logic for navigating "up" a level was a complex web of if/elif statements checking for specific node types like dungeon_complex. This was a hardcoded, genre-specific rule that made the system inflexible and difficult to debug.

  • Entangled Data and Presentation: The type of renderer used was initially tied to the node['type']. A building_interior would always attempt to use a "blueprint" style, and a dungeon_level would always use a "hand-drawn" style. This prevented stylistic flexibility, such as rendering a dungeon with a clean blueprint aesthetic.

  • State Management Chaos: State information (like the last-visited dungeon level) was stored inconsistently, sometimes on a marker, sometimes on a container node, and sometimes not at all. This led to bugs where moving a marker would "forget" its linked dungeon.

This divergent design meant that adding any new feature, like the Player View, would require writing two separate implementations and handling numerous edge cases, doubling the work and exponentially increasing the chance of bugs.

3. The Solution: Regularization via a Unified Interface

The refactor was guided by a single principle: All map controllers must behave identically from the outside.

  1. The BaseController Contract: An abstract class, BaseController, was implemented. It defines the public-facing methods that the main application loop (main.py) is allowed to call:

    • handle_input()

    • draw_map()

    • draw_overlays()

    • refresh_data()

    • render_player_view_surface()

  2. Standardized Marker Logic: The complete, working marker interaction logic from GeoController (hover, drag, short-click entry, right-click context menu) was moved into TacticalController. Now, a marker is a marker, and it behaves the same everywhere. The only difference is that dragging is restricted to the "Tools" tab in GeoController.

  3. Decoupled Rendering Style: The responsibility for choosing a visual style was removed from the controller's __init__ logic. Instead, the Generators (dungeon_gen.py, building_gen.py) now stamp each node with a render_style: 'hand_drawn' or render_style: 'blueprint' in its metadata. The TacticalController now uses a single, unified TacticalRenderer that reads this style and adjusts its drawing primitives accordingly. This makes the system data-driven and extensible to hundreds of potential styles.

4. The Payoff: A Trivial Multi-Process Player Window

The regularization of the controllers made the complex task of adding a second, player-facing window incredibly simple. The main application no longer needs to know what kind of map is active.

The Workflow:

  1. Trigger: The GM moves the "Party View" marker. The active controller (Geo or Tactical) detects the end of the drag and returns the action {"action": "update_player_view"}.

  2. Command: main.py catches this action and makes a single, universal call:
    player_surface = self.map_viewer.controller.render_player_view_surface()

  3. Polymorphism in Action: main.py does not know or care if the active controller is a GeoController or a TacticalController. It only knows that, according to the BaseController contract, the render_player_view_surface() method is guaranteed to exist.

  4. Encapsulated Rendering: The active controller performs a "headless" render of its map from the perspective of the View Marker, using its own specific render_strategy or renderer. It returns a fully drawn pygame.Surface.

  5. Inter-Process Communication (IPC): main.py takes this generic surface, serializes it to a string, and puts it onto the multiprocessing.Queue.

  6. Display: The separate player_window_process receives the image data and displays it.

Because the controllers were regularized, the main application's logic for the player window is only three lines long and works for every map type, now and in the future. The complexity is correctly encapsulated within the controllers, where it belongs.

5. Conclusion

The architectural shift from divergent, special-cased controllers to a unified, contract-based system was a critical step in maturing the Codex Engine. It has already paid significant dividends in stability and has proven its value by making a complex, multi-process feature straightforward to implement. This regularized foundation is now robust enough to support future extensions with confidence.

No comments:

Post a Comment

It is mathematically possible for Democrats to gain a majority before the midterms.

It is mathematically possible for Democrats to gain significant power or even take a technical "majority" if enough Republicans re...