Skip to content

Latest commit

 

History

History
715 lines (510 loc) · 29.3 KB

File metadata and controls

715 lines (510 loc) · 29.3 KB

Space-Time Map Layout Algorithm — Worked Examples

Version: 0.3.0

Companion to: layout-algorithm-specification.md Purpose: Traces two concrete fixtures through every stage of the nine-stage pipeline, producing exact intermediate values at each stage boundary. Intended to be read alongside the specification, one document per monitor.

All values here are confirmed by the project's unit test suite. If any value in this document conflicts with a passing test, the test is authoritative.


Fixtures

Two data files in docs/example-data are used:

  • meetings.ttl — three Persons, two Activities, states on Alice. Simple fixture: no installations, no mereologically transitions.
  • aircraft684_diagram.ttl — Systems, component slots, installations, slot co-location. Complex fixture: multiple timebands with Y-position changes.

Fixture 1: meetings.ttl

Input Summary

Entity Type Key relationships
alice Person states: Active (2021-06-01 → 2023-06-01), Retired (2023-06-01 → 2025-02-01)
bob Person no bounds, no states
carol Person no bounds, no states
project-meeting Activity 2022-09-01 → 2023-02-01; participations: alice, bob, carol
workshop Activity 2023-04-15 → 2023-11-01; participations: alice, carol

Persons are FusionOfIntegralParts (Person is a subclass), but have no visible internal participants in this diagram → rendered as plain bands. Activities render as fusion outlines spanning their participants.


Stage 1 — Ordering Graph Construction

Sub-step 1.1 — Metric breakpoints:

Seven breakpoints are collected from the ParticularPeriod timestamps:

Index Timestamp Starting entities Ending entities
0 2021-06-01 alice-state-active
1 2022-09-01 project-meeting
2 2023-02-01 project-meeting
3 2023-04-15 workshop
4 2023-06-01 alice-state-retired alice-state-active
5 2023-11-01 workshop
6 2025-02-01 alice-state-retired

Note: Activities and states both produce breakpoints. States (like alice-state-active) are temporal parts of individuals and generate breakpoints where they start and end — these affect the internal participation/state track display, not Y positions or transitions.

Sub-steps 1.2–1.3 — Ordering constraints:

No entirelyAfter triples in this fixture. Metric inferred edges are generated from adjacent timestamps (e.g., alice-state-active ends at BP_4, alice-state-retired starts at BP_4 — same timestamp, no inferred edge between them; project-meeting ends at BP_2, workshop starts at BP_3 — adjacent timestamps, inferred edge generated).

Sub-step 1.4 — DAG: No cycles. Pipeline continues.

Sub-step 1.5 — Ordinal breakpoints: All entities have metric bounds. No ordinal breakpoints are injected.

Seven metric breakpoints → eight timebands.


Stage 2 — Timeband Decomposition

includeFirst = true (bob.start === undefined, carol.start === undefined). includeLast = true (bob.end === undefined, carol.end === undefined).

Index Left BP Right BP Notes
0 null 2021-06-01 Left-open; alice not yet active
1 2021-06-01 2022-09-01 alice-state-active begins
2 2022-09-01 2023-02-01 project-meeting active
3 2023-02-01 2023-04-15 gap between activities
4 2023-04-15 2023-06-01 workshop active
5 2023-06-01 2023-11-01 alice-state-retired active; workshop active
6 2023-11-01 2025-02-01 alice-state-retired active; workshop ended
7 2025-02-01 null Right-open; alice-state-retired ended

Eight timebands. ✓


Stage 3 — Entity State Resolution

Entity Changes across timebands
alice participatingInFusions: adds project-meeting in TB_2, removes in TB_3; adds workshop in TB_4, removes in TB_6. activeStatePartIds: [alice-state-active] in TB_1–TB_4; [alice-state-retired] in TB_5–TB_6; [] in TB_0 and TB_7. No fusedIntoSlots ever set.
bob participatingInFusions: project-meeting in TB_2 only. activeStatePartIds: always [].
carol participatingInFusions: project-meeting in TB_2; workshop in TB_4 and TB_5. activeStatePartIds: always [].

No installations. All fusedIntoSlots remain [] throughout.


