@@ -1390,6 +1390,23 @@ local function BuildMpCache()
13901390 end
13911391 end
13921392
1393+ -- Per-node tree-path distance from the component root. Forward pass: parent
1394+ -- precedes child in `order`, so the parent's distance is already final.
1395+ -- Topology-stable, so free to accumulate here. Drives the twig-pulse stagger
1396+ -- so thorn pulses chain across edges instead of restarting per cable.
1397+ local distFromRoot = {}
1398+ for i = 1 , # order do
1399+ local u = order [i ]
1400+ local pi = parentInTree [u ]
1401+ if not pi then
1402+ distFromRoot [u ] = 0
1403+ else
1404+ local e = edges [pi .key ]
1405+ local dx , dz = e .px - e .cx , e .pz - e .cz
1406+ distFromRoot [u ] = distFromRoot [pi .parent ] + sqrt (dx * dx + dz * dz )
1407+ end
1408+ end
1409+
13931410 -- Static per-subtree aggregates (post-order over `order`).
13941411 local subPmax = {}
13951412 local subDmax = {}
@@ -1442,6 +1459,7 @@ local function BuildMpCache()
14421459 mpCache .subPmaxNonWind = subPmaxNonWind
14431460 mpCache .subWindCount = subWindCount
14441461 mpCache .subWindBase = subWindBase
1462+ mpCache .distFromRoot = distFromRoot
14451463 mpCache .valid = true
14461464end
14471465
@@ -1679,6 +1697,7 @@ local function SendAll()
16791697 end
16801698
16811699 -- Bin edges by ally, in one pass.
1700+ local distFromRoot = mpCache .distFromRoot
16821701 local perAlly = {}
16831702 for key , edge in pairs (edges ) do
16841703 local ally = allyOfUnit [edge .parentID ] or allyOfUnit [edge .childID ]
@@ -1687,7 +1706,8 @@ local function SendAll()
16871706 if not pa then
16881707 pa = {
16891708 keys = {}, pxs = {}, pzs = {}, cxs = {}, czs = {},
1690- caps = {}, maxxed = {}, flows = {}, effs = {}, n = 0 ,
1709+ caps = {}, maxxed = {}, flows = {}, effs = {},
1710+ rootAs = {}, rootBs = {}, n = 0 ,
16911711 }
16921712 perAlly [ally ] = pa
16931713 end
@@ -1699,6 +1719,10 @@ local function SendAll()
16991719 pa .caps [i ] = capacities [key ] or 0
17001720 pa .maxxed [i ] = (spGetUnitRulesParam (edge .parentID , " OD_gridMaxxed" ) or spGetUnitRulesParam (edge .childID , " OD_gridMaxxed" )) == 1
17011721 pa .flows [i ] = flows [key ] or 0
1722+ -- Per-end distance-from-root for the twig pulse. v0 = parent end,
1723+ -- v1 = child end; keyed by node so flow reorientation just swaps them.
1724+ pa .rootAs [i ] = distFromRoot [edge .parentID ] or 0
1725+ pa .rootBs [i ] = distFromRoot [edge .childID ] or 0
17021726 -- Cached per-grid lookup; fall back to child end if parent's grid
17031727 -- is unknown. 0 → magenta in the shader (unit_mex_overdrive's
17041728 -- "no grid" sentinel).
@@ -1718,6 +1742,7 @@ local function SendAll()
17181742 keys = pa .keys , pxs = pa .pxs , pzs = pa .pzs ,
17191743 cxs = pa .cxs , czs = pa .czs ,
17201744 caps = pa .caps , maxxed = pa .maxxed , flows = pa .flows , effs = pa .effs ,
1745+ rootAs = pa .rootAs , rootBs = pa .rootBs ,
17211746 })
17221747 alliesWithEdges [ally ] = true
17231748 end
@@ -2322,7 +2347,11 @@ local function GenerateOrganicTree()
23222347 local witherTime = e .witherFrame and (e .witherFrame / GAME_SPEED ) or 0
23232348 local eff = (e .eff or 0 ) * (e .maxxed and - 1 or 1 )
23242349 local flow = e .flow or 0
2325- local phase = e .bubblePhase or 0
2350+ -- vertGrid.z (ex-bubblePhase, now dead — GS drops gridData.z) carries each
2351+ -- end's distance-from-root: v0 = rootA, v1 = rootB. GS lerps it to twig
2352+ -- roots to chain pulses across edges.
2353+ local rootA = e .rootA or 0
2354+ local rootB = e .rootB or 0
23262355 local isOwn = (fullview and 2 ) or (e .isOwnAlly and 1 ) or 0
23272356 -- Coverage SSBO slot index, or -1 to disable bit updates / lookups
23282357 -- on the GS side. Stored as float here for VBO layout simplicity;
@@ -2333,12 +2362,12 @@ local function GenerateOrganicTree()
23332362 -- Vertex 0: parent end (10 floats: pos2 + data3 + grid4 + slot)
23342363 verts [k + 1 ] = e .px ; verts [k + 2 ] = e .pz
23352364 verts [k + 3 ] = cap ; verts [k + 4 ] = appearTime ; verts [k + 5 ] = witherTime
2336- verts [k + 6 ] = eff ; verts [k + 7 ] = flow ; verts [k + 8 ] = phase ; verts [k + 9 ] = isOwn
2365+ verts [k + 6 ] = eff ; verts [k + 7 ] = flow ; verts [k + 8 ] = rootA ; verts [k + 9 ] = isOwn
23372366 verts [k + 10 ] = slot
2338- -- Vertex 1: child end (same per-edge payload)
2367+ -- Vertex 1: child end (same per-edge payload, except rootB for its end )
23392368 verts [k + 11 ] = e .cx ; verts [k + 12 ] = e .cz
23402369 verts [k + 13 ] = cap ; verts [k + 14 ] = appearTime ; verts [k + 15 ] = witherTime
2341- verts [k + 16 ] = eff ; verts [k + 17 ] = flow ; verts [k + 18 ] = phase ; verts [k + 19 ] = isOwn
2370+ verts [k + 16 ] = eff ; verts [k + 17 ] = flow ; verts [k + 18 ] = rootB ; verts [k + 19 ] = isOwn
23422371 verts [k + 20 ] = slot
23432372 k = k + 20
23442373 end
@@ -2583,6 +2612,10 @@ function OnCableTreeFull(data)
25832612 e .eff = data .effs and data .effs [i ] or 0
25842613 e .px , e .pz = data .pxs [i ], data .pzs [i ]
25852614 e .cx , e .cz = data .cxs [i ], data .czs [i ]
2615+ -- Refresh with px/cx so the per-end root distances track the
2616+ -- current parent/child orientation.
2617+ e .rootA = data .rootAs and data .rootAs [i ] or 0
2618+ e .rootB = data .rootBs and data .rootBs [i ] or 0
25862619 end
25872620 e .isOwnAlly = ownAlly
25882621 -- Late-bind a coverage slot if missing (e.g., gadget was reloaded
@@ -2616,6 +2649,8 @@ function OnCableTreeFull(data)
26162649 maxxed = data .maxxed [i ],
26172650 flow = newFlow ,
26182651 eff = data .effs and data .effs [i ] or 0 ,
2652+ rootA = data .rootAs and data .rootAs [i ] or 0 ,
2653+ rootB = data .rootBs and data .rootBs [i ] or 0 ,
26192654 isOwnAlly = ownAlly ,
26202655 appearFrame = af ,
26212656 witherFrame = nil ,
0 commit comments