Skip to content

MSD Rendering Pipeline ​

Internal architecture of the lcards-msd card: how config becomes a rendered Master Systems Display.


Overview ​

The MSD card has a dedicated rendering pipeline that lives in src/msd/. It is significantly more complex than simple LCARdS cards because it composes a base SVG with an arbitrary number of positioned overlay cards, routes SVG lines between them, and applies per-overlay animations and rules.

Non-MSD cards should use LCARdSCard directly; the MSD pipeline is purposely isolated.


Pipeline Stages ​

User config (YAML)
    │
    ▼
1. ConfigProcessor            → validate + merge pack defaults + extract SVG anchors
    │
    ▼
2. MsdCardCoordinator         → init core systems (packs, themes, datasources, rules, RouterCore)
    │
    ▼
3. CardModel.buildCardModel() → resolve base_svg/viewBox/filters, normalize overlay shape
    │
    ▼
4. AnimationManager.initialize(overlays)   → register animation definitions
    │                                          (before ModelBuilder even exists, well before any render)
    ▼
5. ModelBuilder                → resolve overlay positions, sizes, anchor bindings
    │                             (constructed here; computeResolvedModel() re-runs on every re-render)
    ▼  (produces resolvedModel)
6. AdvancedRenderer            → produce SVG markup + overlay DOM elements
    │   ├─ OverlayBase instances (control overlays)
    │   ├─ LineOverlay instances (SVG line routing)
    │   └─ ShapeOverlay instances (polyline/rect/circle geometry)
    │
    ▼
7. AnimationManager.onOverlayRendered(...) → wire each already-registered animation
    │                                          to its now-rendered DOM element
    ▼
Rendered card (Shadow DOM updated)

Note the two-step animation split: AnimationManager.initialize() (step 4) only registers animation definitions per overlay ID; it runs before ModelBuilder is even constructed, long before any DOM exists. Wiring those registrations to actual rendered elements happens later, per-overlay, via AnimationManager.onOverlayRendered() (step 7), after AdvancedRenderer.render() completes. See src/msd/pipeline/PipelineCore.js's initMsdPipeline().


Key Classes ​

ClassFileRole
PipelineCoremsd/pipeline/PipelineCore.jsTop-level entry: orchestrates all stages; returns _msdPipeline API
ConfigProcessormsd/pipeline/ConfigProcessor.jsValidates config, merges pack defaults, extracts SVG metadata and anchors
MsdCardCoordinatormsd/pipeline/MsdCardCoordinator.jsInitialises core systems in correct dependency order before overlay processing
CardModel (buildCardModel)msd/model/CardModel.jsResolves base_svg source/filters, resolves the viewBox, normalizes the overlay list into a consistent shape
ModelBuildermsd/pipeline/ModelBuilder.jsResolves overlay geometry, binding to SVG anchors, viewport scaling
AdvancedRenderermsd/renderer/AdvancedRenderer.jsMain render orchestrator; creates OverlayBase / LineOverlay instances per overlay
OverlayBasemsd/overlays/OverlayBase.jsBase class for control overlays: position, size, embedded HA card
RouterCoremsd/routing/RouterCore.jsPathfinding engine: A* routing, obstacle/crossing avoidance, trunk-and-branch bundling, channels; one instance per card. See Routing Engine
LineOverlaymsd/overlays/LineOverlay.jsPer-line renderer: resolves anchors, requests the route from RouterCore, builds SVG markup
ShapeOverlaymsd/overlays/ShapeOverlay.jsRenders shape overlays (polyline/rect/circle); sibling of LineOverlay, not a subclass; shares its style-resolution logic by adaptation, not inheritance
AttachmentPointManagermsd/renderer/AttachmentPointManager.jsResolves named attachment points on overlays for line endpoints
ViewportScalingmsd/renderer/ViewportScaling.jsScales overlay coords for the current dashboard viewport
AnchorProcessormsd/pipeline/AnchorProcessor.jsExtracts named coordinate anchors from the base SVG