Stage 4 — Co-location Groups

None. No slot-to-slot fusions, no installations. No co-location in any timeband.


Stage 5 — Y-Position Assignment

Settings: minBandHeight=30, paddingV=15, rootGap=5, minInternalRowHeight=20.

Pre-computation — sub-row counts:

  • alice: max concurrent = 2 (e.g. TB_2: alice-state-active sub-row + project-meeting participation). Height = max(30, 2×20) = 40.
  • bob: max concurrent = 1 (TB_2: project-meeting participation). Height = max(30, 1×20) = 30.
  • carol: max concurrent = 1. Height = 30.

Pass 1 — Global tree (all three are roots; alphabetical order):

alice:  height=40, yStart=0                  (cursor starts at 0)
bob:    height=30, yStart=45  (40 + rootGap 5)
carol:  height=30, yStart=80  (45 + 30 + rootGap 5)

No structural parent–child relationships. No internal node expansion needed.

Pass 2 — No co-location groups in any timeband. Assignments are identical to Pass 1 in all timebands.

Final Y layout (identical in all 8 timebands):

Entity yStart height
alice 0 40
bob 45 30
carol 80 30

Heights are constant — Stage 6 will detect zero transition groups for this fixture.


Stage 6 — Transition Group Construction

TransitionGroupList = [] (0 groups).

alice, bob, and carol have identical Y positions across all timebands — no yStart changes are detected at any breakpoint. The Activities and States produce breakpoints but do not move any entity's band vertically.


Stage 7 — Horizontal Section Planning

Settings: chevronOffset=20, minPaddingPx=20, minStraightSectionPx=10, paddingBetweenTransitionsPx=20.

alice, bob, and carol are open-started and open-ended (depth 0 via isPartOf). However, their participation entities (e.g. alice's participation-alice-meeting) have isPartOf project-meeting, giving nesting depth 1. Max nesting depth among open-started entities = 1.

paddingLeftPx  = 1 × 20 + 20 = 40
paddingRightPx = 40  (same entities in TB_7)

No TransitionGroup → no transition sections. The 8 straight-A sections flow directly into each other. Activity outline boxes (project-meeting, workshop) span the relevant straight-A sections without needing a horizontal transition zone.

# Type timebandIndex minWidthPx
1 padding-left null 40
2 straight-A 0 10
3 straight-A 1 10
4 straight-A 2 10
5 straight-A 3 10
6 straight-A 4 10
7 straight-A 5 10
8 straight-A 6 10
9 straight-A 7 10
10 padding-right null 40

Total totalMinWidthPx = 160 px. Section count = 2 + 8 + 0 + 0 = 10 ✓


Stage 8 — Linear Expansion and Final Emission

This fixture has open-bounded timebands: TB_0 has no left metric boundary (null start), and TB_7 has no right metric boundary (null end). However, the 6 interior timebands (TB_1 through TB_6) are bounded by the 7 metric timestamps spanning from 2021-06-01 to 2025-02-01.

minLinearWidthPx is null (open-ended timebands exist). isNonLinear = true. The interior bounded sections receive width proportional to their duration; the open-ended sections receive minWidthPx.

For a canvas large enough to proportionally scale the interior: fixedWidthPx = 40 + 40 = 80 (paddings only). availableForBounded = (canvas − 80). totalBoundedDurationMs = 2025-02-01 − 2021-06-01 ≈ 113,270,400,000 ms. Each bounded interior section gets width proportional to its duration.

timeRange = { startMs: 2021-06-01, endMs: 2025-02-01 } (from the outermost metric-bounded entities at depth 0).


Stage 9 — Worm Plan Construction

Pass A — Z-order:

No installations ever occur. Structural tree: three independent roots. DFS preorder on alphabetically-sorted roots:

alice  → Z = 0
bob    → Z = 1
carol  → Z = 2

Pass B — Segments (alice, at canvas=160 = totalMinWidthPx):

All sections at minWidthPx. Cumulative left-X:

Section # Left X
0 (padding-left) 0
1 (SA-TB_0) 40
2 (SA-TB_1) 50
3 (SA-TB_2) 60
4 (SA-TB_3) 70
5 (SA-TB_4) 80
6 (SA-TB_5) 90
7 (SA-TB_6) 100
8 (SA-TB_7) 110
9 (padding-right) 120

alice: yStart=0, height=40yPos = 0 + 40/2 = 20. nestingDepthStart = 0, unknownStart = true → no left inset. nestingDepthEnd = 0, unknownEnd = truexPos = 160 − (0+1) × 10 = 150.

N=10 active sections → 11 segments:

i xPos yPos note
0 0 20 left body face; depth=0, unknownStart
1 40 20 SA-TB_0 left
2 50 20 SA-TB_1 left
3 60 20 SA-TB_2 left
4 70 20 SA-TB_3 left
5 80 20 SA-TB_4 left
6 90 20 SA-TB_5 left
7 100 20 SA-TB_6 left
8 110 20 SA-TB_7 left
9 120 20 padding-right left
10 150 20 right body face; depth=0, unknownEnd

unknownStart = true, unknownEnd = true. Height = 40. Style: defaultBand.

bob: yPos = 45 + 15 = 60. carol: yPos = 80 + 15 = 95. Identical pattern.

Pass C — Style: All three persons → defaultBand.



Fixture 2: aircraft684_diagram.ttl

Input Summary for Fixture 2

Entity Type(s) Key Relationships
aircraft684 System no bounds
port-engine-component System, ReplaceableLifespan isPartOf aircraft684
fuelpump-component ReplaceableLifespan isPartOf port-engine-component
engine329-fuelpump-component ReplaceableLifespan isPartOf engine329
engine329 Artefact, System no bounds; installed into port-engine-component
fuelpump739 Artefact no bounds; installed into engine329-fuelpump-component
sysCompFusion FusionOfIntegralParts links fuelpump-componentengine329-fuelpump-component; ends 2020-01-01
engine329-installed Installed links engine329port-engine-component; ends 2020-01-01
fuelpump739-installed Installed links fuelpump739engine329-fuelpump-component; ends 2024-06-01

Stage 1 — Ordering Graph Construction

Sub-step 1.1 — Metric breakpoints:

Index Timestamp Ending entities
0 2020-01-01 sysCompFusion, engine329-installed
1 2024-06-01 fuelpump739-installed

Two breakpoints. No explicit entirelyAfter triples. Inferred edge: engine329-installed#end → fuelpump739-installed#end (BP_0 < BP_1). No cycles.

Sub-step 1.5 — No ordinal breakpoints (all entities with bounds have metric breakpoints).

Two metric breakpoints → three timebands.


Stage 2 — Timeband Decomposition

includeFirst = true (engine329.start === undefined, fuelpump739.start === undefined, aircraft684.start === undefined — no metric starts). includeLast = true (same entities have no metric ends).

Index Left BP Right BP Description
0 null 2020-01-01 Both installations and slot fusion active
1 2020-01-01 2024-06-01 engine329 uninstalled; fuelpump739 still installed
2 2024-06-01 null fuelpump739 uninstalled; all standalone

Three timebands. ✓


Stage 3 — Entity State Resolution

Only the rows that change across timebands are shown in detail:

Entity TB_0 TB_1 TB_2
engine329-installed (isActive) yes no no
sysCompFusion (isActive) yes no no
fuelpump739-installed (isActive) yes yes no
engine329 fusedIntoSlots [port-engine-component] [] []
fuelpump739 fusedIntoSlots [engine329-fuelpump-component] [engine329-fuelpump-component] []
fuelpump-component activeSlotFusionIds [sysCompFusion] [] []
engine329-fuelpump-component activeSlotFusionIds [sysCompFusion] [] []

Structural entities (aircraft684, port-engine-component, fuelpump-component, engine329-fuelpump-component, engine329) are always active with fixed mereologicalParentId.


Stage 4 — Co-location Groups

TB_0: Two groups arising from the active installation and slot fusion:

  1. Slot-fusion group (groupId=sysCompFusion):

    • Raw members from activeSlotFusionIds: fuelpump-component and engine329-fuelpump-component (both slots in the fusion).
    • fuelpump739 is installed into engine329-fuelpump-component (a group member) → Union-Find merges its install pair into this group.
    • Final members: { engine329-fuelpump-component, fuelpump-component, fuelpump739 }.
    • Both engine329-fuelpump-component and fuelpump-component are ReplaceableLifespan → multiple slots → alphabetically first = engine329-fuelpump-component (canonical).
  2. Install group (groupId=install:port-engine-component:engine329):

    • engine329.fusedIntoSlots = [port-engine-component] → pair { port-engine-component, engine329 }.
    • Exactly one slot member: port-engine-component (canonical).

TB_1: One group:

  • fuelpump739.fusedIntoSlots = [engine329-fuelpump-component] → pair { engine329-fuelpump-component, fuelpump739 }.
  • Canonical: engine329-fuelpump-component (the only slot member).

TB_2: No groups.


Stage 5 — Y-Position Assignment

Settings: minBandHeight=30, paddingV=15, rootGap=5, minInternalRowHeight=20.

Pre-computation: All entities have subRowCount = 0 (no participations or state sub-rows). Height = max(30, 0×20) = 30 for all. Except containers: aircraft684 and port-engine-component expand in Pass 1 to contain their children.

Pass 1 — Global structural tree

The global structural tree (from invariant mereologicalParentId):

Root: aircraft684
  └── port-engine-component  [isPartOf]
        └── fuelpump-component  [isPartOf]

Root: engine329
  └── engine329-fuelpump-component  [isPartOf]

Root: fuelpump739  (leaf)

Installed entities (engine329, fuelpump739) are not tree-children of their host slots. Installations are handled by Pass 2 co-location, not by the structural tree.

Root ordering is alphabetical by IRI: aircraft684, engine329, fuelpump739.

Depth-first placement:

Derivation:

cursor=0
aircraft684 (internal) → cursor += 15 → cursor=15
  port-engine-component (internal) → cursor += 15 → cursor=30
    fuelpump-component (leaf): yStart=30, height=30; cursor += 30+5 → cursor=65
  port-engine-component: recompute(children=[fuelpump-component])
    minChildY=30, maxChildBottom=60
    yStart=30−15=15, height=60+15−15=60
  (port-engine-component is not a root, so cursor is not advanced by port-engine-component itself)
aircraft684: recompute(children=[port-engine-component])
  minChildY=15, maxChildBottom=75
  yStart=15−15=0, height=75+15−0=90
(aircraft684 is root) → cursor = aircraft684.yStart + aircraft684.height + rootGap = 0+90+5=95

cursor=95
engine329 (internal) → cursor += 15 → cursor=110
  engine329-fuelpump-component (leaf): yStart=110, height=30; cursor += 30+5 → cursor=145
engine329: recompute(children=[engine329-fuelpump-component])
  yStart=110−15=95, height=(110+30)+15−95=60
(engine329 is root) → cursor = 95+60+5=160

cursor=160
fuelpump739 (leaf): yStart=160, height=30; cursor=195

Pass 1 global base (before co-location):

aircraft684:                  yStart=0,   height=90
port-engine-component:        yStart=15,  height=60
fuelpump-component:           yStart=30,  height=30
engine329:                    yStart=95,  height=60
engine329-fuelpump-component: yStart=110, height=30
fuelpump739:                  yStart=160, height=30

Pass 2 — TB_0 co-location overrides

The working assignments map starts as a copy of the Pass 1 global base. Groups are processed install groups first, then slot-fusion groups.

Step A — Install group { port-engine-component (canonical, yStart=15, height=60), engine329 (yStart=95, height=60) }:

delta = canonical.yStart − member.yStart = 15 − 95 = −80
shiftSubtree(engine329, −80):
  engine329:                    yStart = 95 + (−80) = 15
  engine329-fuelpump-component: yStart = 110 + (−80) = 30   ← entire subtree shifts
pin(engine329, { yStart: 15, height: 60 })
re-expand ancestors of engine329: none (engine329 is a root)

Assignments after install group:

aircraft684:                  yStart=0,   height=90
port-engine-component:        yStart=15,  height=60
fuelpump-component:           yStart=30,  height=30
engine329:                    yStart=15,  height=60   ← shifted
engine329-fuelpump-component: yStart=30,  height=30   ← shifted with engine329's subtree
fuelpump739:                  yStart=160, height=30

Step B — Slot-fusion group { engine329-fuelpump-component (canonical, now yStart=30), fuelpump-component (yStart=30), fuelpump739 (yStart=160) }:

The canonical (engine329-fuelpump-component) is now at yStart=30 — it was moved by the install group in Step A.

Process fuelpump-component:

delta = 30 − 30 = 0   ← already aligned; no movement

Process fuelpump739:

delta = 30 − 160 = −130
shiftSubtree(fuelpump739, −130): fuelpump739 yStart = 160 + (−130) = 30
pin(fuelpump739, { yStart: 30, height: 30 })
re-expand ancestors of fuelpump739: none (fuelpump739 is a root)

Key insight: The install group runs first and shifts engine329's entire subtree (including engine329-fuelpump-component) from 110→30. By the time the slot-fusion group runs, its canonical member already sits at 30, so fuelpump-component requires no movement (it was always at 30 in the global base). Only fuelpump739 moves in the slot-fusion step.

TB_0 final Y layout (confirmed by tests):

aircraft684:                  yStart=0,   height=90
port-engine-component:        yStart=15,  height=60   ←→ engine329 (co-located; install group)
fuelpump-component:           yStart=30,  height=30   ←→ engine329-fuelpump-component, fuelpump739 (co-located; slot-fusion)
engine329:                    yStart=15,  height=60   [isInstalledSystem=true]
engine329-fuelpump-component: yStart=30,  height=30
fuelpump739:                  yStart=30,  height=30

Containment check: fuelpump-component [30..60] ⊂ port-engine-component [15..75] ⊂ aircraft684 [0..90]. ✓

Pass 2 — TB_1

Same global base. One co-location group: { engine329-fuelpump-component (canonical, yStart=110), fuelpump739 (yStart=160) }.

No install groups. Slot-fusion/install group:

delta = 110 − 160 = −50
shiftSubtree(fuelpump739, −50): fuelpump739 yStart = 160 + (−50) = 110

TB_1 final Y layout (confirmed by tests):

aircraft684:                  yStart=0,   height=90
port-engine-component:        yStart=15,  height=60
fuelpump-component:           yStart=30,  height=30
engine329:                    yStart=95,  height=60   [standalone; no active install]
engine329-fuelpump-component: yStart=110, height=30   ←→ fuelpump739 (co-located)
fuelpump739:                  yStart=110, height=30

Pass 2 — TB_2

No co-location groups. All assignments from global base applied directly.

TB_2 final Y layout (confirmed by tests):

aircraft684:                  yStart=0,   height=90
port-engine-component:        yStart=15,  height=60
fuelpump-component:           yStart=30,  height=30
engine329:                    yStart=95,  height=60
engine329-fuelpump-component: yStart=110, height=30
fuelpump739:                  yStart=160, height=30   [standalone root]

Stage 6 — Transition Group Construction

At breakpoint 0 (2020-01-01)

Y comparison TB_0 → TB_1:

Entity TB_0 yStart TB_1 yStart Changed?
aircraft684 0 0 No
port-engine-component 15 15 No
fuelpump-component 30 30 No
engine329 15 95 Yes
engine329-fuelpump-component 30 110 Yes
fuelpump739 30 110 Yes

Three transitions in one group (breakpointIndex=0):

Entity fromCenterY toCenterY height direction
engine329 15+30=45 95+30=125 60 exit (fusedIntoSlots shrank)
engine329-fuelpump-component 30+15=45 110+15=125 30 exit (fallback: toCenterY > fromCenterY)
fuelpump739 30+15=45 110+15=125 30 exit (fallback)
verticalTravel values: |125−45|=80, |125−45|=80, |125−45|=80
maxTravel = 80
widthPx = max(50, 80/4) = max(50, 20) = 50
tickEdge = 'left' (all exit)

At breakpoint 1 (2024-06-01)

Y comparison TB_1 → TB_2:

Entity TB_1 yStart TB_2 yStart Changed?
aircraft684 0 0 No
port-engine-component 15 15 No
fuelpump-component 30 30 No
engine329 95 95 No
engine329-fuelpump-component 110 110 No
fuelpump739 110 160 Yes

One transition in one group (breakpointIndex=1):

Entity fromCenterY toCenterY height direction
fuelpump739 110+15=125 160+15=175 30 exit (fusedIntoSlots shrank)
verticalTravel = |175−125| = 50
widthPx = max(50, 50/4) = 50
tickEdge = 'left'

Stage 7 — Horizontal Section Planning

Settings: chevronOffset=20, minPaddingPx=20, minStraightSectionPx=10, paddingBetweenTransitionsPx=20.

Padding-left depth: In TB_0, fuelpump739 has start === undefined and the deepest chain: installed into engine329-fuelpump-component (+1), which is isPartOf engine329 (+1), which is installed into port-engine-component (+1), which is isPartOf aircraft684 (+1). Depth = 4.

paddingLeftPx = 4 × 20 + 20 = 100

Straight-B after BP_0: fuelpump739 transitions at both BP_0 and BP_1 (consecutive breakpoints) → straight-B is inserted between them.

Padding-right depth: In TB_2, fuelpump739 has no temporal hosting (depth 0, standalone). engine329 has no active installation (depth 0). Deepest chain: fuelpump-componentport-engine-componentaircraft684 = depth 2.

paddingRightPx = 2 × 20 + 20 = 60
# Type timebandIndex breakpointIndex minWidthPx Note
1 padding-left null null 100 depth=4
2 straight-A 0 null 10
3 transition null 0 50 BP_0, tickEdge='left'
4 straight-B null null 20 fuelpump739 at both BP_0 and BP_1
5 straight-A 1 null 10
6 transition null 1 50 BP_1, tickEdge='left'
7 straight-A 2 null 10
8 padding-right null null 60 depth=2

Total totalMinWidthPx = 310 px. Section count = 2 + 3 + 2 + 1 = 8 ✓

Breakpoint tick placement: BP_0 and BP_1 both have tickEdge='left', so their ticks sit at the left edge of their transition sections:

  • BP_0 tick at x = 100 + 10 = 110
  • BP_1 tick at x = 100 + 10 + 50 + 20 + 10 + ... wait, let me compute cumulative lefts:
    • Section 1 (padding-left): x=0, width=100 → right at 100
    • Section 2 (SA-TB_0): x=100, width=10 → right at 110
    • Section 3 (transition BP_0): x=110, tickEdge='left' → tick at x=110
    • Section 4 (SB): x=160, width=20 → right at 180
    • Section 5 (SA-TB_1): x=180, width=10 → right at 190
    • Section 6 (transition BP_1): x=190, tickEdge='left' → tick at x=190
    • Section 7 (SA-TB_2): x=240, width=10 → right at 250
    • Section 8 (padding-right): x=250, width=60 → total=310

Stage 8 — Linear Expansion and Final Emission

This fixture has open-ended timebands: TB_0 has no left metric boundary (null), and TB_2 has no right metric boundary (null). minLinearWidthPx = null (full linearity impossible; cannot scale proportionally without bounded endpoints). isNonLinear = true.

Only TB_1 is fully metric-bounded (BP_0 = 2020-01-01 to BP_1 = 2024-06-01, duration ≈ 143,280,000,000 ms). The open-ended timebands TB_0 and TB_2 receive actualWidthPx = minWidthPx.

At canvas=310 (totalMinWidthPx): all sections at minimum width. actualWidthPx = minWidthPx throughout.

totalWidthPx = 310. totalHeightPx = max(yStart + height in TB_2) = max of aircraft684 (0+90=90), port-engine-component (15+60=75), fuelpump-component (30+30=60), engine329 (95+60=155), engine329-fuelpump-component (110+30=140), fuelpump739 (160+30=190) = 190 px.

timeRange from depth-0 entities (those with no isPartOf parent): aircraft684, engine329, fuelpump739 — all have undefined metric bounds → timeRange = { startMs: 0, endMs: 0 } (default; no metric timestamps on top-level entities).


Stage 9 — Worm Plan Construction

Pass A — Z-order

Structural tree (from mereologicalParentId):

aircraft684 (root)
  └── port-engine-component
        └── fuelpump-component
engine329 (root)
  └── engine329-fuelpump-component
fuelpump739 (root)

Virtual-installed edges (across all timebands):

  • engine329 was installed into port-engine-component → add engine329 as virtual-installed child of port-engine-component.
  • fuelpump739 was installed into engine329-fuelpump-component → add fuelpump739 as virtual-installed child of engine329-fuelpump-component.

Augmented tree (structural children first alphabetically, then virtual-installed alphabetically):

aircraft684 (Z=0)
  └── port-engine-component  [structural] (Z=1)
        ├── fuelpump-component  [structural] (Z=2)
        └── engine329  [virtual-installed] (Z=3)
              └── engine329-fuelpump-component  [structural] (Z=4)
                    └── fuelpump739  [virtual-installed] (Z=5)

DFS preorder: aircraft684=0, port-engine-component=1, fuelpump-component=2, engine329=3, engine329-fuelpump-component=4, fuelpump739=5.

Pass B — Segments (fuelpump739, at canvas=310)

Section boundaries (all at minWidthPx):

# Section Width xLeft
1 padding-left 100 0
2 straight-A (TB_0) 10 100
3 transition (BP_0) 50 110
4 straight-B 20 160
5 straight-A (TB_1) 10 180
6 transition (BP_1) 50 190
7 straight-A (TB_2) 10 240
8 padding-right 60 250

fuelpump739 center Y per timeband: TB_0 30+15=45, TB_1 110+15=125, TB_2 160+15=175.

nestingDepthStart = 4 (fp739 in TB_0: install-hop to engine329-fuelpump-component, then isPartOf to engine329, then install-hop to port-engine-component, then isPartOf to aircraft684). unknownStart = truesegments[0].xPos = 0 + 4 × 10 = 40.

nestingDepthEnd = 0 (fp739 in TB_2: no temporal hosting, standalone root). unknownEnd = truesegments[8].xPos = 310 − (0+1) × 10 = 300.

N=8 active sections → 9 segments:

i Vertex xPos yPos Note
0 left body face 40 45 pre-baked; depth=4, unknownStart
1 SA-TB_0 left 100 45 T_s = TB_0
2 TR-BP_0 left 110 45 source Y (TB_0 left edge of transition)
3 SB left 160 125 destination Y carried here (TB_1)
4 SA-TB_1 left 180 125 T_s = TB_1
5 TR-BP_1 left 190 125 source Y (TB_1 left edge of transition)
6 SA-TB_2 left 240 175 destination Y carried here (TB_2)
7 PR left 250 175 T_s = TB_2
8 right body face 300 175 pre-baked; depth=0, unknownEnd

The renderer connects consecutive dots: the transition diagonal at BP_0 runs from segment[2] to segment[3] (yPos 45→125). No Y-averaging is needed.

Pass B — Segments (engine329)

engine329 center Y per timeband: TB_0 15+30=45, TB_1 95+30=125, TB_2 95+30=125.

nestingDepthStart = 2 (engine329 in TB_0: install-hop to port-engine-component, then isPartOf to aircraft684). unknownStart = truesegments[0].xPos = 0 + 2 × 10 = 20.

nestingDepthEnd = 0 (engine329 in TB_2: not installed). unknownEnd = truesegments[8].xPos = 310 − 10 = 300.

i Vertex xPos yPos Note
0 left body face 20 45 depth=2, unknownStart
1 SA-TB_0 left 100 45
2 TR-BP_0 left 110 45 source Y
3 SB left 160 125 destination Y carried here
4 SA-TB_1 left 180 125
5 TR-BP_1 left 190 125 source = destination = 125 (no Y change)
6 SA-TB_2 left 240 125
7 PR left 250 125
8 right body face 300 125 depth=0, unknownEnd

engine329 does not change Y at BP_1 (yStart=95 in both TB_1 and TB_2). Segments 5 and 6 have the same yPos → the BP_1 transition renders as a horizontal rectangle for this entity.

Pass C — Style assignments

Entity isSystem isSystemComponent everHadFusedIntoSlots Style key
aircraft684 true false false rootSystem
port-engine-component true true false componentSlot
fuelpump-component false true false componentSlot
engine329 true false true installedSystem
engine329-fuelpump-component false true false componentSlot
fuelpump739 false false true installedArtefact

port-engine-component is both isSystem and isSystemComponent; isSystemComponent takes precedence since the componentSlot condition is checked before rootSystem in the classification table.