Overlay Types ​

TypeClassDescription
controlOverlayBasePositions an arbitrary HA card (including LCARdS cards) at SVG coordinates
lineLineOverlaySVG polyline from source overlay to target overlay with smart routing
shapeShapeOverlayFreeform geometry: kind: polyline (routed path via RouterCore forced to manual mode), rect/circle (native <rect>/<ellipse>, reusing controls' position+size convention)

Base SVG & Anchors ​

The MSD card renders a base SVG as its background. Named anchor points embedded in the SVG (id="anchor__name") are extracted by AnchorProcessor and made available for overlay position config so overlays can snap to SVG geometry.

yaml
type: custom:lcards-msd-card
msd:
  base_svg:
    source: /local/lcards/assets/my-ship.svg
  overlays:
    - id: warp_status
      type: control
      anchor: warp_core      # an anchor harvested from the SVG (e.g. <circle id="warp_core">)
      size: [200, 90]
      card:
        type: custom:lcards-button
        preset: lozenge

Line Routing ​

All pathfinding lives in RouterCore (one instance per MSD card, shared by every line). LineOverlay resolves a line's endpoints and asks RouterCore for the path; AdvancedRenderer runs a pre-render discovery loop that routes every line (in a fixed sorted order) until the router's shared trunk/crossing registries stabilize, making bundling and crossing-avoidance outcomes independent of YAML declaration order, before the real render pass consumes the cached results.

The engine provides: A* grid routing with obstacle avoidance, cardinal anchor_side/attach_side guarantees, trunk-and-branch bundling (parallel lines share evenly-spaced lanes; channels are pre-seeded trunks), crossing avoidance, and corner rounding/beveling/smoothing.

Full internals: registries, derived lane assignment, convergence discipline, caching: Routing Engine (RouterCore). User-facing configuration: Line Routing & Channels. Manual waypoints: Manual Routing.


Shape Attachment Points ​

shape overlays register into AttachmentPointManager the same way controls do, so a line can attach_to one. This happens inline in AdvancedRenderer's render loop, right after each shape renders (before any line that might target it):

  • rect/circle: OverlayUtils.computeAttachmentPoints() (the same bbox-corner math controls use) registers the standard 9-point grid, plus kebab-case aliases for the 4 corners (top-left, top-right, bottom-left, bottom-right) alongside the camelCase keys. The alias matters: LineOverlay._resolveAttachTo() lowercases attach_side before building its virtual-anchor lookup key, and 'topLeft'.toLowerCase() → 'topleft' matches neither casing; only the kebab-case alias survives that transform. Controls have carried this same alias for the same reason since MsdControlsRenderer._computeAttachmentPointsFromBox; shape registration mirrors it explicitly rather than inheriting it.
  • polyline: each resolved vertex is registered individually as <overlayId>.vertex<N> (AttachmentPointManager.setAnchor), since the fixed 9-key bbox struct can't represent an arbitrary vertex count.

The MSD Studio editor's Connect Mode overlay (_renderAttachmentPointsOverlay() in the Studio dialog) and its drag-snap detector (_getAttachmentTargetAt()) both render/detect shape attachment points using this same convention, so clicking or drag-snapping onto a shape in the editor produces an attach_side value that resolves correctly at runtime.


Delta Updates ​

After the initial render, overlay state changes (templates, rule patches, entity state) are applied as targeted DOM mutations rather than full re-renders. AdvancedRenderer tracks overlayElementCache per overlay ID and patches only the affected element.


Pipeline API ​

The card's _msdPipeline property exposes:

javascript
_msdPipeline.render()          // Force full re-render
_msdPipeline.updateOverlay(id) // Patch single overlay
_msdPipeline.getModel()        // Resolved model snapshot
_msdPipeline.coordinator       // MsdCardCoordinator reference

See Also ​