灵符lingfu

LingSys

↓ 0 downloads

Your machine's memory, GPU, motherboard and network, drawn and played in the Ling design system: LingSys, a small build for USB liquid-cooler displays, LingNet for IPv4/IPv6, DNS and packets, and a LingOS edition. 64-bit and 32-bit Windows builds included.

Keywords system-monitor, visualization, network, ipv6, gpu, lcd, lingos, ling
lingfu install LingSys
LicenseMIT
AuthorsLing Lin <taellinglin@gmail.com>
READMECode / FilesVersionsDependency GraphDependentsSecurityBuildsSettings

Code / Files (48)

hatchnet.ling
# ══════════════════════════════════════════════════════════════════════════════
# HatchSYS: the machine's memory, GPU, motherboard and network, drawn and played
# in the Ling design system.
#
# GENERATED FILE: edit the parts in src/ and run `python tools/build.py`.
# (One file on purpose: the Ling JIT does not see top-level bindings of `use`d modules.)
#
# Line work only: circles, squares, Daemon tiles, Chesilin rings, crosshatch.
#   circle  = something comes in (a page read, a packet received, a frame drawn)
#   square  = something goes out (a page written, a packet sent, a copy)
# The same numbers drive an additive synth: seven channels x four partials plus four
# shimmer partials, 32 audio_tone slots in all, and a short chime for every pulse.
#
# Sections: MAIN (system overview) | MEMORY | GPU | BOARD | NETWORK
# Keys: 0 main, 1-4 section, LEFT/RIGHT section, TAB or SPACE next view, UP/DOWN pick GPU,
#       A auto-cycle, H hud, M mute, ESC back (quit on MAIN), Q quit. The bottom menu is clickable.
# Run:  ling run hatchsys.ling  (run.ps1 starts the samplers for live data first)
# Args: full (borderless fullscreen) | sim (ignore the .snap files) | shot (save a screenshot per view, quit)
# ══════════════════════════════════════════════════════════════════════════════

bind TAU = 6.283185307179586
bind HUE = [357.0, 28.0, 46.0, 140.0, 215.0, 247.0, 285.0]
bind CAT_NAME = ["KERNEL", "STANDBY", "DATA", "MODIFIED", "MAPPED", "COMPRESSED", "FREE"]
bind CAT_LET = "ALBCKFM"
bind ANG1 = [0.785, 0.52, 1.05, 0.0, 2.09, 1.31, 0.26]
bind ORDER = [0, 2, 4, 5, 3, 1, 6]
bind RATIO = [1.0, 1.5, 2.0, 2.25, 3.0, 3.75, 4.5]
bind READ_W = [0.15, 0.9, 0.5, 0.0, 0.8, 0.2, 0.0]
bind WRITE_W = [0.1, 0.0, 0.7, 1.0, 0.2, 0.5, 0.0]

# sections and their views
bind SEC_NAME = ["MAIN", "MEMORY", "GPU", "BOARD", "NETWORK"]
bind SEC_HUE = [2, 4, 3, 1, 6]
bind SEC_LET = "HMGBN"
bind SUB_NAME = [["OVERVIEW"], ["RINGS", "TILES", "ORBITS", "WEAVE"], ["ENGINES", "VRAM", "DIALS", "FLEET"], ["LAYOUT", "CORES", "SIGNALS"], ["FLOW", "LINKS", "TRACES"]]

# the seven channels of every section: ring names, lane names, pulse categories (same order as the Ling hues)
bind CH_GPU = ["3D", "COMPUTE", "COPY", "DECODE", "ENCODE", "VRAM", "POWER"]
bind CH_BRD = ["CORES", "MEMBUS", "PCIE", "CHIPSET", "STORAGE", "NETWORK", "THERMAL"]
bind CH_NET = ["RX", "TX", "PKT IN", "PKT OUT", "TCP", "UDP", "RETRANS"]
bind CH_MAIN = ["MEMORY", "GPU", "CPU", "NETWORK", "VRAM", "DISK", "HEAT"]
bind CH_ALL = [CAT_NAME, CH_MAIN, CH_GPU, CH_BRD, CH_NET]
bind IDM = [0, 1, 2, 3, 4, 5, 6]
bind CH_LET_GPU = "3CPDEVW"
bind CH_LET_BRD = "CMPSDNT"
bind CH_LET_NET = "RTIOUDX"
bind CH_LET_MAIN = "MGCNVDH"

# state list Z, built once per frame and handed to the views and the synth
bind Z_T = 0
bind Z_ROT = 1
bind Z_HS = 2
bind Z_SAT = 3
bind Z_W = 4
bind Z_H = 5
bind Z_CATS = 6
bind Z_TOTAL = 7
bind Z_ACT = 8
bind Z_RD = 9
bind Z_WR = 10
bind Z_FLT = 11
bind Z_EV = 12
bind Z_NAME = 13
bind Z_WS = 14
bind Z_PRIV = 15
bind Z_IOR = 16
bind Z_IOW = 17
bind Z_HIST = 18
bind Z_USED = 19
bind Z_ENERGY = 20
bind Z_HIL = 21
bind Z_LIVE = 22
bind Z_FH = 23
bind Z_D = 24
bind Z_STAT = 25
bind Z_MX = 26
bind Z_MY = 27
bind Z_SEC = 28
bind Z_VIEW = 29
bind Z_FRAC = 30
bind Z_LAB = 31
bind Z_ALERT = 32

# section data pack D: the smoothed readings of all four sections plus the history strips
bind D_GPU = 0
bind D_BRD = 1
bind D_NET = 2
bind D_HS = 3
bind D_MEM = 4
bind D_LIVE = 5

# GPU pack: [n, names, drivers, vecs, engs, procs, sel]; one vec per GPU
bind GV_TEMP = 0
bind GV_FAN = 1
bind GV_POWER = 2
bind GV_PLIM = 3
bind GV_CLK = 4
bind GV_CLKMAX = 5
bind GV_MCLK = 6
bind GV_MCLKMAX = 7
bind GV_UTIL = 8
bind GV_MUTIL = 9
bind GV_VUSED = 10
bind GV_VTOTAL = 11
bind GV_PGEN = 12
bind GV_PW = 13
bind GV_THP = 14
bind GV_THT = 15
bind GV_LEN = 16

# board pack: [info, bv, cutil, cfreq, dimms, disks, dio, sens]
bind BV_UTIL = 0
bind BV_MHZ = 1
bind BV_PMAX = 2
bind BV_TEMP = 3
bind BV_INTR = 4
bind BV_DPC = 5
bind BV_CTX = 6
bind BV_SYSC = 7
bind BV_PROCS = 8
bind BV_THR = 9
bind BV_UP = 10
bind BV_DRD = 11
bind BV_DWR = 12
bind BV_DBUSY = 13
bind BV_DQ = 14
bind BV_LEN = 15

# net pack: [adapters, nv, nodes, procs]
bind NV_RX = 0
bind NV_TX = 1
bind NV_PRX = 2
bind NV_PTX = 3
bind NV_BW = 4
bind NV_EST = 5
bind NV_RETX = 6
bind NV_UDP = 7
bind NV_RTT = 8
bind NV_LISTEN = 9
bind NV_TW = 10
bind NV_CW = 11
bind NV_SYN = 12
bind NV_LOOP = 13
bind NV_FAIL = 14
bind NV_LEN = 15

# history strips: 120 samples, half a second apart, each already scaled to 0..1
bind HS_N = 120
bind HS_GPU = 0
bind HS_BRD = 32
bind HS_NET = 40
bind HS_MEM = 48
bind HS_COUNT = 50

# ---- BEGIN GLYPHS ----
# generated by tools/gen_glyphs.py from the Ling design system bundle; do not edit by hand
bind DAEMON_CH = "!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~"
bind DAEMON = [
[212,699,[[0,611,6,577,26,548,54,529,88,523,122,529,150,548,170,577,176,611,170,645,150,674,122,693,88,699,54,693,26,674,6,645],[212,0,133,368,65,368,88,0]]],
[729,751,[[393,1,294,2,235,205,293,1],[172,420,206,304,173,420],[76,751,144,518,77,751],[634,0,535,1,500,120,534,0],[317,750,385,518,318,750],[487,508,387,508,410,430,510,430],[247,508,147,508,169,430,269,430],[147,508,0,508,0,430,169,430],[387,508,247,508,269,430,410,430],[672,430,672,508,487,508,510,430],[550,293,450,293,472,215,572,215],[309,293,209,293,232,215,332,215],[209,293,48,293,48,215,232,215],[450,293,309,293,332,215,472,215],[720,215,720,293,550,293,572,215],[535,1,634,1,575,205,729,205,729,303,547,303,513,420,681,420,681,518,485,518,418,750,318,750,387,508,487,508,417,750,318,750,418,750,485,518,681,518,681,420,513,420,547,303,729,303,729,205,575,205,634,1,572,215,472,215,500,120],[447,304,450,293,550,293,510,430,410,430,413,420],[294,2,393,2,332,215,232,215,234,205],[172,420,209,293,309,293,269,430,169,430],[77,751,147,508,247,508,176,751]]],
[500,749,[[400,283,394,192,370,136,351,119,292,103,210,103,151,119,131,136,116,160,101,248,102,333,116,401,151,437,177,446,292,450,351,437,384,401,398,333],[0,286,7,188,16,149,28,115,63,62,85,42,140,14,250,0,356,14,411,40,433,59,470,113,482,146,495,208,500,282,493,380,484,417,473,449,438,498,416,516,361,540,251,551,140,540,110,530,62,498,43,476,16,417,7,380,0,287],[175,279,183,340,190,351,214,363,272,365,301,358,318,340,324,305,318,217,311,206,288,193,230,191,200,198,183,217,176,253],[125,283,128,235,139,198,156,172,180,154,250,140,320,154,344,172,361,198,372,235,375,283,368,348,358,371,326,402,279,414,220,414,174,402,142,371,127,319],[175,285,183,345,190,355,214,364,301,361,318,345,324,311,318,222,311,210,288,195,230,192,200,201,183,222,176,259],[125,283,139,199,156,172,181,155,212,144,251,141,289,144,321,155,344,172,361,199,372,236,375,283,368,348,358,371,327,402,280,414,221,414,174,402,143,371,127,319],[1,30,79,30,79,749,1,749]]],
[569,751,[[50,139,57,77,74,58,88,54,125,51,176,58,193,77,199,113,193,201,186,212,162,224,104,225,74,219,57,201,51,165],[0,144,3,96,14,59,31,32,55,15,87,4,125,1,163,4,195,15,219,32,236,59,247,96,250,144,248,180,233,232,201,263,154,275,95,275,49,263,17,232,2,180],[50,146,57,205,64,215,88,224,176,221,193,205,199,172,193,82,176,61,162,55,104,52,74,61,57,82,51,120],[0,144,3,96,14,59,31,32,55,15,87,4,125,1,163,4,195,15,219,32,236,59,247,96,250,144,248,180,233,232,201,263,154,275,95,275,49,263,17,232,2,180],[369,614,376,552,406,529,465,527,494,534,512,552,518,588,516,660,512,676,494,694,444,701,394,694,376,676,370,640],[319,619,322,571,333,534,350,508,374,490,444,476,482,479,514,490,538,508,555,534,566,571,569,619,562,684,552,707,520,738,473,750,414,750,368,738,336,707,321,655],[369,621,376,680,406,700,494,696,512,680,518,647,512,557,504,544,482,530,423,527,394,536,376,557,370,595],[319,619,322,571,333,534,350,508,374,490,444,476,482,479,514,490,538,508,555,534,566,571,569,619,562,684,552,707,520,738,473,750,414,750,368,738,336,707,321,655],[343,0,443,0,226,750,126,750]]],
[428,583,[[341,582,259,583,18,0,94,0,341,583],[302,283,78,283,80,458,428,280,428,370,0,583,0,208,221,205,220,82,80,88,40,0,303,0]]],
[208,164,[[208,0,193,43,149,107,98,149,68,164,0,164,41,113,70,63,84,0]]],
[445,806,[[405,0,445,2,445,89,387,89,301,104,233,134,194,164,157,200,120,259,93,349,88,403,94,464,112,527,126,558,165,615,218,663,285,698,380,718,445,719,445,806,355,805,284,790,202,761,134,718,73,652,46,605,20,537,0,437,0,368,20,266,38,212,86,134,143,82,209,42,275,18,374,1]]],
[445,806,[[71,1,170,18,236,42,302,82,359,134,394,186,425,266,445,368,445,437,425,537,399,605,372,652,311,718,243,761,162,790,90,805,0,806,0,719,66,718,160,698,228,663,280,615,319,558,333,527,356,433,357,379,337,290,304,220,252,164,212,134,144,104,58,89,0,89,0,2,40,0]]],
[334,334,[[334,138,334,196,0,196,0,138],[138,0,196,0,196,334,138,334],[264,28,306,70,70,306,28,264],[28,70,70,28,306,264,264,306]]],
[445,445,[[445,183,445,261,0,261,0,183],[184,0,262,0,262,445,184,445]]],
[176,266,[[88,0,123,6,151,26,170,54,176,103,165,154,145,190,68,266,0,266,38,220,66,174,37,161,16,140,0,88,6,54,26,26,54,6]]],
[445,78,[[445,0,445,78,0,78,0,0]]],
[176,176,[[0,88,6,54,26,26,54,6,88,0,122,6,150,26,170,54,176,88,170,122,150,150,122,170,88,176,54,170,26,150,6,122]]],
[317,750,[[0,750,217,0,317,0,100,750]]],
[500,745,[[400,370,394,265,370,180,351,147,325,122,292,106,251,101,210,106,177,122,151,147,116,219,101,333,107,476,130,564,150,597,176,623,209,640,250,645,291,640,324,623,350,597,370,564,387,508,399,407],[0,373,4,293,16,222,28,180,63,106,85,75,140,28,173,12,250,0,289,2,353,20,405,52,446,96,475,162,484,201,492,242,500,369,496,450,484,521,457,603,438,638,416,669,360,716,290,742,250,745,209,742,139,716,84,669,62,638,43,603,16,521,4,450,0,374]]],
[456,749,[[23,381,16,585,0,749,68,749,147,381],[356,169,355,112,345,81,281,100,230,106,202,105,113,81,104,96,100,136,102,224,104,238,113,248,179,216,230,209,281,216,345,248,353,238,356,201],[0,172,1,120,12,54,29,24,52,8,83,1,229,5,344,0,390,6,410,15,445,52,454,73,456,106,451,256,435,302,406,320,230,309,72,322,35,314,12,283,1,220,0,173]]],
[456,749,[[23,381,16,585,0,749,68,749,147,381],[356,169,355,112,345,81,281,100,230,106,202,105,113,81,104,96,100,136,102,224,104,238,113,248,179,216,230,209,281,216,345,248,353,238,356,201],[0,172,1,120,12,54,29,24,52,8,83,1,229,5,344,0,390,6,410,15,445,52,454,73,456,106,451,256,435,302,406,320,230,309,72,322,35,314,12,283,1,220,0,173],[177,381,170,585,154,749,222,749,301,381]]],
[456,749,[[23,381,16,585,0,749,68,749,147,381],[331,381,324,585,308,749,376,749,455,381],[356,169,355,112,345,81,281,100,230,106,202,105,113,81,104,96,100,136,102,224,104,238,113,248,179,216,230,209,281,216,345,248,353,238,356,201],[0,172,1,120,12,54,29,24,52,8,83,1,229,5,344,0,390,6,410,15,445,52,454,73,456,106,451,256,435,302,406,320,230,309,72,322,35,314,12,283,1,220,0,173],[177,381,170,585,154,749,222,749,301,381]]],
[455,742,[[23,374,16,578,0,742,68,742,147,374],[0,192,455,192,455,292,0,292],[230,0,286,10,331,38,348,58,368,108,369,191,267,193,266,127,246,105,229,102,212,105,193,128,191,194,90,191,91,117,110,67,147,23,213,1]]],
[455,742,[[23,374,16,578,0,742,68,742,147,374],[0,192,455,192,455,292,0,292],[230,0,286,10,331,38,348,58,368,108,369,191,267,193,266,127,246,105,229,102,212,105,193,128,191,194,90,191,91,117,110,67,147,23,213,1],[177,374,170,578,154,742,222,742,301,374]]],
[455,742,[[23,374,16,578,0,742,68,742,147,374],[0,192,455,192,455,292,0,292],[230,0,286,10,331,38,348,58,368,108,369,191,267,193,266,127,246,105,229,102,212,105,193,128,191,194,90,191,91,117,110,67,147,23,213,1],[331,374,324,578,308,742,376,742,455,374],[177,374,170,578,154,742,222,742,301,374]]],
[455,750,[[23,382,16,586,0,750,68,750,147,382],[0,200,455,200,455,300,0,300],[0,0,455,0,455,100,0,100]]],
[455,750,[[23,382,16,586,0,750,68,750,147,382],[0,200,455,200,455,300,0,300],[0,0,455,0,455,100,0,100],[177,382,170,586,154,750,222,750,301,382]]],
[455,751,[[23,383,16,587,0,751,68,751,147,383],[0,201,455,201,455,301,0,301],[0,1,455,1,455,101,0,101,0,0],[331,383,324,587,308,751,376,751,455,383],[177,383,170,587,154,751,222,751,301,383]]],
[176,399,[[0,311,6,277,26,248,54,229,88,223,122,229,150,248,170,277,176,311,170,345,150,374,122,393,88,399,54,393,26,374,6,345],[0,88,6,54,26,26,54,6,88,0,122,6,150,26,170,54,176,88,170,122,150,150,122,170,88,176,54,170,26,150,6,122]]],
[176,491,[[0,88,6,54,26,26,54,6,88,0,122,6,150,26,170,54,176,88,170,122,150,150,122,170,88,176,54,170,26,150,6,122],[88,225,123,232,151,251,170,280,176,328,165,378,145,415,68,491,0,491,38,445,66,399,37,386,16,364,0,313,6,279,26,250,54,231]]],
[448,804,[[361,2,448,2,448,82,424,191,410,232,364,311,309,364,246,403,327,457,394,542,436,660,447,727,447,804,360,804,356,715,329,617,299,569,234,504,188,478,140,460,88,450,0,448,0,359,82,358,164,338,226,309,296,242,328,188,350,132,358,90,361,0]]],
[455,301,[[0,201,455,201,455,301,0,301],[0,1,455,1,455,101,0,101,0,0]]],
[448,801,[[87,49,98,130,119,186,151,239,195,286,221,306,283,335,365,355,448,356,448,445,359,447,307,458,237,486,191,521,149,567,119,615,92,713,88,801,1,801,1,725,8,672,47,553,79,501,120,455,202,401,158,376,100,328,51,256,23,189,0,80,0,0,87,0]]],
[226,701,[[0,613,6,579,26,551,54,532,88,525,122,532,150,551,170,579,176,613,170,647,150,676,122,695,88,701,54,695,26,676,6,647],[0,132,0,1,49,0,125,9,166,25,196,47,215,74,226,140,213,210,182,286,133,370,65,370,113,245,120,191,118,171,105,144,80,128,43,126]]],
[500,551,[[102,327,112,391,130,423,150,438,194,449,297,450,351,438,386,429,494,380,481,430,463,469,430,507,364,539,293,551,202,551,143,541,107,529,72,507,41,476,17,425,1,327],[178,327,192,354,222,366,297,363,310,354,323,327,466,327,432,357,378,390,351,403,295,416,221,416,184,409,156,389,137,360,127,327],[1,327,7,192,28,116,45,86,83,43,112,25,222,0,292,1,350,12,400,32,438,64,467,105,485,147,500,231,500,283,466,327,323,327,325,257,308,206,270,192,222,194,196,206,178,238,178,327,127,327,127,252,139,201,168,164,217,144,284,145,336,166,361,196,368,217,375,305,398,279,391,183,377,150,355,123,285,103,219,103,152,120,124,147,111,182,101,241,102,327]]],
[500,750,[[166,750,0,0,100,0,250,650,401,0,500,0,333,750]]],
[500,750,[[100,407,400,407,400,97,0,97,0,0,500,0,500,503,200,503,200,750,100,750]]],
[500,750,[[200,653,200,0,300,0,300,653,500,653,500,749,0,750,0,654]]],
[500,750,[[100,318,100,0,200,0,200,221,500,221,500,750,0,750,0,653,400,653,400,318]]],
[500,745,[[0,447,0,303,500,0,500,134,100,377,498,613,498,745]]],
[500,750,[[0,0,500,0,500,100,100,100,100,650,500,650,500,750,0,750,0,726]]],
[500,750,[[0,0,100,0,100,648,400,648,400,0,500,0,500,750,0,750]]],
[500,750,[[0,0,500,0,500,750,0,750,0,650,400,650,400,100,0,100]]],
[500,750,[[166,0,333,0,500,750,400,750,250,100,100,750,0,750]]],
[500,750,[[0,0,500,0,500,100,100,99,100,750,0,750]]],
[500,750,[[100,100,99,750,0,750,0,0,500,0,500,750,400,750,400,100]]],
[500,750,[[400,650,100,650,100,750,0,750,0,0,500,0,500,750,400,750],[400,550,400,100,101,100,100,550]]],
[500,751,[[368,750,0,750,0,650,365,650,400,441,400,425,0,425,0,325,400,325,400,100,0,100,0,0,500,0,500,441,450,751]]],
[500,750,[[350,750,394,425,0,425,0,325,400,325,400,100,0,100,0,0,500,0,500,441,450,750]]],
[500,750,[[500,425,454,421,416,429,385,447,340,503,324,540,306,618,300,750,200,750,189,583,168,520,152,493,107,452,42,429,0,425,0,325,77,307,133,277,161,252,197,194,212,130,210,62,200,0,300,0,314,125,303,203,276,271,230,326,200,348,125,375,166,391,205,418,235,457,250,505,284,406,310,372,340,346,374,330,412,322,500,325,500,375]]],
[500,749,[[415,350,100,350,100,650,500,650,500,749,1,749,0,250,315,250,315,0,415,0,415,340]]],
[500,750,[[100,325,500,325,500,425,100,425,100,750,0,750,0,0,100,0]]],
[500,750,[[300,650,450,650,450,750,50,750,50,650,200,650,200,100,0,100,0,0,500,0,500,100,300,100]]],
[500,750,[[0,0,500,0,500,100,100,100,100,325,500,325,500,750,0,750,0,650,400,650,400,425,0,425]]],
[500,750,[[300,100,300,750,200,750,200,100,0,100,0,0,500,0,500,100]]],
[500,750,[[500,325,500,425,0,750,0,650,400,373,0,100,0,0]]],
[500,750,[[100,100,500,100,500,200,350,200,350,400,250,400,250,200,100,200,100,750,0,750,0,0,100,0]]],
[500,750,[[100,425,100,675,0,675,0,75,100,75,100,326,400,326,400,0,500,0,500,750,400,750,400,425]]],
[500,750,[[0,0,100,0,100,650,200,650,200,0,500,0,500,750,400,750,400,100,300,100,300,750,0,750,0,18]]],
[500,750,[[100,100,100,650,500,650,500,750,0,750,0,0,500,0,500,100,350,100,350,400,250,400,250,100]]],
[500,750,[[500,0,500,750,400,750,400,100,0,100,0,0]]],
[200,783,[[0,0,200,0,200,98,98,98,98,686,200,686,200,783,0,783]]],
[200,783,[[200,0,200,783,0,783,0,686,102,686,102,98,0,98,0,0]]],
[804,448,[[802,361,802,448,722,448,613,424,572,410,493,364,440,309,401,246,347,327,262,394,144,436,77,447,0,447,0,360,89,356,187,329,235,299,300,234,326,188,344,140,354,88,356,0,445,0,446,82,466,164,495,226,562,296,616,328,672,350,714,358,804,361]]],
[672,96,[[672,22,672,96,0,95,0,0,672,0]]],
[211,164,[[124,0,126,38,150,100,187,146,211,164,143,164,105,141,50,88,0,0]]],
[503,499,[[298,499,210,499,0,0,104,0,254,375,404,0,503,0]]],
[500,500,[[194,266,400,266,400,95,0,95,0,0,500,0,500,363,200,363,200,500,100,500,100,266]]],
[500,496,[[300,399,500,399,500,496,0,496,0,399,200,398,200,0,300,0]]],
[500,500,[[208,230,111,230,111,0,211,0,211,130,500,130,500,500,0,500,0,400,400,400,400,230]]],
[499,503,[[0,298,0,210,499,0,499,104,124,254,499,404,499,503]]],
[500,500,[[0,0,500,0,500,100,97,100,97,400,495,400,495,500,0,500]]],
[500,500,[[0,500,0,0,99,0,99,402,403,402,403,0,500,0,499,500]]],
[500,500,[[500,500,0,500,0,400,400,400,400,100,0,100,0,0,500,0]]],
[503,499,[[205,0,293,0,503,499,399,499,249,124,99,499,0,499]]],
[500,500,[[0,0,500,0,500,100,99,101,100,500,0,500]]],
[500,500,[[500,0,500,500,400,500,402,96,100,100,100,500,0,500,0,0]]],
[500,499,[[400,399,100,400,100,499,0,499,0,0,500,0,500,499,400,499],[400,299,400,99,100,99,100,299]]],
[503,500,[[400,200,400,100,0,100,0,0,503,0,500,500,0,500,0,400,400,400,400,300,0,300,0,200]]],
[500,500,[[400,500,400,300,0,300,0,200,400,200,400,100,0,100,0,0,500,0,500,500]]],
[500,500,[[0,291,0,191,84,184,150,151,190,100,203,43,205,0,305,0,300,96,279,158,240,210,213,228,174,241,208,255,231,274,255,320,276,274,294,252,348,212,409,194,500,191,500,291,402,301,370,315,346,334,316,384,308,416,305,500,205,500,201,418,182,366,132,319,102,304,40,292]]],
[500,500,[[317,230,100,230,100,399,500,400,500,500,0,500,0,129,315,130,315,0,415,0,415,230]]],
[500,500,[[100,300,100,500,0,500,0,0,100,0,100,200,500,200,500,300]]],
[503,500,[[301,100,301,400,503,400,503,500,1,500,1,400,201,400,201,100,0,100,0,0,501,0,501,100]]],
[500,500,[[0,1,500,0,500,100,105,100,107,200,500,200,500,500,0,500,0,405,400,405,400,300,0,300]]],
[500,500,[[200,100,0,100,0,0,500,0,500,100,300,100,300,500,200,500]]],
[499,503,[[499,205,499,293,0,503,0,399,375,249,0,99,0,0]]],
[499,500,[[99,100,499,100,499,200,349,200,349,350,249,350,249,200,99,200,99,500,0,500,0,0,99,0]]],
[500,500,[[400,300,100,300,100,500,0,500,0,0,100,0,100,200,400,200,400,0,500,0,500,500,400,500]]],
[500,500,[[0,500,0,0,100,0,100,400,200,400,200,0,500,0,500,500,400,500,400,100,300,100,300,500]]],
[500,500,[[250,100,100,100,100,400,500,400,500,500,0,500,0,0,500,0,500,100,350,100,350,300,250,300]]],
[500,500,[[500,0,500,500,400,500,400,100,0,100,0,0]]],
[340,806,[[158,0,245,0,245,74,236,143,212,218,180,284,148,326,238,356,340,359,340,446,244,450,148,480,186,534,212,586,240,688,245,731,245,805,157,806,150,690,127,620,95,566,1,469,48,430,95,403,48,376,0,337,89,249,117,206,148,122,158,51]]],
[100,750,[[100,750,0,750,0,0,100,0]]],
[340,808,[[182,53,192,124,223,207,251,251,340,339,292,378,245,405,292,432,339,471,245,568,213,622,189,698,183,808,95,808,96,714,112,638,144,552,192,482,96,452,0,448,0,360,86,359,192,328,160,286,128,220,104,145,95,76,95,0,182,1]]],
[409,161,[[126,0,160,6,161,0,230,0,234,59,246,76,283,89,309,83,332,65,337,0,409,0,409,48,392,104,355,141,326,156,299,161,179,161,175,102,162,86,124,72,90,86,76,102,72,161,0,161,1,103,20,54,59,15,113,0]]]
]
bind CHESILIN_CH = "!,.0123456789?ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
bind CHESILIN = [
[97,844,[[47,0,97,0,62,218,46,250,0,250,34,42],[47,595,97,595,64,806,46,842,0,844,34,637]]],
[432,844,[[424,0,432,12,432,92,422,195,399,306,376,370,345,428,304,476,279,495,220,518,96,514,50,494,16,455,0,393,3,87,16,27,28,4,39,2,44,14,40,306,47,354,70,385,97,403,202,420,276,408,302,394,356,328,382,239,395,58,417,2],[54,590,336,590,321,751,310,798,299,830,286,844,24,844,30,714,42,632]]],
[317,844,[[30,0,317,0,294,169,276,239,262,254,0,254,2,168,26,10],[30,591,317,591,294,759,276,829,262,844,0,844,2,758,26,601]]],
[362,664,[[362,0,361,68,341,93,225,93,225,567,362,567,361,640,341,664,0,664,2,609,22,571,133,571,133,93,0,93,2,38,22,0]]],
[264,659,[[264,0,264,638,214,659,174,659,174,94,0,94,2,39,22,0]]],
[263,662,[[263,0,263,623,242,662,0,662,2,602,27,566,173,566,173,21,225,5]]],
[262,658,[[262,0,262,636,220,658,177,658,177,372,0,372,0,323,27,274,170,275,170,21,233,4]]],
[362,663,[[195,0,235,0,235,565,362,565,361,638,340,663,0,663,1,607,22,570,144,570,144,21,184,1]]],
[362,659,[[362,0,361,70,340,94,223,94,223,637,173,659,132,659,132,93,0,93,1,38,22,0]]],
[368,664,[[27,0,368,0,368,642,314,662,277,658,277,96,6,96,0,70,16,18]]],
[366,663,[[116,0,366,0,366,69,350,98,95,98,95,642,47,663,0,663,0,27,26,14,108,0]]],
[364,663,[[51,0,91,0,91,568,364,568,362,638,342,663,0,663,0,22,40,2]]],
[367,663,[[367,0,367,624,346,663,0,663,2,603,27,567,277,567,277,22,338,4]]],
[432,844,[[425,0,432,6,430,120,399,308,345,430,304,478,251,511,184,522,92,512,49,494,20,461,0,383,2,104,9,46,30,4,40,3,45,15,41,315,52,361,72,388,119,409,182,420,244,418,285,403,323,376,363,315,382,238,393,65,418,2],[166,595,216,595,186,795,168,840,120,844,153,637]]],
[78,321,[[45,0,78,0,78,276,61,321,42,321,42,102,7,103,0,91,8,38,32,3]]],
[290,1006,[[189,531,142,542,110,590,93,650,91,743,106,806,132,864,180,906,216,919,214,531],[271,0,287,7,290,985,281,994,234,1006,218,1004,215,989,187,997,121,986,63,944,38,910,8,830,0,725,14,644,34,588,53,560,131,490,188,462,214,458,212,24,228,10,266,0],[189,531,142,542,110,590,93,650,91,743,106,806,132,864,180,906,216,919,214,531],[271,0,287,7,290,985,281,994,234,1006,218,1004,215,989,187,997,121,986,63,944,38,910,8,830,0,725,14,644,34,588,53,560,131,490,188,462,214,458,212,24,228,10,266,0]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[289,1006,[[215,75,154,89,125,112,103,160,89,233,94,322,120,397,151,440,213,472],[274,0,289,5,286,982,268,996,231,1006,211,999,213,548,168,549,127,540,87,518,51,484,14,409,0,333,0,278,18,178,69,94,144,34,270,0],[215,75,154,89,125,112,103,160,89,233,94,322,120,397,151,440,213,472],[274,0,289,5,286,982,268,996,231,1006,211,999,213,548,168,549,127,540,87,518,51,484,14,409,0,333,0,278,18,178,69,94,144,34,270,0]]],
[109,270,[[54,0,95,30,108,60,108,102,79,207,55,270,18,235,1,206,45,26]]],
[393,1006,[[78,534,76,931,169,909,235,877,284,824,298,790,303,750,284,670,238,611,176,569,81,534],[64,0,80,4,77,457,138,459,211,479,317,544,319,24,326,14,354,4,393,5,393,706,389,748,356,824,332,857,268,914,158,974,14,1006,0,999,5,21,59,0],[78,534,76,931,169,909,235,877,284,824,298,790,303,750,284,670,238,611,176,569,81,534],[64,0,80,4,77,457,138,459,211,479,317,544,319,24,326,14,354,4,393,5,393,706,389,748,356,824,332,857,268,914,158,974,14,1006,0,999,5,21,59,0]]],
[392,1005,[[75,84,77,474,152,468,221,440,285,376,303,308,292,242,257,182,197,132,105,91],[59,0,72,2,75,17,99,8,157,14,264,58,333,112,368,160,384,198,392,229,392,984,375,996,335,1005,318,1001,315,427,226,495,157,526,77,548,79,980,44,1001,5,1000,0,22,53,0],[75,84,77,474,152,468,221,440,285,376,303,308,292,242,257,182,197,132,105,91],[59,0,72,2,75,17,99,8,157,14,264,58,333,112,368,160,384,198,392,229,392,984,375,996,335,1005,318,1001,315,427,226,495,157,526,77,548,79,980,44,1001,5,1000,0,22,53,0]]],
[288,997,[[272,0,286,4,288,505,258,522,145,541,112,586,91,666,93,750,109,820,125,853,174,904,209,919,250,924,259,933,251,962,194,996,151,994,95,972,38,910,8,830,0,719,22,617,52,561,130,490,188,462,214,458,211,24,266,1]]],
[180,82,[[0,0,143,0,180,55,180,82,54,82,22,58,0,23]]],
[291,1006,[[78,535,75,931,134,918,174,882,201,773,196,687,171,612,141,570,80,535],[64,0,80,4,77,457,163,467,221,504,266,568,280,606,291,678,282,800,240,889,172,956,87,993,14,1006,0,1000,5,21,59,1],[78,535,75,931,134,918,174,882,201,773,196,687,171,612,141,570,80,535],[64,0,80,4,77,457,163,467,221,504,266,568,280,606,291,678,282,800,240,889,172,956,87,993,14,1006,0,1000,5,21,59,1]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[487,1009,[[187,77,160,81,139,94,110,133,92,207,90,266,100,334,129,402,162,441,207,463,209,77,192,77],[282,548,280,934,333,929,352,916,388,848,397,784,396,723,370,629,355,601,300,555],[268,0,284,5,282,472,345,472,383,488,442,543,476,615,487,748,468,842,421,923,347,982,294,1002,242,1009,205,999,207,540,155,541,127,533,70,496,44,466,10,393,0,265,28,151,43,121,90,65,178,12,266,0],[187,77,160,81,139,94,110,133,92,207,90,266,100,334,129,402,162,441,207,463,209,77,192,77],[282,548,280,934,333,929,352,916,388,848,397,784,396,723,370,629,355,601,300,555],[268,0,284,5,282,472,345,472,383,488,442,543,476,615,487,748,468,842,421,923,347,982,294,1002,242,1009,205,999,207,540,155,541,127,533,70,496,44,466,10,393,0,265,28,151,43,121,90,65,178,12,266,0]]],
[333,1005,[[238,530,202,534,146,564,110,614,91,702,100,768,128,828,180,880,260,918,258,530,241,530],[60,0,75,3,77,559,148,501,222,467,295,455,332,466,333,982,316,996,280,1005,262,1002,259,992,217,996,174,986,116,957,57,904,8,812,0,748,1,23,19,9,54,0],[238,530,202,534,146,564,110,614,91,702,100,768,128,828,180,880,260,918,258,530,241,530],[60,0,75,3,77,559,148,501,222,467,295,455,332,466,333,982,316,996,280,1005,262,1002,259,992,217,996,174,986,116,957,57,904,8,812,0,748,1,23,19,9,54,0]]],
[288,997,[[103,0,148,6,186,22,243,77,277,149,288,272,285,312,264,383,215,460,140,518,74,538,76,974,30,996,2,994,0,495,27,477,143,456,180,406,198,315,190,218,164,150,116,96,81,79,31,68,38,34,95,1]]],
[181,228,[[29,0,142,97,176,143,180,177,164,228,52,138,7,88,2,45,20,6]]],
[392,1006,[[318,74,226,96,159,128,110,180,95,215,90,257,104,322,144,386,214,436,316,472],[377,0,392,5,388,981,372,995,329,1005,314,1000,316,547,240,544,183,527,126,500,76,461,75,983,38,1002,1,1002,0,299,4,257,37,182,62,148,126,90,236,31,366,1],[318,74,226,96,159,128,110,180,95,215,90,257,104,322,144,386,214,436,316,472],[377,0,392,5,388,981,372,995,329,1005,314,1000,316,547,240,544,183,527,126,500,76,461,75,983,38,1002,1,1002,0,299,4,257,37,182,62,148,126,90,236,31,366,1]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[289,997,[[63,0,77,4,75,458,137,460,173,472,229,514,265,574,278,613,289,673,289,728,269,830,216,916,140,975,81,995,51,997,31,990,40,956,82,928,146,910,175,876,190,835,199,771,192,678,168,608,147,578,120,552,87,536,28,532,0,520,3,24,57,0]]],
[332,1006,[[74,88,76,474,147,464,194,433,226,384,243,310,228,221,200,170,155,124,76,88],[59,0,70,2,74,13,98,8,160,17,216,44,263,82,292,117,327,194,332,984,318,995,275,1006,258,1002,255,446,187,503,149,523,74,546,29,549,2,542,0,22,52,0],[74,88,76,474,147,464,194,433,226,384,243,310,228,221,200,170,155,124,76,88],[59,0,70,2,74,13,98,8,160,17,216,44,263,82,292,117,327,194,332,984,318,995,275,1006,258,1002,255,446,187,503,149,523,74,546,29,549,2,542,0,22,52,0]]],
[291,1005,[[76,86,76,475,136,470,155,456,182,413,200,341,200,261,184,195,153,134,108,98],[59,0,72,4,76,17,106,8,151,14,188,30,246,85,280,157,291,288,287,326,265,395,216,472,168,514,110,541,77,547,80,981,62,996,25,1005,5,999,0,24,11,12,55,0],[76,86,76,475,136,470,155,456,182,413,200,341,200,261,184,195,153,134,108,98],[59,0,72,4,76,17,106,8,151,14,188,30,246,85,280,157,291,288,287,326,265,395,216,472,168,514,110,541,77,547,80,981,62,996,25,1005,5,999,0,24,11,12,55,0]]],
[202,364,[[143,0,202,12,202,30,145,56,145,336,92,363,75,364,47,347,10,312,0,288,19,258,38,256,50,270,50,28,120,2]]],
[392,1006,[[77,510,77,579,166,510,392,510,392,985,374,998,335,1006,321,1002,318,989,288,998,258,996,161,966,73,906,28,850,3,784,0,748,2,510],[317,531,242,538,152,581,105,637,90,707,95,748,110,786,159,846,220,887,319,921],[2,510,1,24,18,10,54,0,75,4,77,510],[392,510,166,510,242,476,317,458,315,25,342,6,379,0,389,8],[317,531,242,538,152,581,105,637,90,707,95,748,110,786,159,846,220,887,319,921],[60,0,75,4,77,579,136,529,196,494,317,458,315,25,342,6,388,4,392,985,374,998,335,1006,321,1002,318,989,288,998,258,996,194,980,100,929,28,850,3,784,1,24,18,10,54,0]]],
[333,1006,[[259,78,186,93,148,118,106,174,91,233,96,306,128,382,171,428,257,470],[320,0,333,4,332,522,321,533,277,546,200,546,127,520,77,482,75,984,58,997,22,1006,1,999,0,301,11,225,42,158,114,77,209,26,315,0],[259,78,186,93,148,118,106,174,91,233,96,306,128,382,171,428,257,470],[320,0,333,4,332,522,321,533,277,546,200,546,127,520,77,482,75,984,58,997,22,1006,1,999,0,301,11,225,42,158,114,77,209,26,315,0]]],
[487,1009,[[277,81,280,473,335,462,360,441,387,381,396,317,389,217,371,166,341,120,280,81],[205,539,149,549,114,588,96,640,90,722,96,789,122,860,166,910,207,929],[262,0,334,6,371,18,427,60,463,120,476,159,487,219,487,274,474,354,436,435,371,502,313,531,279,539,282,983,255,1000,195,1009,156,1007,102,987,44,932,11,860,0,736,12,656,50,576,114,510,205,471,203,24,219,10,256,0],[277,81,280,473,335,462,360,441,387,381,396,317,389,217,371,166,341,120,280,81],[205,539,149,549,114,588,96,640,90,722,96,789,122,860,166,910,207,929],[262,0,334,6,371,18,427,60,463,120,476,159,487,219,487,274,474,354,436,435,371,502,313,531,279,539,282,983,255,1000,195,1009,156,1007,102,987,44,932,11,860,0,736,12,656,50,576,114,510,205,471,203,24,219,10,256,0]]],
[487,1008,[[278,81,280,464,339,452,360,435,385,385,397,320,394,238,375,175,344,123,295,87],[263,0,340,7,380,22,440,75,476,146,487,278,484,317,456,397,390,478,307,524,233,544,153,554,126,574,106,607,90,695,91,743,98,795,117,848,151,895,178,916,225,932,270,935,277,948,255,985,192,1008,109,990,58,950,19,881,0,791,0,736,12,658,28,617,69,556,142,500,205,480,203,23,220,10,257,0],[278,81,280,464,339,452,360,435,385,385,397,320,394,238,375,175,344,123,295,87],[263,0,340,7,380,22,440,75,476,146,487,278,484,317,456,397,390,478,307,524,233,544,153,554,126,574,106,607,90,695,91,743,98,795,117,848,151,895,178,916,225,932,270,935,277,948,255,985,192,1008,109,990,58,950,19,881,0,791,0,736,12,658,28,617,69,556,142,500,205,480,203,23,220,10,257,0]]],
[480,1009,[[199,544,135,560,106,592,91,632,83,748,110,850,150,901,202,927],[287,0,333,5,372,20,433,73,454,107,477,183,480,280,466,356,449,396,407,456,334,510,274,529,276,986,230,1008,161,1006,112,993,47,944,0,842,0,678,33,592,100,522,182,480,328,453,355,432,374,400,390,293,384,228,361,156,314,98,280,80,208,72,203,60,225,23,278,1],[199,544,135,560,106,592,91,632,83,748,110,850,150,901,202,927],[287,0,333,5,372,20,433,73,454,107,477,183,480,280,466,356,449,396,407,456,334,510,274,529,276,986,230,1008,161,1006,112,993,47,944,0,842,0,678,33,592,100,522,182,480,328,453,355,432,374,400,390,293,384,228,361,156,314,98,280,80,208,72,203,60,225,23,278,1]]],
[78,321,[[45,0,78,0,78,276,61,321,42,321,42,102,7,103,0,91,8,38,32,3]]],
[290,1006,[[189,531,142,542,110,590,93,650,91,743,106,806,132,864,180,906,216,919,214,531],[271,0,287,7,290,985,281,994,234,1006,218,1004,215,989,187,997,121,986,63,944,38,910,8,830,0,725,14,644,34,588,53,560,131,490,188,462,214,458,212,24,228,10,266,0],[189,531,142,542,110,590,93,650,91,743,106,806,132,864,180,906,216,919,214,531],[271,0,287,7,290,985,281,994,234,1006,218,1004,215,989,187,997,121,986,63,944,38,910,8,830,0,725,14,644,34,588,53,560,131,490,188,462,214,458,212,24,228,10,266,0]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[289,1006,[[215,75,154,89,125,112,103,160,89,233,94,322,120,397,151,440,213,472],[274,0,289,5,286,982,268,996,231,1006,211,999,213,548,168,549,127,540,87,518,51,484,14,409,0,333,0,278,18,178,69,94,144,34,270,0],[215,75,154,89,125,112,103,160,89,233,94,322,120,397,151,440,213,472],[274,0,289,5,286,982,268,996,231,1006,211,999,213,548,168,549,127,540,87,518,51,484,14,409,0,333,0,278,18,178,69,94,144,34,270,0]]],
[109,270,[[54,0,95,30,108,60,108,102,79,207,55,270,18,235,1,206,45,26]]],
[395,1006,[[78,534,76,931,169,909,235,877,284,824,298,790,303,750,284,670,238,611,176,569,81,534],[64,0,80,4,77,457,138,459,211,479,317,544,319,24,326,14,355,4,395,5,394,690,389,748,376,787,332,857,302,887,232,938,158,974,14,1006,0,999,5,21,59,0],[78,534,76,931,169,909,235,877,284,824,298,790,303,750,284,670,238,611,176,569,81,534],[64,0,80,4,77,457,138,459,211,479,317,544,319,24,326,14,355,4,395,5,394,690,389,748,376,787,332,857,302,887,232,938,158,974,14,1006,0,999,5,21,59,0]]],
[392,1005,[[75,84,77,474,152,468,221,440,285,376,303,308,292,242,257,182,197,132,105,91],[59,0,72,2,75,17,99,8,157,14,264,58,333,112,368,160,384,198,392,229,392,984,375,996,335,1005,318,1001,315,427,226,495,157,526,77,548,79,980,44,1001,5,1000,0,22,53,0],[75,84,77,474,152,468,221,440,285,376,303,308,292,242,257,182,197,132,105,91],[59,0,72,2,75,17,99,8,157,14,264,58,333,112,368,160,384,198,392,229,392,984,375,996,335,1005,318,1001,315,427,226,495,157,526,77,548,79,980,44,1001,5,1000,0,22,53,0]]],
[288,997,[[272,0,286,4,288,505,258,522,145,541,112,586,91,666,93,750,109,820,125,853,174,904,209,919,250,924,259,933,251,962,194,996,151,994,95,972,38,910,8,830,0,719,22,617,52,561,130,490,188,462,214,458,211,24,266,1]]],
[180,82,[[0,0,143,0,180,55,180,82,54,82,22,58,0,23]]],
[291,1006,[[78,535,75,931,134,918,174,882,201,773,196,687,171,612,141,570,80,535],[64,0,80,4,77,457,163,467,221,504,266,568,280,606,291,678,282,800,240,889,172,956,87,993,14,1006,0,1000,5,21,59,1],[78,535,75,931,134,918,174,882,201,773,196,687,171,612,141,570,80,535],[64,0,80,4,77,457,163,467,221,504,266,568,280,606,291,678,282,800,240,889,172,956,87,993,14,1006,0,1000,5,21,59,1]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[487,1009,[[187,77,160,81,139,94,110,133,92,207,90,266,100,334,129,402,162,441,207,463,209,77,192,77],[282,548,280,934,333,929,352,916,388,848,397,784,396,723,370,629,355,601,300,555],[268,0,284,5,282,472,345,472,383,488,442,543,476,615,487,748,468,842,421,923,347,982,294,1002,242,1009,205,999,207,540,155,541,127,533,70,496,44,466,10,393,0,265,28,151,43,121,90,65,178,12,266,0],[187,77,160,81,139,94,110,133,92,207,90,266,100,334,129,402,162,441,207,463,209,77,192,77],[282,548,280,934,333,929,352,916,388,848,397,784,396,723,370,629,355,601,300,555],[268,0,284,5,282,472,345,472,383,488,442,543,476,615,487,748,468,842,421,923,347,982,294,1002,242,1009,205,999,207,540,155,541,127,533,70,496,44,466,10,393,0,265,28,151,43,121,90,65,178,12,266,0]]],
[333,1005,[[238,530,202,534,146,564,110,614,91,702,100,768,128,828,180,880,260,918,258,530,241,530],[60,0,75,3,77,559,148,501,222,467,295,455,332,466,333,982,316,996,280,1005,262,1002,259,992,217,996,174,986,116,957,57,904,8,812,0,748,1,23,19,9,54,0],[238,530,202,534,146,564,110,614,91,702,100,768,128,828,180,880,260,918,258,530,241,530],[60,0,75,3,77,559,148,501,222,467,295,455,332,466,333,982,316,996,280,1005,262,1002,259,992,217,996,174,986,116,957,57,904,8,812,0,748,1,23,19,9,54,0]]],
[288,997,[[103,0,148,6,186,22,243,77,277,149,288,272,285,312,264,383,215,460,140,518,74,538,76,974,30,996,2,994,0,495,27,477,143,456,180,406,198,315,190,218,164,150,116,96,81,79,31,68,38,34,95,1]]],
[181,228,[[29,0,142,97,176,143,180,177,164,228,52,138,7,88,2,45,20,6]]],
[392,1006,[[318,74,226,96,159,128,110,180,95,215,90,257,104,322,144,386,214,436,316,472],[377,0,392,5,388,981,372,995,329,1005,314,1000,316,547,240,544,183,527,126,500,76,461,75,983,38,1002,1,1002,0,299,4,257,37,182,62,148,126,90,236,31,366,1],[318,74,226,96,159,128,110,180,95,215,90,257,104,322,144,386,214,436,316,472],[377,0,392,5,388,981,372,995,329,1005,314,1000,316,547,240,544,183,527,126,500,76,461,75,983,38,1002,1,1002,0,299,4,257,37,182,62,148,126,90,236,31,366,1]]],
[290,1005,[[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0],[77,314,76,709,140,696,164,674,190,621,200,551,197,479,179,406,154,363,106,324],[63,0,78,4,78,237,138,239,175,252,231,295,251,322,280,393,290,445,290,517,286,556,264,625,212,701,136,757,74,775,74,981,59,995,15,1005,0,998,4,23,19,10,58,0]]],
[289,997,[[63,0,77,4,75,458,137,460,173,472,229,514,265,574,278,613,289,673,289,728,269,830,216,916,140,975,81,995,51,997,31,990,40,956,82,928,146,910,175,876,190,835,199,771,192,678,168,608,147,578,120,552,87,536,28,532,0,520,3,24,57,0]]],
[332,1006,[[74,88,76,474,147,464,194,433,226,384,243,310,228,221,200,170,155,124,76,88],[59,0,70,2,74,13,98,8,160,17,216,44,263,82,292,117,327,194,332,984,318,995,275,1006,258,1002,255,446,187,503,149,523,74,546,29,549,2,542,0,22,52,0],[74,88,76,474,147,464,194,433,226,384,243,310,228,221,200,170,155,124,76,88],[59,0,70,2,74,13,98,8,160,17,216,44,263,82,292,117,327,194,332,984,318,995,275,1006,258,1002,255,446,187,503,149,523,74,546,29,549,2,542,0,22,52,0]]],
[291,1005,[[76,86,76,475,136,470,155,456,182,413,200,341,200,261,184,195,153,134,108,98],[59,0,72,4,76,17,106,8,151,14,188,30,246,85,280,157,291,288,287,326,265,395,216,472,168,514,110,541,77,547,80,981,62,996,25,1005,5,999,0,24,11,12,55,0],[76,86,76,475,136,470,155,456,182,413,200,341,200,261,184,195,153,134,108,98],[59,0,72,4,76,17,106,8,151,14,188,30,246,85,280,157,291,288,287,326,265,395,216,472,168,514,110,541,77,547,80,981,62,996,25,1005,5,999,0,24,11,12,55,0]]],
[202,364,[[143,0,202,12,202,30,145,56,145,336,92,363,75,364,47,347,10,312,0,288,19,258,38,256,50,270,50,28,120,2]]],
[392,1006,[[77,510,77,579,166,510,392,510,392,985,374,998,335,1006,321,1002,318,989,288,998,258,996,161,966,73,906,28,850,3,784,0,748,2,510],[317,531,242,538,152,581,105,637,90,707,95,748,110,786,159,846,220,887,319,921],[2,510,1,24,18,10,54,0,75,4,77,510],[166,510,242,476,317,458,315,25,332,10,369,0,389,8,392,510],[317,531,242,538,152,581,105,637,90,707,95,748,110,786,159,846,220,887,319,921],[60,0,75,4,77,579,136,529,196,494,317,458,315,25,342,6,388,4,392,985,374,998,335,1006,321,1002,318,989,288,998,258,996,194,980,100,929,28,850,3,784,1,24,18,10,54,0]]],
[333,1006,[[259,78,186,93,148,118,106,174,91,233,96,306,128,382,171,428,257,470],[320,0,333,4,332,522,321,533,277,546,200,546,127,520,77,482,75,984,58,997,22,1006,1,999,0,301,11,225,42,158,114,77,209,26,315,0],[259,78,186,93,148,118,106,174,91,233,96,306,128,382,171,428,257,470],[320,0,333,4,332,522,321,533,277,546,200,546,127,520,77,482,75,984,58,997,22,1006,1,999,0,301,11,225,42,158,114,77,209,26,315,0]]],
[487,1009,[[277,81,280,473,335,462,360,441,387,381,396,317,389,217,371,166,341,120,280,81],[205,539,149,549,114,588,96,640,90,722,96,789,122,860,166,910,207,929],[262,0,334,6,371,18,427,60,463,120,476,159,487,219,487,274,474,354,436,435,371,502,313,531,279,539,282,983,255,1000,195,1009,156,1007,102,987,44,932,11,860,0,736,12,656,50,576,114,510,205,471,203,24,219,10,256,0],[277,81,280,473,335,462,360,441,387,381,396,317,389,217,371,166,341,120,280,81],[205,539,149,549,114,588,96,640,90,722,96,789,122,860,166,910,207,929],[262,0,334,6,371,18,427,60,463,120,476,159,487,219,487,274,474,354,436,435,371,502,313,531,279,539,282,983,255,1000,195,1009,156,1007,102,987,44,932,11,860,0,736,12,656,50,576,114,510,205,471,203,24,219,10,256,0]]],
[487,1008,[[278,81,280,464,339,452,360,435,385,385,397,320,394,238,375,175,344,123,295,87],[263,0,340,7,380,22,440,75,476,146,487,278,484,317,456,397,390,478,307,524,233,544,153,554,126,574,106,607,90,695,91,743,98,795,117,848,151,895,178,916,225,932,270,935,277,948,255,985,192,1008,109,990,58,950,19,881,0,791,0,736,12,658,28,617,69,556,142,500,205,480,203,23,220,10,257,0],[278,81,280,464,339,452,360,435,385,385,397,320,394,238,375,175,344,123,295,87],[263,0,340,7,380,22,440,75,476,146,487,278,484,317,456,397,390,478,307,524,233,544,153,554,126,574,106,607,90,695,91,743,98,795,117,848,151,895,178,916,225,932,270,935,277,948,255,985,192,1008,109,990,58,950,19,881,0,791,0,736,12,658,28,617,69,556,142,500,205,480,203,23,220,10,257,0]]],
[480,1009,[[199,544,135,560,106,592,91,632,83,748,110,850,150,901,202,927],[287,0,333,5,372,20,433,73,454,107,477,183,480,280,466,356,449,396,407,456,334,510,274,529,276,986,230,1008,161,1006,112,993,47,944,0,842,0,678,33,592,100,522,182,480,328,453,355,432,374,400,390,293,384,228,361,156,314,98,280,80,208,72,203,60,225,23,278,1],[199,544,135,560,106,592,91,632,83,748,110,850,150,901,202,927],[287,0,333,5,372,20,433,73,454,107,477,183,480,280,466,356,449,396,407,456,334,510,274,529,276,986,230,1008,161,1006,112,993,47,944,0,842,0,678,33,592,100,522,182,480,328,453,355,432,374,400,390,293,384,228,361,156,314,98,280,80,208,72,203,60,225,23,278,1]]]
]
# ---- END GLYPHS ----

# ───────────────────────────── small helpers ─────────────────────────────────
fn mix(a, b, k) { return a + (b - a) * k }

fn pos_mod(a, m) {
    bind r = a % m
    if r < 0.0 { bind r = r + m }
    return r
}

# set the pen to Ling hue k; shift is the global hue drift in degrees
fn col(k, sat, lit, shift) {
    set_color_hsl(pos_mod(HUE[k] + shift, 360.0), clamp(sat, 0.0, 100.0), clamp(lit, 0.0, 100.0))
}

fn ink(lit) { set_color_hsl(214.0, 14.0, clamp(lit, 0.0, 100.0)) }

fn px(cx, r, a) { return cx + r * sin(a) }
fn py(cy, r, a) { return cy - r * cos(a) }

fn pline(cx, cy, r0, a0, r1, a1) {
    draw_line(px(cx, r0, a0), py(cy, r0, a0), px(cx, r1, a1), py(cy, r1, a1))
}

# square of half-size h centred on (x, y), turned by a
fn square(x, y, h, a) {
    bind c = cos(a)
    bind s = sin(a)
    bind x0 = x - h * c + h * s
    bind y0 = y - h * s - h * c
    bind x1 = x + h * c + h * s
    bind y1 = y + h * s - h * c
    bind x2 = x + h * c - h * s
    bind y2 = y + h * s + h * c
    bind x3 = x - h * c - h * s
    bind y3 = y - h * s + h * c
    draw_line(x0, y0, x1, y1)
    draw_line(x1, y1, x2, y2)
    draw_line(x2, y2, x3, y3)
    draw_line(x3, y3, x0, y0)
}

# hatch inside a circle: parallel chords at angle a, spacing sp
fn hatch_disc(cx, cy, r, a, sp) {
    bind nx = cos(a + 1.5708)
    bind ny = sin(a + 1.5708)
    bind tx = cos(a)
    bind ty = sin(a)
    bind d = 0.0 - r + sp * 0.5
    while d < r {
        bind h = sqrt(max(r * r - d * d, 0.0))
        draw_line(cx + nx * d - tx * h, cy + ny * d - ty * h, cx + nx * d + tx * h, cy + ny * d + ty * h)
        bind d = d + sp
    }
}

# hatch inside an axis-aligned square [x, x+s] x [y, y+s], lines at 45 degrees
fn hatch_box(x, y, s, sp) {
    bind d = sp
    while d < 2.0 * s {
        bind ax = x
        bind ay = y + d
        bind bx = x + d
        bind by = y
        if d > s {
            bind ax = x + d - s
            bind ay = y + s
            bind bx = x + s
            bind by = y + d - s
        }
        draw_line(ax, ay, bx, by)
        bind d = d + sp
    }
}

fn fmt_mb(v) {
    if v >= 1024.0 { return num_str(round(v / 102.4) / 10.0) + "G" }
    return num_str(round(v)) + "M"
}

fn fmt_rate(v) {
    if v >= 10000.0 { return num_str(round(v / 1000.0)) + "K" }
    if v >= 1000.0 { return num_str(round(v / 100.0) / 10.0) + "K" }
    return num_str(round(v))
}

# ───────────────────────────── glyph drawing ─────────────────────────────────
# Outlines come from the Ling bundle. Contours are closed, y runs down.

# Daemon glyph fitted into a bw x bh box centred on (x, y), turned by a
fn daemon_glyph(ch, x, y, bw, bh, a) {
    bind i = str_find(DAEMON_CH, ch)
    if i < 0 { return 0 }
    bind g = DAEMON[i]
    bind sc = min(bw / g[0], bh / g[1])
    bind gw = g[0] * sc
    bind gh = g[1] * sc
    bind c = cos(a)
    bind s = sin(a)
    bind polys = g[2]
    bind p = 0
    while p < len(polys) {
        bind pts = polys[p]
        bind n = len(pts) / 2
        bind j = 0
        while j < n {
            bind k = (j + 1) % n
            bind ux = pts[j * 2] * sc - gw / 2.0
            bind uy = pts[j * 2 + 1] * sc - gh / 2.0
            bind vx = pts[k * 2] * sc - gw / 2.0
            bind vy = pts[k * 2 + 1] * sc - gh / 2.0
            draw_line(x + ux * c - uy * s, y + ux * s + uy * c, x + vx * c - vy * s, y + vx * s + vy * c)
            bind j = j + 1
        }
        bind p = p + 1
    }
    return 1
}

# a run of Daemon letters, h tall, left-aligned at (x, y) where y is the vertical centre
fn daemon_text(str, x, y, h) {
    bind n = str_len(str)
    bind i = 0
    while i < n {
        bind ch = str_slice(str, i, 1)
        if ch != " " { daemon_glyph(ch, x + i * h * 0.74 + h * 0.3, y, h * 0.62, h, 0.0) }
        bind i = i + 1
    }
}

fn text_w(str, h) { return str_len(str) * h * 0.74 }

# Chesilin glyph bent round a ring: radius rm, centred on angle ac (radians, clockwise from up)
fn chesilin_glyph(ch, cx, cy, rm, ac, bw, bh) {
    bind i = str_find(CHESILIN_CH, ch)
    if i < 0 { return 0 }
    bind g = CHESILIN[i]
    bind sc = min(bw / g[0], bh / g[1])
    bind gw = g[0] * sc
    bind gh = g[1] * sc
    bind polys = g[2]
    bind p = 0
    while p < len(polys) {
        bind pts = polys[p]
        bind n = len(pts) / 2
        bind j = 0
        while j < n {
            bind k = (j + 1) % n
            bind ux = pts[j * 2] * sc - gw / 2.0
            bind uy = pts[j * 2 + 1] * sc - gh / 2.0
            bind vx = pts[k * 2] * sc - gw / 2.0
            bind vy = pts[k * 2 + 1] * sc - gh / 2.0
            bind m = 1 + floor(abs(vx - ux) / (rm * 0.05))
            bind q = 0
            while q < m {
                bind f0 = q / m
                bind f1 = (q + 1) / m
                pline(cx, cy, rm - mix(uy, vy, f0), ac + mix(ux, vx, f0) / rm, rm - mix(uy, vy, f1), ac + mix(ux, vx, f1) / rm)
                bind q = q + 1
            }
            bind j = j + 1
        }
        bind p = p + 1
    }
    return 1
}

# ring text: the string repeats round the whole ring in n cells, turned by spin
fn ring_text(str, cx, cy, r0, r1, spin, lit, k, sat, hs) {
    bind band = r1 - r0
    bind rm = (r0 + r1) / 2.0
    bind sl = str_len(str)
    bind n = floor(TAU * rm / (band * 0.62))
    bind da = TAU / n
    col(k, sat, lit, hs)
    bind i = 0
    while i < n {
        bind ch = str_slice(str, i % sl, 1)
        if ch != " " { chesilin_glyph(ch, cx, cy, rm, spin + (i + 0.5) * da, rm * da * 0.72, band * 0.62) }
        bind i = i + 1
    }
}

# a Daemon tile: frame, corner brackets, letter, optional hatch
fn tile(ch, x, y, s, lit, k, sat, hs, fillk) {
    col(k, sat, lit, hs)
    draw_line(x, y, x + s, y)
    draw_line(x + s, y, x + s, y + s)
    draw_line(x + s, y + s, x, y + s)
    draw_line(x, y + s, x, y)
    bind a = s * 0.055
    bind b = s * 0.21
    draw_line(x + a, y + a + b, x + a, y + a)
    draw_line(x + a, y + a, x + a + b, y + a)
    draw_line(x + s - a - b, y + a, x + s - a, y + a)
    draw_line(x + s - a, y + a, x + s - a, y + a + b)
    draw_line(x + s - a, y + s - a - b, x + s - a, y + s - a)
    draw_line(x + s - a, y + s - a, x + s - a - b, y + s - a)
    draw_line(x + a + b, y + s - a, x + a, y + s - a)
    draw_line(x + a, y + s - a, x + a, y + s - a - b)
    if fillk > 0.0 {
        col(k, sat * 0.8, lit * 0.45, hs)
        hatch_box(x + a, y + a, s - 2.0 * a, max(2.5, s * 0.07) / fillk)
    }
    col(k, sat, min(lit + 16.0, 92.0), hs)
    daemon_glyph(ch, x + s / 2.0, y + s / 2.0, s * 0.6, s * 0.6, 0.0)
}

# ───────────────────────────── generic snapshot helpers ──────────────────────
# The *.snap files are written by sampler.ps1 (memory) and sampler_hw.ps1 (gpu, board, net).
# One record per line, fields split by '|'. A row is a list of strings.

fn nf(row, i) {
    if i < len(row) { return to_number(row[i]) }
    return 0.0
}

fn sf(row, i) {
    if i < len(row) { return row[i] }
    return ""
}

fn nums(row, from) {
    bind out = []
    bind i = from
    while i < len(row) {
        bind out = list_push(out, to_number(row[i]))
        bind i = i + 1
    }
    return out
}

fn zeros(n) {
    bind out = []
    bind i = 0
    while i < n {
        bind out = list_push(out, 0.0)
        bind i = i + 1
    }
    return out
}

fn pad_to(l, n) {
    bind out = l
    while len(out) < n {
        bind out = list_push(out, 0.0)
    }
    return out
}

fn snap_rows(text) {
    bind lines = split(text, "\n")
    bind out = []
    bind i = 0
    while i < len(lines) {
        bind ln = trim(lines[i])
        if str_len(ln) > 2 { bind out = list_push(out, split(ln, "|")) }
        bind i = i + 1
    }
    return out
}

# the sampler's wall-clock stamp: if it stops changing, the sampler has stopped
fn snap_time(rows) {
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        if r[0] == "t" { return nf(r, 1) }
        bind i = i + 1
    }
    return 0.0
}

# glide a list of numbers (or a list of such lists) toward its target
fn glide(cur, tgt, k) {
    if len(cur) != len(tgt) { return tgt }
    bind out = []
    bind i = 0
    while i < len(cur) {
        bind out = list_push(out, mix(cur[i], tgt[i], k))
        bind i = i + 1
    }
    return out
}

fn glide_ll(cur, tgt, k) {
    if len(cur) != len(tgt) { return tgt }
    bind out = []
    bind i = 0
    while i < len(cur) {
        bind out = list_push(out, glide(cur[i], tgt[i], k))
        bind i = i + 1
    }
    return out
}

# log scale to 0..1: full is log10 of the value that fills the strip
fn lg(x, full) { return clamp(log10(1.0 + max(x, 0.0)) / full, 0.0, 1.0) }

fn fmt_bytes(v) {
    if v >= 1000000000.0 { return num_str(round(v / 100000000.0) / 10.0) + "G" }
    if v >= 1000000.0 { return num_str(round(v / 100000.0) / 10.0) + "M" }
    if v >= 1000.0 { return num_str(round(v / 1000.0)) + "K" }
    return num_str(round(v))
}

fn fmt_up(s) {
    bind d = floor(s / 86400.0)
    bind h = floor((s - d * 86400.0) / 3600.0)
    bind m = floor((s - d * 86400.0 - h * 3600.0) / 60.0)
    if d > 0 { return num_str(d) + "D " + num_str(h) + "H" }
    return num_str(h) + "H " + num_str(m) + "M"
}

fn pct(x) { return num_str(round(x)) }

fn short_gpu(name) {
    bind s = str_replace(name, "NVIDIA ", "")
    bind s = str_replace(s, "GEFORCE ", "")
    bind s = str_replace(s, "AMD ", "")
    bind s = str_replace(s, "RADEON ", "")
    bind s = str_replace(s, "INTEL(R) ", "")
    return str_slice(s, 0, 14)
}

# ───────────────────────────── history strips ────────────────────────────────
fn hs_new() {
    bind hs = []
    bind i = 0
    while i < HS_COUNT {
        bind hs = list_push(hs, zeros(HS_N))
        bind i = i + 1
    }
    return hs
}

fn hs_push(hs, id, v) {
    bind old = hs[id]
    bind nw = []
    bind i = 1
    while i < len(old) {
        bind nw = list_push(nw, old[i])
        bind i = i + 1
    }
    bind nw = list_push(nw, clamp(v, 0.0, 1.0))
    return list_set(hs, id, nw)
}

# ───────────────────────────── GPU ───────────────────────────────────────────
# pack GP: [good, n, names, drvs, vecs, engs, procs, sel]
bind GP_N = 1
bind GP_NAME = 2
bind GP_DRV = 3
bind GP_VEC = 4
bind GP_ENG = 5
bind GP_PROC = 6
bind GP_SEL = 7

fn parse_gpu(text) {
    bind rows = snap_rows(text)
    bind n = 0
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        if r[0] == "n" { bind n = max(n, floor(nf(r, 1)) + 1) }
        bind i = i + 1
    }
    bind n = min(n, 8)
    bind names = []
    bind drvs = []
    bind vecs = []
    bind engs = []
    bind procs = []
    bind g = 0
    while g < n {
        bind names = list_push(names, "GPU")
        bind drvs = list_push(drvs, "")
        bind vecs = list_push(vecs, zeros(GV_LEN))
        bind engs = list_push(engs, zeros(6))
        bind procs = list_push(procs, [[], [], [], []])
        bind g = g + 1
    }
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        bind tag = r[0]
        bind g = floor(nf(r, 1))
        if g >= 0 {
            if g < n {
                if tag == "n" {
                    bind names = list_set(names, g, str_upper(sf(r, 2)))
                    bind drvs = list_set(drvs, g, sf(r, 3))
                }
                if tag == "v" { bind vecs = list_set(vecs, g, pad_to(nums(r, 2), GV_LEN)) }
                if tag == "e" { bind engs = list_set(engs, g, pad_to(nums(r, 2), 6)) }
                if tag == "a" {
                    bind pg = procs[g]
                    bind pg = [list_push(pg[0], str_upper(sf(r, 2))), list_push(pg[1], nf(r, 4)), list_push(pg[2], nf(r, 5)), list_push(pg[3], nf(r, 6))]
                    bind procs = list_set(procs, g, pg)
                }
            }
        }
        bind i = i + 1
    }
    bind good = 0
    if n > 0 { bind good = 1 }
    return [good, n, names, drvs, vecs, engs, procs, 0, snap_time(rows)]
}

bind SIM_NAMES = ["chrome", "code", "firefox", "explorer", "python", "node", "slack", "spotify", "ling", "audiodg",
                  "svchost", "dwm", "teams", "steam", "obs", "docker", "mysqld", "discord", "winword", "excel"]

fn sim_gpu(t) {
    bind names = []
    bind drvs = []
    bind vecs = []
    bind engs = []
    bind procs = []
    bind g = 0
    while g < 4 {
        bind u = 22.0 + 20.0 * sin(t * 0.31 + g * 2.0) + 55.0 * pow(abs(sin(t * 0.07 + g * 1.3)), 4.0)
        bind u = clamp(u, 1.0, 100.0)
        bind vu = 9000.0 + 5000.0 * sin(t * 0.05 + g) + 40.0 * u
        bind names = list_push(names, "SIM GPU " + num_str(g))
        bind drvs = list_push(drvs, "0.0")
        bind vecs = list_push(vecs, [36.0 + 0.4 * u, 28.0 + 0.55 * u, 18.0 + 4.1 * u, 450.0, 210.0 + 28.0 * u, 3105.0, 405.0 + 100.0 * u, 10501.0, u, u * 0.4, vu, 24564.0, 4.0, 16.0, 0.0, 0.0])
        bind engs = list_push(engs, [u * 0.95, u * 0.3 * (1.0 + sin(t * 0.4)), 12.0 * pow(abs(sin(t * 0.21 + g)), 3.0), 6.0 * pow(abs(sin(t * 0.13)), 5.0), 25.0 * pow(abs(sin(t * 0.09 + g)), 6.0), 3.0])
        bind pn = []
        bind pd = []
        bind ps = []
        bind pu = []
        bind q = 0
        while q < 8 {
            bind pn = list_push(pn, str_upper(SIM_NAMES[(q + g * 5) % 20]))
            bind pd = list_push(pd, vu * 0.5 / (1.0 + q * 0.9))
            bind ps = list_push(ps, 30.0 / (1.0 + q))
            bind pu = list_push(pu, u * 0.7 / (1.0 + q))
            bind q = q + 1
        }
        bind procs = list_push(procs, [pn, pd, ps, pu])
        bind g = g + 1
    }
    return [1, 4, names, drvs, vecs, engs, procs, 0, 0.0]
}

# ───────────────────────────── board ─────────────────────────────────────────
# pack BP: [good, info, bv, cutil, cfreq, dimms, disks, dio, sens, t]
bind BP_INFO = 1
bind BP_BV = 2
bind BP_CU = 3
bind BP_CF = 4
bind BP_DIMM = 5
bind BP_DISK = 6
bind BP_DIO = 7
bind BP_SENS = 8
# info: [maker, product, bios vendor, bios version, bios date, cpu, cores, threads, max MHz, sockets, dimm slots, max GB]
bind BI_MAKER = 0
bind BI_PROD = 1
bind BI_BIOSV = 2
bind BI_BIOSVER = 3
bind BI_BIOSDATE = 4
bind BI_CPU = 5
bind BI_CORES = 6
bind BI_THR = 7
bind BI_MHZ = 8
bind BI_SOCK = 9
bind BI_SLOTS = 10
bind BI_MAXGB = 11

fn parse_board(text) {
    bind rows = snap_rows(text)
    bind info = ["", "", "", "", "", "", 0, 0, 0, 1, 0, 0]
    bind bv = []
    bind nc = 0
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        if r[0] == "k" { bind nc = max(nc, floor(nf(r, 1)) + 1) }
        bind i = i + 1
    }
    bind nc = min(nc, 128)
    bind cu = zeros(nc)
    bind cf = zeros(nc)
    bind dimms = []
    bind disks = []
    bind dio = []
    bind sens = []
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        bind tag = r[0]
        if tag == "b" {
            bind info = list_set(info, BI_MAKER, str_upper(sf(r, 1)))
            bind info = list_set(info, BI_PROD, str_upper(sf(r, 2)))
        }
        if tag == "i" {
            bind info = list_set(info, BI_BIOSV, str_upper(sf(r, 1)))
            bind info = list_set(info, BI_BIOSVER, sf(r, 2))
            bind info = list_set(info, BI_BIOSDATE, sf(r, 3))
        }
        if tag == "c" {
            bind info = list_set(info, BI_CPU, str_upper(sf(r, 1)))
            bind info = list_set(info, BI_CORES, nf(r, 2))
            bind info = list_set(info, BI_THR, nf(r, 3))
            bind info = list_set(info, BI_MHZ, nf(r, 4))
            bind info = list_set(info, BI_SOCK, nf(r, 5))
        }
        if tag == "y" {
            bind info = list_set(info, BI_SLOTS, nf(r, 1))
            bind info = list_set(info, BI_MAXGB, nf(r, 2))
        }
        if tag == "m" { bind dimms = list_push(dimms, [str_upper(sf(r, 1)), nf(r, 2), nf(r, 3)]) }
        if tag == "d" { bind disks = list_push(disks, [str_upper(sf(r, 1)), nf(r, 2)]) }
        if tag == "l" { bind bv = pad_to(nums(r, 1), BV_LEN) }
        if tag == "k" {
            bind c = floor(nf(r, 1))
            if c >= 0 {
                if c < nc {
                    bind cu = list_set(cu, c, nf(r, 2))
                    bind cf = list_set(cf, c, nf(r, 3))
                }
            }
        }
        if tag == "s" { bind dio = list_push(dio, [str_upper(sf(r, 1)), nf(r, 2), nf(r, 3), nf(r, 4)]) }
        if tag == "h" { bind sens = list_push(sens, [sf(r, 1), str_upper(sf(r, 2)), nf(r, 3)]) }
        bind i = i + 1
    }
    bind good = 0
    if len(bv) >= BV_LEN {
        if nc > 0 { bind good = 1 }
    }
    return [good, info, bv, cu, cf, dimms, disks, dio, sens, snap_time(rows)]
}

fn sim_board(t) {
    bind u = clamp(30.0 + 25.0 * sin(t * 0.3) + 35.0 * pow(abs(sin(t * 0.11)), 5.0), 2.0, 100.0)
    bind info = ["SIMCORP", "HATCH SIM Z999", "SIM BIOS", "1.0", "2030-01-01", "SIM CORE 16", 8, 16, 4800, 1, 4, 128]
    bind bv = [u, 2800.0 + 2000.0 * u / 100.0, 100.0, 32.0 + 28.0 * u / 100.0, 18000.0 + 160000.0 * u / 100.0, 2000.0 + 30000.0 * pow(abs(sin(t * 0.4)), 4.0),
               90000.0 + 600000.0 * u / 100.0, 800000.0 + 6000000.0 * u / 100.0, 420.0, 5200.0, 86400.0 * 3.0 + t,
               1000000.0 * pow(abs(sin(t * 0.23)), 6.0) * 300.0, 1000000.0 * pow(abs(sin(t * 0.19 + 1.0)), 6.0) * 200.0,
               30.0 * pow(abs(sin(t * 0.2)), 3.0), 1.0]
    bind cu = []
    bind cf = []
    bind i = 0
    while i < 16 {
        bind ci = clamp(u + 45.0 * sin(t * 0.5 + i * 0.9) - 15.0, 0.0, 100.0)
        bind cu = list_push(cu, ci)
        bind cf = list_push(cf, 1200.0 + 3600.0 * ci / 100.0)
        bind i = i + 1
    }
    bind dimms = [["DIMM_A1", 16.0, 5600.0], ["DIMM_B1", 16.0, 5600.0]]
    bind disks = [["SIM NVME 1000", 1000.0], ["SIM SATA 2000", 2000.0]]
    bind dio = [["0 C:", bv[BV_DRD], bv[BV_DWR], bv[BV_DBUSY]], ["1 D:", 0.0, 0.0, 0.0]]
    return [1, info, bv, cu, cf, dimms, disks, dio, [], 0.0]
}

# ───────────────────────────── network ───────────────────────────────────────
# pack NP: [good, adapters, nv, nodes, procs, t]; adapter [name, rx, tx, bits/s, pkts in, pkts out, errors];
# node [hash 0..1, port, state 0 est / 1 syn, process]; proc [name, connections]
bind NP_AD = 1
bind NP_NV = 2
bind NP_NODE = 3
bind NP_PROC = 4

# remote port -> one of the seven classes (= the seven Ling hues)
fn port_class(p) {
    if p == 443 { return 0 }
    if p == 80 { return 1 }
    if p == 8080 { return 1 }
    if p == 8000 { return 1 }
    if p == 53 { return 2 }
    if p == 5353 { return 2 }
    if p == 22 { return 3 }
    if p == 3389 { return 3 }
    if p == 5900 { return 3 }
    if p == 25 { return 4 }
    if p == 110 { return 4 }
    if p == 143 { return 4 }
    if p == 465 { return 4 }
    if p == 587 { return 4 }
    if p == 993 { return 4 }
    if p == 995 { return 4 }
    if p >= 1024 { return 5 }
    return 6
}

bind PORT_NAME = ["HTTPS", "HTTP", "DNS", "REMOTE", "MAIL", "HIGH", "OTHER"]

fn parse_net(text) {
    bind rows = snap_rows(text)
    bind ads = []
    bind nodes = []
    bind procs = []
    bind kk = [0.0, 0.0, 0.0, 0.0, 0.0]
    bind cc = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    bind rtt = 0.0 - 1.0
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        bind tag = r[0]
        if tag == "i" { bind ads = list_push(ads, [sf(r, 1), nf(r, 2), nf(r, 3), nf(r, 4), nf(r, 5), nf(r, 6), nf(r, 7)]) }
        if tag == "k" { bind kk = pad_to(nums(r, 1), 5) }
        if tag == "r" { bind rtt = nf(r, 1) }
        if tag == "c" { bind cc = pad_to(nums(r, 1), 7) }
        if tag == "x" { bind procs = list_push(procs, [str_upper(sf(r, 1)), nf(r, 3)]) }
        if tag == "d" {
            bind st = 0
            if sf(r, 3) != "ESTABLISHED" { bind st = 1 }
            bind nodes = list_push(nodes, [nf(r, 1), nf(r, 2), st, str_upper(sf(r, 4))])
        }
        bind i = i + 1
    }
    bind rx = 0.0
    bind tx = 0.0
    bind prx = 0.0
    bind ptx = 0.0
    bind bw = 0.0
    bind a = 0
    while a < len(ads) {
        bind ad = ads[a]
        bind rx = rx + ad[1]
        bind tx = tx + ad[2]
        bind prx = prx + ad[4]
        bind ptx = ptx + ad[5]
        if a == 0 { bind bw = ad[3] }
        bind a = a + 1
    }
    bind nv = [rx, tx, prx, ptx, bw, kk[0], kk[1], kk[4], rtt, cc[1], cc[2], cc[3], cc[4], cc[6], kk[2]]
    bind good = 0
    if len(ads) > 0 { bind good = 1 }
    return [good, ads, nv, nodes, procs, snap_time(rows)]
}

fn sim_net(t) {
    bind b1 = pow(abs(sin(t * 0.21)), 5.0)
    bind b2 = pow(abs(sin(t * 0.33 + 1.0)), 6.0)
    bind rx = 40000.0 + 90000.0 * (1.0 + sin(t * 0.5)) + 40000000.0 * b1
    bind tx = 8000.0 + 20000.0 * (1.0 + sin(t * 0.4)) + 6000000.0 * b2
    bind ads = [["SIM ETHERNET", rx, tx, 1000000000.0, rx / 1200.0, tx / 900.0, 0.0]]
    bind nodes = []
    bind ports = [443, 443, 443, 80, 53, 22, 8333, 25565, 443, 993, 443, 3389, 8080, 51820]
    bind i = 0
    while i < 28 {
        bind nodes = list_push(nodes, [fract(i * 0.6180339 + 0.1), ports[i % 14], 0, str_upper(SIM_NAMES[(i * 7) % 20])])
        bind i = i + 1
    }
    bind procs = []
    bind i = 0
    while i < 8 {
        bind procs = list_push(procs, [str_upper(SIM_NAMES[i]), 12.0 - i])
        bind i = i + 1
    }
    bind nv = [rx, tx, rx / 1200.0, tx / 900.0, 1000000000.0, 90.0 + 20.0 * sin(t * 0.2), 2.0 + 30.0 * b2, 15.0 + 60.0 * b1, 8.0 + 10.0 * b2, 12.0, 6.0, 2.0, 0.0, 40.0, 3.0]
    return [1, ads, nv, nodes, procs, 0.0]
}

# ───────────────────────────── history sampling ──────────────────────────────
# push one 0..1 sample into every strip; GP, BP, NP are the smoothed packs
fn hs_sample(hs, GP, BP, NP, memUsed, memFlt) {
    bind g = 0
    while g < min(GP[GP_N], 8) {
        bind v = GP[GP_VEC][g]
        bind b = HS_GPU + g * 4
        bind hs = hs_push(hs, b, v[GV_UTIL] / 100.0)
        bind hs = hs_push(hs, b + 1, v[GV_TEMP] / 100.0)
        bind hs = hs_push(hs, b + 2, v[GV_POWER] / max(v[GV_PLIM], 1.0))
        bind hs = hs_push(hs, b + 3, v[GV_VUSED] / max(v[GV_VTOTAL], 1.0))
        bind g = g + 1
    }
    bind bv = BP[BP_BV]
    bind hs = hs_push(hs, HS_BRD, bv[BV_UTIL] / 100.0)
    bind hs = hs_push(hs, HS_BRD + 1, bv[BV_TEMP] / 100.0)
    bind hs = hs_push(hs, HS_BRD + 2, lg(bv[BV_INTR], 6.0))
    bind hs = hs_push(hs, HS_BRD + 3, lg(bv[BV_DPC], 5.0))
    bind hs = hs_push(hs, HS_BRD + 4, lg(bv[BV_CTX], 6.5))
    bind hs = hs_push(hs, HS_BRD + 5, lg(bv[BV_DRD], 9.0))
    bind hs = hs_push(hs, HS_BRD + 6, lg(bv[BV_DWR], 9.0))
    bind nv = NP[NP_NV]
    bind hs = hs_push(hs, HS_NET, lg(nv[NV_RX], 9.0))
    bind hs = hs_push(hs, HS_NET + 1, lg(nv[NV_TX], 9.0))
    bind hs = hs_push(hs, HS_NET + 2, lg(nv[NV_PRX], 6.0))
    bind hs = hs_push(hs, HS_NET + 3, lg(nv[NV_PTX], 6.0))
    bind hs = hs_push(hs, HS_NET + 4, lg(nv[NV_EST], 3.5))
    bind hs = hs_push(hs, HS_NET + 5, lg(nv[NV_UDP], 4.0))
    bind rt = 0.0
    if nv[NV_RTT] > 0.0 { bind rt = nv[NV_RTT] / 500.0 }
    bind hs = hs_push(hs, HS_NET + 6, rt)
    bind hs = hs_push(hs, HS_NET + 7, lg(nv[NV_RETX], 3.0))
    bind hs = hs_push(hs, HS_MEM, memUsed)
    bind hs = hs_push(hs, HS_MEM + 1, lg(memFlt, 6.0))
    return hs
}

# ───────────────────────────── data ──────────────────────────────────────────
# mem.snap, see sampler.ps1. Returns [good, g, r, private_all, names, ws, priv, ior, iow]
fn parse_snap(text) {
    bind g = []
    bind r = []
    bind pall = 0.0
    bind names = []
    bind ws = []
    bind priv = []
    bind ior = []
    bind iow = []
    bind lines = split(text, "\n")
    bind i = 0
    while i < len(lines) {
        bind f = split(trim(lines[i]), "|")
        bind tag = f[0]
        if tag == "g" {
            bind g = []
            bind j = 1
            while j < len(f) {
                bind g = list_push(g, to_number(f[j]))
                bind j = j + 1
            }
        }
        if tag == "r" {
            bind r = []
            bind j = 1
            while j < len(f) {
                bind r = list_push(r, to_number(f[j]))
                bind j = j + 1
            }
        }
        if tag == "t" { bind ts = to_number(f[1]) }
        if tag == "s" { bind pall = to_number(f[1]) }
        if tag == "p" {
            if len(f) >= 8 {
                bind names = list_push(names, f[1])
                bind ws = list_push(ws, to_number(f[3]))
                bind priv = list_push(priv, to_number(f[4]))
                bind ior = list_push(ior, to_number(f[5]))
                bind iow = list_push(iow, to_number(f[6]))
            }
        }
        bind i = i + 1
    }
    bind good = 0
    if len(g) >= 11 {
        if len(r) >= 8 { bind good = 1 }
    }
    return [good, g, r, pall, names, ws, priv, ior, iow, ts]
}

# the seven category sizes in MB, from the sampler's counters
fn categories(g, pall) {
    bind total = g[0]
    bind avail = g[1]
    bind free = g[2]
    bind standby = g[3]
    bind modified = g[4]
    bind kernel = g[5] + g[6]
    bind comp = g[7]
    bind inuse = max(total - avail - modified, 0.0)
    bind data = clamp(pall - comp, 0.0, max(inuse - kernel - comp, 0.0))
    bind mapped = max(inuse - kernel - comp - data, 0.0)
    return [kernel, standby, data, modified, mapped, comp, free]
}

# a made-up machine for when there is no sampler: 16 GB, breathing, with bursts of paging
fn sim_cats(t) {
    bind total = 16384.0
    bind kernel = 900.0 + 150.0 * sin(t * 0.13)
    bind standby = 4200.0 + 1600.0 * sin(t * 0.05 + 1.0)
    bind data = 5200.0 + 2200.0 * sin(t * 0.031 + 2.0) + 600.0 * sin(t * 0.21)
    bind modified = 130.0 + 110.0 * (sin(t * 0.4) + 1.0)
    bind comp = 700.0 + 300.0 * sin(t * 0.07)
    bind mapped = 1500.0 + 500.0 * sin(t * 0.09 + 3.0)
    bind free = max(total - kernel - standby - data - modified - comp - mapped, 200.0)
    return [kernel, standby, data, modified, mapped, comp, free]
}

fn sim_rates(t) {
    bind b1 = pow(abs(sin(t * 0.37)), 6.0)
    bind b2 = pow(abs(sin(t * 0.23 + 1.0)), 8.0)
    bind b3 = pow(abs(sin(t * 0.51 + 2.0)), 5.0)
    return [3000.0 + 2500.0 * sin(t * 0.7) + 40000.0 * b3, 40.0 + 4000.0 * b1, 20.0 + 3000.0 * b2]
}


# weighted pick: w is a list of weights, u in [0,1)
fn pick(w, u) {
    bind sum = 0.0
    bind i = 0
    while i < len(w) {
        bind sum = sum + w[i]
        bind i = i + 1
    }
    bind v = u * sum
    bind i = 0
    while i < len(w) - 1 {
        if v < w[i] { return i }
        bind v = v - w[i]
        bind i = i + 1
    }
    return len(w) - 1
}

# Hilbert curve table for a 32 x 32 page map: flat [x0, y0, x1, y1, ...]
fn hilbert_table(n) {
    bind out = []
    bind d = 0
    while d < n * n {
        bind t = d
        bind x = 0
        bind y = 0
        bind s = 1
        while s < n {
            bind rx = floor(t / 2) % 2
            bind ry = (t % 2 + rx) % 2
            if ry == 0 {
                if rx == 1 {
                    bind x = s - 1 - x
                    bind y = s - 1 - y
                }
                bind tmp = x
                bind x = y
                bind y = tmp
            }
            bind x = x + s * rx
            bind y = y + s * ry
            bind t = floor(t / 4)
            bind s = s * 2
        }
        bind out = list_push(out, x)
        bind out = list_push(out, y)
        bind d = d + 1
    }
    return out
}

# ───────────────────────────── pulses ────────────────────────────────────────
# EV is flat, six numbers per pulse: kind (0 read circle, 1 write square), category,
# born, seed, magnitude, process index.

fn ev_add(ev, kind, cat, born, seed, mag, proc) {
    bind ev = list_push(ev, kind)
    bind ev = list_push(ev, cat)
    bind ev = list_push(ev, born)
    bind ev = list_push(ev, seed)
    bind ev = list_push(ev, mag)
    bind ev = list_push(ev, proc)
    return ev
}

fn ev_cull(ev, t, life) {
    bind out = []
    bind i = 0
    while i < len(ev) {
        if t - ev[i + 2] < life {
            bind j = 0
            while j < 6 {
                bind out = list_push(out, ev[i + j])
                bind j = j + 1
            }
        }
        bind i = i + 6
    }
    return out
}

# ───────────────────────────── view 1: RINGS ─────────────────────────────────
fn ring_geom(c, R) {
    bind r1 = R * (1.0 - c * 0.1)
    bind band = R * 0.082
    return [r1, r1 - band, band]
}

fn ring_spin(Z, c) {
    bind dir = 1.0
    if c % 2 == 1 { bind dir = 0.0 - 1.0 }
    return Z[Z_ROT] * dir * (0.35 + 0.12 * c) + Z[Z_T] * dir * 0.03 * (c + 1)
}

fn view_rings(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind cx = W / 2.0
    bind cy = H / 2.0
    bind R = min(W, H) * 0.5 - 16.0
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind cats = Z[Z_CATS]
    bind total = Z[Z_TOTAL]
    bind en = Z[Z_ENERGY]
    bind c = 0
    while c < 7 {
        bind gm = ring_geom(c, R)
        bind r1 = gm[0]
        bind r0 = gm[1]
        bind band = gm[2]
        bind frac = cats[c] / total
        bind spin = ring_spin(Z, c)
        bind e = en[c]
        # the two rails
        col(c, sat * 0.7, 26.0 + 14.0 * e, hs)
        draw_circle(cx, cy, r1)
        draw_circle(cx, cy, r0)
        # crosshatch over the occupied sector, a second angle once the ring is busy
        bind sweep = max(frac * TAU, 0.03)
        bind da = 0.0215
        bind n = floor(sweep / da)
        bind k = 0
        col(c, sat, 36.0 + 26.0 * e, hs)
        while k < n {
            bind a = spin + k * da
            pline(cx, cy, r0, a, r1, a + 0.05 + 0.05 * e)
            bind k = k + 1
        }
        if e > 0.35 {
            bind k = 0
            col(c, sat, 30.0 + 22.0 * e, hs)
            while k < n {
                bind a = spin + k * da
                pline(cx, cy, r0, a + 0.05, r1, a)
                bind k = k + 2
            }
        }
        col(c, sat, 62.0 + 28.0 * e, hs)
        pline(cx, cy, r0 - 4.0, spin, r1 + 4.0, spin)
        pline(cx, cy, r0 - 4.0, spin + sweep, r1 + 4.0, spin + sweep)
        # ring text: name, size, share
        bind label = CAT_NAME[c] + " " + fmt_mb(cats[c]) + " " + num_str(round(frac * 1000.0) / 10.0) + "% "
        ring_text(label, cx, cy, r0 + band * 0.06, r1 - band * 0.06, spin * 0.6 + 1.0, 74.0 + 20.0 * e, c, sat, hs)
        bind c = c + 1
    }
    # hub: used share in Daemon, ticks turning
    bind hr = R * 0.3 - 6.0
    ink(34.0)
    draw_circle(cx, cy, hr)
    bind k = 0
    while k < 60 {
        bind a = k * TAU / 60.0 + Z[Z_ROT] * 0.5
        bind tl = 5.0
        if k % 5 == 0 { bind tl = 11.0 }
        pline(cx, cy, hr - tl, a, hr, a)
        bind k = k + 1
    }
    bind used = num_str(round(Z[Z_USED] * 100.0)) + "%"
    col(2, sat, 78.0, hs)
    daemon_text(used, cx - text_w(used, hr * 0.5) / 2.0, cy - hr * 0.08, hr * 0.5)
    ink(55.0)
    daemon_text("USED", cx - text_w("USED", hr * 0.2) / 2.0, cy + hr * 0.4, hr * 0.2)
    # pulses ride their ring
    bind ev = Z[Z_EV]
    bind i = 0
    while i < len(ev) {
        bind age = t - ev[i + 2]
        bind p = age / 1.8
        bind cc = ev[i + 1]
        bind gm = ring_geom(cc, R)
        bind rm = (gm[0] + gm[1]) / 2.0
        bind a = ring_spin(Z, cc) + ev[i + 3] * TAU
        bind x = px(cx, rm, a)
        bind y = py(cy, rm, a)
        col(cc, sat, (1.0 - p) * (55.0 + 40.0 * ev[i + 4]), hs)
        if ev[i] == 0 {
            draw_circle(x, y, gm[2] * (0.25 + 1.5 * p))
            draw_circle(x, y, gm[2] * (0.1 + 0.9 * p))
        } else {
            square(x, y, gm[2] * (1.1 - 0.8 * p), a + p * 2.0)
            square(x, y, gm[2] * (0.6 - 0.4 * p), a - p * 2.0)
        }
        bind i = i + 6
    }
}

# ───────────────────────────── view 2: TILES ─────────────────────────────────
fn cell_cat(i, cum) {
    bind k = 0
    while k < 6 {
        if i < cum[k] { return ORDER[k] }
        bind k = k + 1
    }
    return ORDER[6]
}

fn view_tiles(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind cats = Z[Z_CATS]
    bind total = Z[Z_TOTAL]
    bind en = Z[Z_ENERGY]
    bind hil = Z[Z_HIL]
    bind S = min(H * 0.76, W * 0.5)
    bind cs = S / 32.0
    bind mx = W * 0.36
    bind my = H * 0.5
    bind tilt = 0.09 * sin(t * 0.07) + 0.07 * sin(Z[Z_ROT] * 0.8)
    bind ca = cos(tilt)
    bind sa = sin(tilt)
    # cumulative cell counts along the curve, in ORDER
    bind cum = []
    bind run = 0.0
    bind k = 0
    while k < 7 {
        bind run = run + cats[ORDER[k]] / total * 1024.0
        bind cum = list_push(cum, round(run))
        bind k = k + 1
    }
    bind i = 0
    while i < 1024 {
        bind c = cell_cat(i, cum)
        bind gx = hil[i * 2] - 15.5
        bind gy = hil[i * 2 + 1] - 15.5
        bind x = mx + (gx * ca - gy * sa) * cs
        bind y = my + (gx * sa + gy * ca) * cs
        bind hv = fract(sin(i * 12.9898) * 43758.5453)
        bind hv = fract(hv + 0.18 * sin(t * 0.6 + i * 0.37) + 1.0)
        bind e = en[c]
        bind h = cs * 0.46
        col(c, sat * 0.8, 20.0 + 16.0 * hv + 14.0 * e, hs + (hv - 0.5) * 10.0)
        square(x, y, h, tilt)
        if c != 6 {
            bind nl = 1 + floor(hv * 3.0 + e * 2.0)
            bind m = 0
            col(c, sat, 30.0 + 26.0 * hv + 22.0 * e, hs + (hv - 0.5) * 10.0)
            while m < nl {
                bind u = (m + 0.5) / nl
                bind s0 = (u * 2.0 - 1.0) * h
                bind an = tilt + ANG1[c]
                bind dx = cos(an) * h * 0.9
                bind dy = sin(an) * h * 0.9
                bind ox = (0.0 - sin(an)) * s0
                bind oy = cos(an) * s0
                draw_line(x + ox - dx, y + oy - dy, x + ox + dx, y + oy + dy)
                bind m = m + 1
            }
        } else {
            col(6, sat * 0.5, 18.0, hs)
            draw_pixel(x, y)
        }
        bind i = i + 1
    }
    ink(30.0)
    square(mx, my, S * 0.5 + cs * 0.9, tilt)
    # pulses: read = circles leaving a cell, write = squares closing on it
    bind ev = Z[Z_EV]
    bind j = 0
    while j < len(ev) {
        bind age = t - ev[j + 2]
        bind p = age / 1.8
        bind c = ev[j + 1]
        bind kk = 0
        while kk < 7 {
            if ORDER[kk] == c {
                bind lo = 0
                if kk > 0 { bind lo = cum[kk - 1] }
                bind span = max(cum[kk] - lo, 1.0)
                bind idx = min(floor(lo + ev[j + 3] * span), 1023)
                bind gx = hil[idx * 2] - 15.5
                bind gy = hil[idx * 2 + 1] - 15.5
                bind x = mx + (gx * ca - gy * sa) * cs
                bind y = my + (gx * sa + gy * ca) * cs
                col(c, sat, (1.0 - p) * (60.0 + 35.0 * ev[j + 4]), hs)
                if ev[j] == 0 {
                    draw_circle(x, y, cs * (0.5 + 3.5 * p))
                    draw_circle(x, y, cs * (0.3 + 1.6 * p))
                } else {
                    square(x, y, cs * (2.6 - 2.1 * p), tilt + p * 3.0)
                    square(x, y, cs * 0.5, tilt)
                    if p < 0.35 { draw_disc(x, y, cs * 0.35) }
                }
            }
            bind kk = kk + 1
        }
        bind j = j + 6
    }
    # legend: seven Daemon tiles
    bind lx = W * 0.68
    bind ts = min(H * 0.095, 62.0)
    bind c = 0
    while c < 7 {
        bind ly = H * 0.1 + c * (ts + H * 0.03)
        bind e = en[c]
        tile(str_slice(CAT_LET, c, 1), lx, ly, ts, 52.0 + 30.0 * e, c, sat, hs, 0.6 + e)
        col(c, sat, 68.0, hs)
        daemon_text(CAT_NAME[c], lx + ts + 14.0, ly + ts * 0.3, ts * 0.3)
        ink(62.0)
        daemon_text(fmt_mb(cats[c]) + " " + num_str(round(cats[c] / total * 1000.0) / 10.0) + "%", lx + ts + 14.0, ly + ts * 0.68, ts * 0.26)
        bind bw = W * 0.28
        bind bx = lx + ts + 14.0
        bind by = ly + ts * 0.95
        col(c, sat * 0.8, 40.0, hs)
        draw_line(bx, by, bx + bw, by)
        bind fw = bw * cats[c] / total
        col(c, sat, 60.0 + 20.0 * e, hs)
        bind u = 0.0
        while u < fw {
            draw_line(bx + u, by, bx + u + 4.0, by - 5.0)
            bind u = u + 3.0
        }
        bind c = c + 1
    }
}

# ───────────────────────────── view 3: ORBITS ────────────────────────────────
fn name_hue(name) {
    bind h = 0
    bind i = 0
    while i < str_len(name) {
        bind h = (h * 31 + str_find(DAEMON_CH, str_slice(name, i, 1)) + 1) % 7919
        bind i = i + 1
    }
    return h % 7
}

fn view_orbits(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind cx = W / 2.0
    bind cy = H / 2.0
    bind R = min(W, H) * 0.5 - 20.0
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind names = Z[Z_NAME]
    bind ws = Z[Z_WS]
    bind pv = Z[Z_PRIV]
    bind ior = Z[Z_IOR]
    bind iow = Z[Z_IOW]
    bind np = min(min(len(names), len(ior)), 22)
    bind i = 0
    while i < np {
        ink(14.0)
        draw_circle(cx, cy, R * (0.24 + 0.74 * i / max(np - 1, 1)))
        bind i = i + 1
    }
    # the sun: memory in use
    bind used = num_str(round(Z[Z_USED] * 100.0)) + "%"
    col(2, sat, 70.0, hs)
    draw_circle(cx, cy, R * 0.12)
    draw_circle(cx, cy, R * 0.1)
    daemon_text(used, cx - text_w(used, R * 0.09) / 2.0, cy, R * 0.09)
    bind k = 0
    while k < 36 {
        bind a = k * TAU / 36.0 - Z[Z_ROT] * 0.7
        pline(cx, cy, R * 0.12, a, R * 0.12 + 5.0 + 4.0 * (k % 3), a)
        bind k = k + 1
    }
    # bodies: circle = working set, square = private bytes
    bind pos = []
    bind i = 0
    while i < np {
        bind orr = R * (0.24 + 0.74 * i / max(np - 1, 1))
        bind dir = 1.0
        if i % 2 == 1 { bind dir = 0.0 - 1.0 }
        bind io = clamp(log10(1.0 + ior[i] + iow[i]) / 4.0, 0.0, 1.0)
        bind a = i * 2.399 + Z[Z_ROT] * dir * (0.8 / sqrt(1.0 + i)) * (1.0 + 2.0 * io)
        bind x = px(cx, orr, a)
        bind y = py(cy, orr, a)
        bind pos = list_push(pos, x)
        bind pos = list_push(pos, y)
        bind k = name_hue(names[i])
        bind r = clamp(4.0 + sqrt(ws[i]) * 0.3, 5.0, R * 0.11)
        col(k, sat, 48.0 + 24.0 * io, hs)
        draw_circle(x, y, r)
        draw_circle(x, y, r * 0.86)
        col(k, sat, 32.0 + 22.0 * io, hs)
        hatch_disc(x, y, r * 0.84, ANG1[k] + Z[Z_ROT] * 0.5, max(2.6, r * 0.12))
        bind sh = r * 0.7 * sqrt(clamp(pv[i] / max(ws[i], 1.0), 0.05, 1.0))
        col(k, sat, 78.0, hs)
        square(x, y, sh, t * 0.6 * dir + i)
        if r > 15.0 {
            col(k, sat, 88.0, hs)
            daemon_glyph(str_upper(str_slice(names[i], 0, 1)), x, y, r * 0.5, r * 0.6, 0.0)
        }
        if r > 11.0 {
            bind nm = str_upper(str_slice(names[i], 0, 10))
            bind m = str_len(nm)
            bind rm2 = r + 9.0
            bind da = 11.0 / rm2
            bind q = 0
            col(k, sat * 0.9, 70.0, hs)
            while q < m {
                bind ch = str_slice(nm, q, 1)
                if ch != " " { chesilin_glyph(ch, x, y, rm2, (q - (m - 1) / 2.0) * da, 9.0, 8.0) }
                bind q = q + 1
            }
        }
        bind i = i + 1
    }
    # pulses leave their process
    bind ev = Z[Z_EV]
    bind j = 0
    while j < len(ev) {
        bind pidx = ev[j + 5]
        if pidx < np {
            bind p = (t - ev[j + 2]) / 1.8
            bind x = pos[pidx * 2]
            bind y = pos[pidx * 2 + 1]
            bind k = name_hue(names[pidx])
            bind r = clamp(4.0 + sqrt(ws[pidx]) * 0.3, 5.0, R * 0.11)
            col(k, sat, (1.0 - p) * (60.0 + 38.0 * ev[j + 4]), hs)
            if ev[j] == 0 {
                draw_circle(x, y, r * (1.0 + 1.1 * p))
                draw_circle(x, y, r * (1.0 + 0.5 * p))
            } else {
                square(x, y, r * (1.9 - 0.8 * p), p * 2.5)
                square(x, y, r * (1.4 - 0.6 * p), 0.0 - p * 2.5)
            }
        }
        bind j = j + 6
    }
}

# ───────────────────────────── view 4: WEAVE ─────────────────────────────────
# Seven strings, one per category, each a sine at that category's partial ratio and
# loudness: the picture of the additive synth. The bottom trace is their sum. Notes
# (every read and write) are born at the playhead and scroll left.

fn lane_y(c, H) { return H * 0.14 + c * H * 0.095 }

fn view_weave(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind cats = Z[Z_CATS]
    bind total = Z[Z_TOTAL]
    bind en = Z[Z_ENERGY]
    bind x0 = W * 0.12
    bind x1 = W * 0.9
    bind xph = W * 0.86
    bind sw = 8.0
    bind amp = H * 0.042
    bind c = 0
    while c < 7 {
        bind y = lane_y(c, H)
        bind e = en[c]
        bind a = amp * (0.12 + 0.88 * sqrt(cats[c] / total)) * (0.7 + 0.5 * e)
        col(c, sat * 0.6, 14.0, hs)
        draw_line(x0, y, x1, y)
        col(c, sat, 40.0 + 36.0 * e, hs)
        bind x = x0
        bind prevy = y + a * sin(0.0 - t * RATIO[c] * 1.3)
        while x < x1 {
            bind u = (x + sw - x0) / (x1 - x0)
            bind ny = y + a * sin(u * TAU * RATIO[c] * 2.0 - t * RATIO[c] * 1.3)
            draw_line(x, prevy, x + sw, ny)
            bind prevy = ny
            bind x = x + sw
        }
        tile(str_slice(CAT_LET, c, 1), W * 0.03, y - H * 0.035, H * 0.07, 40.0 + 40.0 * e, c, sat, hs, 0.3 + e)
        col(c, sat, 40.0 + 40.0 * e, hs)
        bind dr = H * 0.033
        draw_circle(W * 0.095, y, dr)
        pline(W * 0.095, y, 0.0, 0.0, dr, t * RATIO[c] * 0.6 + c)
        bind c = c + 1
    }
    # the sum of the seven strings
    bind ys = H * 0.86
    ink(18.0)
    draw_line(x0, ys, x1, ys)
    col(2, sat * 0.4, 86.0, hs)
    bind x = x0
    bind prevy = ys
    bind first = 1
    while x < x1 {
        bind u = (x - x0) / (x1 - x0)
        bind s = 0.0
        bind k = 0
        while k < 7 {
            bind s = s + sqrt(cats[k] / total) * sin(u * TAU * RATIO[k] * 2.0 - t * RATIO[k] * 1.3)
            bind k = k + 1
        }
        bind ny = ys + s * H * 0.045
        if first == 0 { draw_line(x - 3.0, prevy, x, ny) }
        bind prevy = ny
        bind first = 0
        bind x = x + 3.0
    }
    ink(70.0)
    draw_line(xph, H * 0.05, xph, H * 0.95)
    draw_line(xph + 6.0, H * 0.05, xph + 6.0, H * 0.95)
    # notes: four numbers each, kind, category, born, magnitude
    bind hist = Z[Z_HIST]
    bind i = 0
    while i < len(hist) {
        bind age = t - hist[i + 2]
        bind x = xph - age * (xph - x0) / 14.0
        bind c = hist[i + 1]
        bind y = lane_y(c, H)
        bind r = H * 0.011 + H * 0.014 * hist[i + 3]
        bind lit = 78.0 - age * 4.0
        if age < 0.4 { bind lit = 96.0 }
        col(c, sat, lit, hs)
        if hist[i] == 0 {
            draw_circle(x, y, r)
            if age < 0.9 { draw_circle(x, y, r * (1.0 + age * 3.0)) }
        } else {
            square(x, y, r, age * 0.4)
            if age < 0.9 { square(x, y, r * (1.0 + age * 2.0), age * 2.0) }
        }
        bind i = i + 4
    }
}

# ───────────────────────────── shared views ──────────────────────────────────
# Used by every section: a stack of Chesilin rings (any number of rings) and a scrolling
# seven-lane scope. The Memory views in 40_memory.ling keep their own copies of this.

bind D_HT = 6

fn fit_h(str, maxw, h) { return min(h, maxw / (max(str_len(str), 1) * 0.74)) }

fn ring_geom2(c, R, nr) {
    bind step = 0.7 / nr
    bind r1 = R * (1.0 - c * step)
    bind band = R * step * 0.82
    return [r1, r1 - band, band]
}

# nr rings, one per channel: fr[c] is how much of the ring is crosshatched, labels[c] runs round it.
# The hub shows `big` (Daemon) and `small`. Pulses (Z_EV, category = ring) ride the rings.
fn ring_stack(Z, nr, hm, labels, fr, big, small, bigk) {
    ring_stack_at(Z, Z[Z_W] / 2.0, Z[Z_H] / 2.0, min(Z[Z_W], Z[Z_H]) * 0.5 - 16.0, nr, hm, labels, fr, big, small, bigk)
}

# the same, centred on (cx, cy) with outer radius R
fn ring_stack_at(Z, cx, cy, R, nr, hm, labels, fr, big, small, bigk) {
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind en = Z[Z_ENERGY]
    bind c = 0
    while c < nr {
        bind gm = ring_geom2(c, R, nr)
        bind r1 = gm[0]
        bind r0 = gm[1]
        bind band = gm[2]
        bind frac = clamp(fr[c], 0.0, 1.0)
        bind spin = ring_spin(Z, c)
        bind e = en[c]
        col(hm[c], sat * 0.7, 26.0 + 14.0 * e, hs)
        draw_circle(cx, cy, r1)
        draw_circle(cx, cy, r0)
        bind sweep = max(frac * TAU, 0.03)
        bind da = 0.0215
        bind n = floor(sweep / da)
        bind k = 0
        col(hm[c], sat, 36.0 + 26.0 * e, hs)
        while k < n {
            bind a = spin + k * da
            pline(cx, cy, r0, a, r1, a + 0.05 + 0.05 * e)
            bind k = k + 1
        }
        if e > 0.35 {
            bind k = 0
            col(hm[c], sat, 30.0 + 22.0 * e, hs)
            while k < n {
                bind a = spin + k * da
                pline(cx, cy, r0, a + 0.05, r1, a)
                bind k = k + 2
            }
        }
        col(hm[c], sat, 62.0 + 28.0 * e, hs)
        pline(cx, cy, r0 - 4.0, spin, r1 + 4.0, spin)
        pline(cx, cy, r0 - 4.0, spin + sweep, r1 + 4.0, spin + sweep)
        ring_text(labels[c], cx, cy, r0 + band * 0.06, r1 - band * 0.06, spin * 0.6 + 1.0, 74.0 + 20.0 * e, hm[c], sat, hs)
        bind c = c + 1
    }
    bind hr = R * 0.3 - 6.0
    ink(34.0)
    draw_circle(cx, cy, hr)
    bind k = 0
    while k < 60 {
        bind a = k * TAU / 60.0 + Z[Z_ROT] * 0.5
        bind tl = 5.0
        if k % 5 == 0 { bind tl = 11.0 }
        pline(cx, cy, hr - tl, a, hr, a)
        bind k = k + 1
    }
    bind bh = fit_h(big, hr * 1.5, hr * 0.5)
    col(bigk, sat, 78.0, hs)
    daemon_text(big, cx - text_w(big, bh) / 2.0, cy - hr * 0.08, bh)
    bind sh = fit_h(small, hr * 1.5, hr * 0.2)
    ink(55.0)
    daemon_text(small, cx - text_w(small, sh) / 2.0, cy + hr * 0.4, sh)
    bind ev = Z[Z_EV]
    bind i = 0
    while i < len(ev) {
        bind cc = ev[i + 1]
        if cc < nr {
            bind age = t - ev[i + 2]
            bind p = age / 1.8
            bind gm = ring_geom2(cc, R, nr)
            bind rm = (gm[0] + gm[1]) / 2.0
            bind a = ring_spin(Z, cc) + ev[i + 3] * TAU
            bind x = px(cx, rm, a)
            bind y = py(cy, rm, a)
            col(hm[cc], sat, (1.0 - p) * (55.0 + 40.0 * ev[i + 4]), hs)
            if ev[i] == 0 {
                draw_circle(x, y, gm[2] * (0.25 + 1.5 * p))
                draw_circle(x, y, gm[2] * (0.1 + 0.9 * p))
            } else {
                square(x, y, gm[2] * (1.1 - 0.8 * p), a + p * 2.0)
                square(x, y, gm[2] * (0.6 - 0.4 * p), a - p * 2.0)
            }
        }
        bind i = i + 6
    }
}

# seven lanes, each a Daemon tile, a name, a current value and the last minute as a trace.
# ids are history strip numbers (hs_push), vals are the current readings as text.
# Notes (every pulse) are born at the playhead and scroll left at the trace's speed.
fn scope(Z, titles, letters, ids, vals) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind en = Z[Z_ENERGY]
    bind D = Z[Z_D]
    bind hst = D[D_HS]
    bind x0 = W * 0.22
    bind xph = W * 0.9
    bind dx = (xph - x0) / (HS_N - 1.0)
    bind sh = clamp((t - D[D_HT]) / 0.5, 0.0, 1.0) * dx
    bind amp = H * 0.05
    bind ts = H * 0.075
    bind c = 0
    while c < 7 {
        bind y = H * 0.15 + c * H * 0.118
        bind e = en[c]
        bind yb = y + amp
        bind ser = hst[ids[c]]
        col(c, sat * 0.6, 14.0, hs)
        draw_line(x0, yb, xph, yb)
        draw_line(x0, yb - amp * 2.0, xph, yb - amp * 2.0)
        bind k = 0
        bind ox = x0 - sh
        bind oy = yb - ser[0] * amp * 2.0
        while k < HS_N - 1 {
            bind nx = x0 + (k + 1) * dx - sh
            bind ny = yb - ser[k + 1] * amp * 2.0
            if ox >= x0 - 0.5 {
                col(c, sat * 0.8, 20.0 + 18.0 * ser[k + 1], hs)
                draw_line(nx, yb, nx, ny)
                col(c, sat, 44.0 + 36.0 * e, hs)
                draw_line(ox, oy, nx, ny)
            }
            bind ox = nx
            bind oy = ny
            bind k = k + 1
        }
        col(c, sat, 60.0 + 30.0 * e, hs)
        draw_line(ox, oy, xph, oy)
        tile(str_slice(letters, c, 1), W * 0.02, y - ts * 0.5, ts, 40.0 + 40.0 * e, c, sat, hs, 0.3 + e)
        col(c, sat, 68.0 + 20.0 * e, hs)
        daemon_text(titles[c], W * 0.02 + ts + 12.0, y - ts * 0.14, ts * 0.3)
        ink(62.0)
        daemon_text(vals[c], W * 0.02 + ts + 12.0, y + ts * 0.3, ts * 0.3)
        bind c = c + 1
    }
    ink(70.0)
    draw_line(xph, H * 0.04, xph, H * 0.97)
    draw_line(xph + 6.0, H * 0.04, xph + 6.0, H * 0.97)
    bind hist = Z[Z_HIST]
    bind i = 0
    while i < len(hist) {
        bind age = t - hist[i + 2]
        bind x = xph - age * dx * 2.0
        bind c = hist[i + 1]
        if c < 7 {
            if x > x0 {
                bind y = H * 0.15 + c * H * 0.118
                bind r = H * 0.009 + H * 0.012 * hist[i + 3]
                bind lit = 78.0 - age * 4.0
                if age < 0.4 { bind lit = 96.0 }
                col(c, sat, lit, hs)
                if hist[i] == 0 {
                    draw_circle(x, y, r)
                    if age < 0.9 { draw_circle(x, y, r * (1.0 + age * 3.0)) }
                } else {
                    square(x, y, r, age * 0.4)
                    if age < 0.9 { square(x, y, r * (1.0 + age * 2.0), age * 2.0) }
                }
            }
        }
        bind i = i + 4
    }
}

# the last n samples of a strip in a w x h box with its top-left at (x, y)
fn spark(x, y, w, h, ser, n, k, sat, lit, hs) {
    bind s0 = len(ser) - n
    bind dx = w / (n - 1.0)
    col(k, sat * 0.6, 18.0, hs)
    draw_line(x, y + h, x + w, y + h)
    bind i = 0
    while i < n - 1 {
        bind a = ser[s0 + i]
        bind b = ser[s0 + i + 1]
        col(k, sat * 0.8, 22.0 + 14.0 * b, hs)
        draw_line(x + (i + 1) * dx, y + h, x + (i + 1) * dx, y + h - b * h)
        col(k, sat, lit, hs)
        draw_line(x + i * dx, y + h - a * h, x + (i + 1) * dx, y + h - b * h)
        bind i = i + 1
    }
}

# a hatched bar: rail, then diagonal strokes for the filled part
fn hbar(x, y, w, frac, k, sat, lit, hs) {
    col(k, sat * 0.8, 38.0, hs)
    draw_line(x, y, x + w, y)
    draw_line(x, y - 6.0, x, y)
    draw_line(x + w, y - 6.0, x + w, y)
    bind fw = w * clamp(frac, 0.0, 1.0)
    col(k, sat, lit, hs)
    bind u = 0.0
    while u < fw {
        draw_line(x + u, y, x + u + 4.0, y - 6.0)
        bind u = u + 3.0
    }
}

fn bline(x0, y0, S, u0, v0, u1, v1) {
    draw_line(x0 + u0 * S, y0 + v0 * S, x0 + u1 * S, y0 + v1 * S)
}

# an axis-aligned rectangle in board units
fn brect(x0, y0, S, u0, v0, u1, v1) {
    bline(x0, y0, S, u0, v0, u1, v0)
    bline(x0, y0, S, u1, v0, u1, v1)
    bline(x0, y0, S, u1, v1, u0, v1)
    bline(x0, y0, S, u0, v1, u0, v0)
}

# diagonal hatch inside a board-unit rectangle; spacing in pixels
fn bhatch(x0, y0, S, u0, v0, u1, v1, sp) {
    bind w = (u1 - u0) * S
    bind h = (v1 - v0) * S
    bind xa = x0 + u0 * S
    bind ya = y0 + v0 * S
    bind d = sp
    while d < w + h {
        bind ax = xa
        bind ay = ya + d
        if d > h {
            bind ax = xa + d - h
            bind ay = ya + h
        }
        bind bx = xa + d
        bind by = ya
        if d > w {
            bind bx = xa + w
            bind by = ya + d - w
        }
        draw_line(ax, ay, bx, by)
        bind d = d + sp
    }
}

# ───────────────────────────── NETWORK section ───────────────────────────────
# Channels (rings, lanes, pulse categories): RX, TX, PKT IN, PKT OUT, TCP, UDP, RETRANS.
# Data: net.snap from sampler_hw.ps1 (interface counters, TCP/UDP counters, netstat, one probe of your gateway).
# Remote addresses are never drawn as text: a connection is a dot whose place comes from a hash of the
# address and whose sector is its remote port (HTTPS, HTTP, DNS, REMOTE, MAIL, HIGH, OTHER).

# an arc of a circle, a0 to a1 (radians, clockwise from up)
fn arc(cx, cy, r, a0, a1) {
    bind n = floor(abs(a1 - a0) * r / 6.0) + 1
    bind i = 0
    while i < n {
        pline(cx, cy, r, mix(a0, a1, i / n), r, mix(a0, a1, (i + 1) / n))
        bind i = i + 1
    }
}

# Chesilin text laid along an arc, centred on angle ac, radius rm
fn arc_text(str, cx, cy, rm, ac, h) {
    bind m = str_len(str)
    bind da = h * 1.25 / rm
    bind q = 0
    while q < m {
        bind ch = str_slice(str, q, 1)
        if ch != " " { chesilin_glyph(ch, cx, cy, rm, ac + (q - (m - 1) / 2.0) * da, h * 1.05, h) }
        bind q = q + 1
    }
}

fn adapter_name(np) {
    bind ads = np[NP_AD]
    if len(ads) == 0 { return "NO ADAPTER" }
    bind ad = ads[0]
    bind s = str_upper(ad[0])
    bind s = str_replace(s, "INTEL[R] ", "")
    bind s = str_replace(s, "INTEL(R) ", "")
    return s
}

fn node_xy(nd, i, cx, cy, R) {
    bind c = port_class(nd[1])
    bind sec = TAU / 7.0
    bind a = c * sec + sec * (0.1 + 0.8 * fract(nd[0] * 3.7 + 0.31)) + (fract(i * 0.37) - 0.5) * 0.015
    bind r = R * (0.4 + 0.47 * fract(nd[0] * 17.3 + nd[1] * 0.0137))
    return [px(cx, r, a), py(cy, r, a), a, r]
}

# ---- view 1: FLOW, a constellation of live connections
fn view_net_flow(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind cx = W / 2.0
    bind cy = H / 2.0
    bind R = min(W * 0.34, H * 0.5 - 20.0)
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind en = Z[Z_ENERGY]
    bind np = Z[Z_D][D_NET]
    bind nv = np[NP_NV]
    bind nodes = np[NP_NODE]
    bind sec = TAU / 7.0
    bind spin = Z[Z_ROT] * 0.02
    # sectors: rim arc, divider, name and count
    bind counts = [0, 0, 0, 0, 0, 0, 0]
    bind i = 0
    while i < len(nodes) {
        bind c = port_class(nodes[i][1])
        bind counts = list_set(counts, c, counts[c] + 1)
        bind i = i + 1
    }
    bind c = 0
    while c < 7 {
        bind e = en[c]
        bind a0 = c * sec + 0.02
        bind a1 = (c + 1) * sec - 0.02
        col(c, sat * 0.8, 36.0 + 24.0 * e, hs)
        arc(cx, cy, R, a0, a1)
        arc(cx, cy, R * 0.915, a0, a1)
        col(c, sat * 0.6, 16.0 + 10.0 * e, hs)
        pline(cx, cy, R * 0.17, c * sec, R * 0.915, c * sec)
        col(c, sat, 66.0 + 24.0 * e, hs)
        arc_text(PORT_NAME[c] + " " + num_str(counts[c]), cx, cy, R * 0.955, (c + 0.5) * sec, R * 0.05)
        bind c = c + 1
    }
    # spokes and dots
    bind pos = []
    bind i = 0
    while i < len(nodes) {
        bind nd = nodes[i]
        bind xy = node_xy(nd, i, cx, cy, R)
        bind pos = list_push(pos, xy[0])
        bind pos = list_push(pos, xy[1])
        bind k = port_class(nd[1])
        col(k, sat * 0.7, 14.0 + 8.0 * en[k], hs)
        pline(cx, cy, R * 0.17, xy[2], xy[3], xy[2])
        col(k, sat, 52.0 + 24.0 * en[k], hs)
        if nd[2] == 0 {
            draw_circle(xy[0], xy[1], 5.0)
            draw_circle(xy[0], xy[1], 2.0)
        } else {
            square(xy[0], xy[1], 4.0, t * 0.8 + i)
        }
        bind i = i + 1
    }
    # hub: this machine, rates in and out
    bind hr = R * 0.17
    col(2, sat, 70.0, hs)
    draw_circle(cx, cy, hr)
    square(cx, cy, hr * 0.7, Z[Z_ROT] * 0.5)
    square(cx, cy, hr * 0.7, Z[Z_ROT] * 0.5 + 0.7854)
    bind a1s = "IN " + fmt_bytes(nv[NV_RX])
    bind a2s = "OUT " + fmt_bytes(nv[NV_TX])
    bind hh = fit_h(a1s, hr * 1.5, hr * 0.3)
    col(0, sat, 80.0, hs)
    daemon_text(a1s, cx - text_w(a1s, hh) / 2.0, cy - hr * 0.2, hh)
    bind hh2 = fit_h(a2s, hr * 1.5, hr * 0.3)
    col(1, sat, 80.0, hs)
    daemon_text(a2s, cx - text_w(a2s, hh2) / 2.0, cy + hr * 0.22, hh2)
    # pulses: rx = circle travelling in, tx = square travelling out
    bind ev = Z[Z_EV]
    bind j = 0
    while j < len(ev) {
        bind ni = ev[j + 5]
        if ni < len(nodes) {
            bind p = (t - ev[j + 2]) / 1.8
            bind nd = nodes[ni]
            bind k = port_class(nd[1])
            bind xy = node_xy(nd, ni, cx, cy, R)
            bind q = p
            if ev[j] == 0 { bind q = 1.0 - p }
            bind hx = px(cx, hr, xy[2])
            bind hy = py(cy, hr, xy[2])
            bind x = mix(hx, xy[0], q)
            bind y = mix(hy, xy[1], q)
            col(k, sat, (1.0 - p * 0.6) * (60.0 + 38.0 * ev[j + 4]), hs)
            if ev[j] == 0 {
                draw_circle(x, y, 7.0)
                draw_circle(x, y, 3.0)
            } else {
                square(x, y, 6.0, p * 4.0)
            }
        }
        bind j = j + 6
    }
    # left: the link
    bind lx = 18.0
    bind ly = H * 0.18
    bind lh = 13.0
    bind rtt = nv[NV_RTT]
    bind rs = "---"
    if rtt > 0.0 { bind rs = num_str(round(rtt)) + "MS" }
    bind adn = str_slice(adapter_name(np), 0, 26)
    bind bw = nv[NV_BW]
    bind lines = [adn, "LINK " + fmt_bytes(bw) + "BIT", "IN  " + fmt_bytes(nv[NV_RX]) + "/S", "OUT " + fmt_bytes(nv[NV_TX]) + "/S",
                  "PKTS " + fmt_rate(nv[NV_PRX]) + " IN " + fmt_rate(nv[NV_PTX]) + " OUT", "GATEWAY " + rs, "RETRANS " + fmt_rate(nv[NV_RETX]) + "/S",
                  "TCP EST " + fmt_rate(nv[NV_EST]), "LISTEN " + fmt_rate(nv[NV_LISTEN]) + "  WAIT " + fmt_rate(nv[NV_TW]),
                  "CLOSE " + fmt_rate(nv[NV_CW]) + "  LOOP " + fmt_rate(nv[NV_LOOP])]
    tile("N", lx, ly - 54.0, 44.0, 66.0, 6, sat, hs, 0.6)
    bind i = 0
    while i < len(lines) {
        col(6, sat, 78.0 - 4.0 * i, hs)
        if i > 1 { ink(64.0 - 2.0 * i) }
        daemon_text(lines[i], lx, ly + 8.0 + i * 24.0, lh)
        bind i = i + 1
    }
    # right: who holds the connections
    bind rx0 = W - 18.0 - min(W * 0.22, 280.0)
    bind pw = min(W * 0.22, 280.0)
    col(6, sat, 70.0, hs)
    daemon_text("BUSIEST PROCESSES", rx0, H * 0.1, 13.0)
    bind pr = np[NP_PROC]
    bind top = 1.0
    if len(pr) > 0 { bind top = max(pr[0][1], 1.0) }
    bind i = 0
    while i < min(len(pr), 9) {
        bind pe = pr[i]
        bind yy = H * 0.1 + 32.0 + i * 44.0
        bind k = name_hue(pe[0])
        col(k, sat, 72.0, hs)
        daemon_text(str_slice(pe[0], 0, 14) + " " + num_str(pe[1]), rx0, yy, 12.0)
        hbar(rx0, yy + 16.0, pw, pe[1] / top, k, sat, 56.0, hs)
        bind i = i + 1
    }
}

# ---- view 2: LINKS, seven rings, one per channel
fn view_net_links(Z) {
    bind np = Z[Z_D][D_NET]
    bind nv = np[NP_NV]
    bind labels = ["RX " + fmt_bytes(nv[NV_RX]) + " PER S ", "TX " + fmt_bytes(nv[NV_TX]) + " PER S ", "PACKETS IN " + fmt_rate(nv[NV_PRX]) + " ",
                   "PACKETS OUT " + fmt_rate(nv[NV_PTX]) + " ", "TCP " + fmt_rate(nv[NV_EST]) + " LINKS ", "UDP " + fmt_rate(nv[NV_UDP]) + " PER S ",
                   "RETRANSMITS " + fmt_rate(nv[NV_RETX]) + " "]
    bind rtt = nv[NV_RTT]
    bind big = "---"
    if rtt > 0.0 { bind big = num_str(round(rtt)) + "MS" }
    ring_stack(Z, 7, IDM, labels, Z[Z_FRAC], big, str_slice(adapter_name(np), 0, 22), 6)
}

# ---- view 3: TRACES, the last minute of seven vital signs
fn view_net_traces(Z) {
    bind nv = Z[Z_D][D_NET][NP_NV]
    bind titles = ["RECEIVE", "TRANSMIT", "PACKETS IN", "PACKETS OUT", "TCP LINKS", "UDP", "GATEWAY"]
    bind rtt = nv[NV_RTT]
    bind rs = "---"
    if rtt > 0.0 { bind rs = num_str(round(rtt)) + "MS" }
    bind vals = [fmt_bytes(nv[NV_RX]) + "/S", fmt_bytes(nv[NV_TX]) + "/S", fmt_rate(nv[NV_PRX]) + "/S", fmt_rate(nv[NV_PTX]) + "/S",
                 fmt_rate(nv[NV_EST]), fmt_rate(nv[NV_UDP]) + "/S", rs]
    bind ids = [HS_NET, HS_NET + 1, HS_NET + 2, HS_NET + 3, HS_NET + 4, HS_NET + 5, HS_NET + 6]
    scope(Z, titles, "RTIOCUG", ids, vals)
}

# ───────────────────────────── LINGNET: the network, in detail ───────────────
# Data: netx.snap from sampler_hw.ps1 -Kind netx. "Device" means a network adapter on this PC: every view can be
# narrowed to one (Up / Down, or click a chip in the bar) or show all of them together.
# Windows keeps counters per adapter and per IP version, and a connection table that names the local address,
# so those are the things split by device. It does not count traffic for other machines on your network;
# the neighbours (ARP / NDP table) show who is there, not how much they send.

# pack NX: [good, ads, ms, addrs, gws, dnss, neigh, proto, conns, ks, dns, probes, t, vecs]
bind NX_AD = 1
bind NX_M = 2
bind NX_ADDR = 3
bind NX_GW = 4
bind NX_DNS = 5
bind NX_NB = 6
bind NX_PR = 7
bind NX_CN = 8
bind NX_K = 9
bind NX_DC = 10
bind NX_PQ = 11
bind NX_T = 12
bind NX_VEC = 13

# history strips: 7 for all devices, 7 for each of up to six, 7 for the protocols, 3 for DNS
bind HN_ALL = 0
bind HN_DEV = 7
bind HN_PROTO = 49
bind HN_DNS = 56
bind HN_COUNT = 59

bind FLEET_HUE = [3, 4, 1, 6, 0, 2, 5, 3]
bind NET_VIEWS = ["DEVICES", "FLOW", "PACKETS", "PROTOCOLS", "DNS", "ADDRESSES"]
bind LANE_NAME = ["BYTES IN", "BYTES OUT", "PACKETS IN", "PACKETS OUT", "MULTICAST IN", "MULTICAST OUT", "ERRORS"]
bind PROTO_NAME = ["IPV4", "IPV6", "TCP V4", "TCP V6", "UDP V4", "UDP V6", "ICMP"]
bind DNS_NAME = ["A", "AAAA", "CNAME", "PTR", "SRV", "OTHER", "NEGATIVE"]

fn pos_of(ifx, idx) {
    bind i = 0
    while i < len(ifx) {
        if ifx[i] == idx { return i }
        bind i = i + 1
    }
    return 0 - 1
}

fn parse_netx(text) {
    bind rows = snap_rows(text)
    bind ads = []
    bind ifx = []
    bind vecs = []
    bind ms = []
    bind addrs = []
    bind gws = []
    bind dnss = []
    bind neigh = []
    bind proto = zeros(12)
    bind conns = []
    bind k4 = zeros(7)
    bind k6 = zeros(7)
    bind dns = zeros(8)
    bind probes = []
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        if r[0] == "a" {
            bind ads = list_push(ads, [str_upper(sf(r, 2)), str_upper(sf(r, 3)), nf(r, 5)])
            bind ifx = list_push(ifx, nf(r, 1))
            bind vecs = list_push(vecs, [nf(r, 6), nf(r, 7), nf(r, 8), nf(r, 9), nf(r, 10), nf(r, 11), nf(r, 12)])
        }
        bind i = i + 1
    }
    bind i = 0
    while i < len(rows) {
        bind r = rows[i]
        bind tag = r[0]
        bind ps = pos_of(ifx, nf(r, 1))
        if tag == "m" {
            if ps >= 0 { bind ms = list_push(ms, [ps, nf(r, 2), nf(r, 3), nf(r, 4), nf(r, 5)]) }
        }
        if tag == "p" {
            if ps >= 0 { bind addrs = list_push(addrs, [ps, nf(r, 2), sf(r, 3), nf(r, 4), sf(r, 5), sf(r, 6)]) }
        }
        if tag == "g" {
            if ps >= 0 { bind gws = list_push(gws, [ps, nf(r, 2), sf(r, 3)]) }
        }
        if tag == "s" {
            if ps >= 0 { bind dnss = list_push(dnss, [ps, nf(r, 2), sf(r, 3)]) }
        }
        if tag == "n" {
            if ps >= 0 { bind neigh = list_push(neigh, [ps, nf(r, 2), nf(r, 3), nf(r, 4)]) }
        }
        if tag == "r" { bind proto = pad_to(nums(r, 1), 12) }
        if tag == "c" { bind conns = list_push(conns, [nf(r, 1), pos_of(ifx, nf(r, 2)), nf(r, 3), nf(r, 4), nf(r, 5), str_upper(sf(r, 6))]) }
        if tag == "k" {
            if nf(r, 1) == 4 { bind k4 = pad_to(nums(r, 2), 7) } else { bind k6 = pad_to(nums(r, 2), 7) }
        }
        if tag == "d" { bind dns = pad_to(nums(r, 1), 8) }
        if tag == "q" { bind probes = list_push(probes, [nf(r, 1), sf(r, 2), nf(r, 3), nf(r, 4), nf(r, 5)]) }
        bind i = i + 1
    }
    bind good = 0
    if len(ads) > 0 { bind good = 1 }
    return [good, ads, ms, addrs, gws, dnss, neigh, proto, conns, [k4, k6], dns, probes, snap_time(rows), vecs]
}

fn sim_netx(t) {
    bind b1 = pow(abs(sin(t * 0.21)), 5.0)
    bind b2 = pow(abs(sin(t * 0.33 + 1.0)), 6.0)
    bind ads = [["SIM WI-FI 7 ADAPTER", "WI-FI", 1000000000.0], ["SIM GIGABIT ETHERNET", "ETHERNET", 1000000000.0], ["SIM VPN TUNNEL", "VPN", 100000000.0]]
    bind vecs = [[40000.0 + 90000.0 * (1.0 + sin(t * 0.5)) + 40000000.0 * b1, 8000.0 + 6000000.0 * b2, 300.0 + 3000.0 * b1, 120.0 + 2000.0 * b2, 4.0, 2.0, 0.0],
                 [200000.0 * (1.0 + sin(t * 0.3)), 50000.0 * (1.0 + sin(t * 0.4)), 160.0 * (1.0 + sin(t * 0.3)), 60.0 * (1.0 + sin(t * 0.4)), 1.0, 1.0, 0.0],
                 [9000.0 * (1.0 + sin(t * 0.7)), 4000.0 * (1.0 + sin(t * 0.6)), 20.0, 10.0, 0.0, 0.0, 1.5 * (1.0 + sin(t * 0.2))]]
    bind ms = [[0, 4, 1, 1500, 35], [0, 6, 1, 1500, 35], [1, 4, 0, 1500, 25], [1, 6, 1, 1500, 25], [2, 4, 0, 1400, 50], [2, 6, 1, 1400, 50]]
    bind addrs = [[0, 4, "192.168.1.20", 24, "PRIVATE", "Dhcp"], [0, 6, "2001:db8:1::20", 64, "GLOBAL", "RouterAdvertisement"],
                  [0, 6, "fd12:3456:789a::20", 64, "ULA", "RouterAdvertisement"], [0, 6, "fe80::9f9b:b525:5f56:ada1", 64, "LINKLOCAL", "WellKnown"],
                  [1, 4, "10.0.0.5", 24, "PRIVATE", "Manual"], [1, 6, "fe80::dd9f:2a3a:24fb:369e", 64, "LINKLOCAL", "WellKnown"],
                  [2, 4, "100.64.0.2", 32, "CGNAT", "Manual"], [2, 6, "fd7a:115c:a1e0::2", 128, "ULA", "Manual"]]
    bind gws = [[0, 4, "192.168.1.1"], [0, 6, "fe80::1"], [1, 4, "10.0.0.1"]]
    bind dnss = [[0, 4, "1.1.1.1"], [0, 4, "9.9.9.9"], [0, 6, "2606:4700:4700::1111"], [1, 4, "10.0.0.1"]]
    bind neigh = []
    bind i = 0
    while i < 14 {
        bind vv = 4
        if i % 3 == 1 { bind vv = 6 }
        bind neigh = list_push(neigh, [i % 3, vv, fract(i * 0.618 + 0.2), i % 3])
        bind i = i + 1
    }
    bind ports = [443, 443, 443, 80, 53, 22, 8333, 25565, 443, 993, 443, 3389, 8080, 51820]
    bind conns = []
    bind i = 0
    while i < 40 {
        bind vv = 4
        if i % 4 == 0 { bind vv = 6 }
        bind conns = list_push(conns, [vv, i % 3, 0, fract(i * 0.618 + 0.1), ports[i % 14], str_upper(SIM_NAMES[(i * 7) % 20])])
        bind i = i + 1
    }
    bind proto = [450.0 + 300.0 * b1, 30.0 + 40.0 * b2, 140.0 + 90.0 * b1, 12.0 + 20.0 * b2, 3.0 + 40.0 * b2, 8.0 + 15.0 * b1, 2.0 + 6.0 * b2, 5.0, 87.0, 12.0, 3.0, 1.0]
    bind probes = [[4, "1.1.1.1", 12.0 + 4.0 * b2, 1, 1], [4, "9.9.9.9", 50.0, 1, 1], [6, "2606:4700:4700::1111", 18.0, 1, 1], [4, "1.1.1.1", 15.0, 1, 28]]
    return [1, ads, ms, addrs, gws, dnss, neigh, proto, conns, [[87.0, 108.0, 13.0, 6.0, 0.0, 0.0, 600.0], [12.0, 29.0, 0.0, 0.0, 0.0, 0.0, 2.0]], [103.0, 22.0, 6.0, 12.0, 0.0, 0.0, 11.0, 154.0], probes, 0.0, vecs]
}

# ───────────────────────────── small helpers ─────────────────────────────────
fn inr(mx, my, r) {
    if mx >= r[0] {
        if mx <= r[0] + r[2] {
            if my >= r[1] {
                if my <= r[1] + r[3] { return 1 }
            }
        }
    }
    return 0
}

fn dev_name(nx, i) {
    if i < 0 { return "ALL DEVICES" }
    return str_slice(nx[NX_AD][i][1], 0, 18)
}

# the seven counters of one device, or the sum of all of them
fn sel_vec(nx, sel) {
    bind vecs = nx[NX_VEC]
    if sel >= 0 {
        if sel < len(vecs) { return vecs[sel] }
    }
    bind s = zeros(7)
    bind i = 0
    while i < len(vecs) {
        bind j = 0
        while j < 7 {
            bind s = list_set(s, j, s[j] + vecs[i][j])
            bind j = j + 1
        }
        bind i = i + 1
    }
    return s
}

# connections of one device (or all), as [ver, pos, state, hash, port, proc]
fn sel_conns(nx, sel) {
    bind cn = nx[NX_CN]
    if sel < 0 { return cn }
    bind out = []
    bind i = 0
    while i < len(cn) {
        if cn[i][1] == sel { bind out = list_push(out, cn[i]) }
        bind i = i + 1
    }
    return out
}

fn scope_hue(sc) {
    if sc == "GLOBAL" { return 3 }
    if sc == "PUBLIC" { return 3 }
    if sc == "PRIVATE" { return 4 }
    if sc == "ULA" { return 5 }
    if sc == "LINKLOCAL" { return 1 }
    if sc == "CGNAT" { return 6 }
    if sc == "LOOPBACK" { return 0 }
    return 2
}

fn origin_name(o) {
    if o == "Dhcp" { return "DHCP" }
    if o == "RouterAdvertisement" { return "RA" }
    if o == "Manual" { return "STATIC" }
    if o == "WellKnown" { return "AUTO" }
    return str_upper(o)
}

fn fmt_speed(bps) { return fmt_bytes(bps) + "BIT" }

fn gw_of(nx, pos, vv) {
    bind g = nx[NX_GW]
    bind i = 0
    while i < len(g) {
        if g[i][0] == pos {
            if g[i][1] == vv { return g[i][2] }
        }
        bind i = i + 1
    }
    return "NONE"
}

fn count_conns(nx, pos, vv) {
    bind cn = nx[NX_CN]
    bind c = 0
    bind i = 0
    while i < len(cn) {
        if cn[i][1] == pos {
            if cn[i][0] == vv { bind c = c + 1 }
        }
        bind i = i + 1
    }
    return c
}

fn count_neigh(nx, pos, vv) {
    bind nb = nx[NX_NB]
    bind c = 0
    bind i = 0
    while i < len(nb) {
        if nb[i][0] == pos {
            if nb[i][1] == vv { bind c = c + 1 }
        }
        bind i = i + 1
    }
    return c
}

# the first address of a device in a family, shortened: "ADDRESS/PREFIX SCOPE"
fn first_addr(nx, pos, vv) {
    bind a = nx[NX_ADDR]
    bind best = "NONE"
    bind i = 0
    while i < len(a) {
        if a[i][0] == pos {
            if a[i][1] == vv {
                if best == "NONE" { bind best = str_slice(a[i][2], 0, 26) + "/" + num_str(a[i][3]) + " " + a[i][4] }
                if a[i][4] == "GLOBAL" { bind best = str_slice(a[i][2], 0, 26) + "/" + num_str(a[i][3]) + " " + a[i][4] }
            }
        }
        bind i = i + 1
    }
    return best
}

# busiest processes among a list of connections: [names, counts], at most m
fn top_procs(cn, m) {
    bind names = []
    bind counts = []
    bind i = 0
    while i < len(cn) {
        bind nm = cn[i][5]
        bind found = 0 - 1
        bind j = 0
        while j < len(names) {
            if names[j] == nm { bind found = j }
            bind j = j + 1
        }
        if found >= 0 {
            bind counts = list_set(counts, found, counts[found] + 1)
        } else {
            bind names = list_push(names, nm)
            bind counts = list_push(counts, 1)
        }
        bind i = i + 1
    }
    bind on = []
    bind oc = []
    bind k = 0
    while k < m {
        bind bi = 0 - 1
        bind bv = 0
        bind j = 0
        while j < len(names) {
            if counts[j] > bv {
                bind bv = counts[j]
                bind bi = j
            }
            bind j = j + 1
        }
        if bi >= 0 {
            bind on = list_push(on, names[bi])
            bind oc = list_push(oc, bv)
            bind counts = list_set(counts, bi, 0)
        }
        bind k = k + 1
    }
    return [on, oc]
}

fn hn_new() {
    bind hs = []
    bind i = 0
    while i < HN_COUNT {
        bind hs = list_push(hs, zeros(HS_N))
        bind i = i + 1
    }
    return hs
}

# the seven lane values of a counter vector, each 0..1
fn lane_fracs(v) {
    return [lg(v[0], 9.0), lg(v[1], 9.0), lg(v[2], 6.0), lg(v[3], 6.0), lg(v[4], 5.0), lg(v[5], 5.0), lg(v[6], 2.0)]
}

fn hn_sample(hs, nx) {
    bind lf = lane_fracs(sel_vec(nx, 0 - 1))
    bind i = 0
    while i < 7 {
        bind hs = hs_push(hs, HN_ALL + i, lf[i])
        bind i = i + 1
    }
    bind d = 0
    while d < min(len(nx[NX_VEC]), 6) {
        bind lf = lane_fracs(nx[NX_VEC][d])
        bind i = 0
        while i < 7 {
            bind hs = hs_push(hs, HN_DEV + d * 7 + i, lf[i])
            bind i = i + 1
        }
        bind d = d + 1
    }
    bind p = nx[NX_PR]
    bind pf = [lg(p[0], 4.5), lg(p[1], 4.5), lg(p[2], 4.5), lg(p[3], 4.5), lg(p[4], 4.0), lg(p[5], 4.0), lg(p[6] + p[7], 3.0)]
    bind i = 0
    while i < 7 {
        bind hs = hs_push(hs, HN_PROTO + i, pf[i])
        bind i = i + 1
    }
    bind pq = nx[NX_PQ]
    bind a4 = 0.0
    bind a6 = 0.0
    bind i = 0
    while i < len(pq) {
        if pq[i][3] > 0.5 {
            if pq[i][4] == 1 {
                if a4 <= 0.0 { bind a4 = pq[i][2] / 300.0 }
            } else {
                if a6 <= 0.0 { bind a6 = pq[i][2] / 300.0 }
            }
        }
        bind i = i + 1
    }
    bind hs = hs_push(hs, HN_DNS, a4)
    bind hs = hs_push(hs, HN_DNS + 1, a6)
    bind hs = hs_push(hs, HN_DNS + 2, lg(nx[NX_DC][7], 3.0))
    return hs
}

# ───────────────────────────── view 1: DEVICES ───────────────────────────────
fn dev_rect(i, n, W, H) {
    bind cols = n
    if n > 2 { bind cols = 3 }
    if n == 4 { bind cols = 2 }
    bind rows = ceil(n / cols)
    bind gap = 14.0
    bind cw = (W - 36.0 - (cols - 1) * gap) / cols
    bind ch = (H - 82.0 - (rows - 1) * gap) / rows
    return [18.0 + (i % cols) * (cw + gap), 70.0 + floor(i / cols) * (ch + gap), cw, ch]
}

fn dev_hit(mx, my, W, H, n) {
    bind i = 0
    while i < n {
        if inr(mx, my, dev_rect(i, n, W, H)) == 1 { return i }
        bind i = i + 1
    }
    return 0 - 1
}

fn view_net_devices(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind D = Z[Z_D]
    bind nx = D[0]
    bind sel = D[1]
    bind hst = D[D_HS]
    bind n = min(len(nx[NX_AD]), 6)
    bind hov = dev_hit(Z[Z_MX], Z[Z_MY], W, H, n)
    bind rows = ceil(n / max(min(n, 3), 1))
    if n == 4 { bind rows = 2 }
    bind g = 0
    while g < n {
        bind ad = nx[NX_AD][g]
        bind v = nx[NX_VEC][g]
        bind r = dev_rect(g, n, W, H)
        bind x = r[0]
        bind y = r[1]
        bind cw = r[2]
        bind ch = r[3]
        bind k = FLEET_HUE[g % 8]
        bind on = 0
        if g == sel { bind on = 1 }
        bind lit = 30.0 + 40.0 * on
        if g == hov { bind lit = lit + 14.0 }
        col(k, sat * 0.7, lit, hs)
        brect(x, y, 1.0, 0.0, 0.0, cw, ch)
        if on == 1 {
            bind a = 14.0
            col(k, sat, 84.0, hs)
            bline(x, y, 1.0, 0.0, a, 0.0, 0.0)
            bline(x, y, 1.0, 0.0, 0.0, a, 0.0)
            bline(x, y, 1.0, cw - a, 0.0, cw, 0.0)
            bline(x, y, 1.0, cw, 0.0, cw, a)
            bline(x, y, 1.0, cw, ch - a, cw, ch)
            bline(x, y, 1.0, cw, ch, cw - a, ch)
            bline(x, y, 1.0, a, ch, 0.0, ch)
            bline(x, y, 1.0, 0.0, ch, 0.0, ch - a)
        }
        tile(num_str(g), x + 10.0, y + 10.0, 38.0, 54.0 + 28.0 * on, k, sat, hs, 0.5 + on)
        bind tw = (cw - 70.0) / 0.74
        col(k, sat, 80.0 + 10.0 * on, hs)
        daemon_text(str_slice(ad[1], 0, 20), x + 58.0, y + 24.0, min(15.0, (cw - 70.0) / (0.74 * 14.0)))
        ink(50.0)
        daemon_text(str_slice(ad[0], 0, 30), x + 58.0, y + 44.0, min(10.0, (cw - 70.0) / (0.74 * 26.0)))
        # traffic in and out
        bind lw = cw - 24.0
        col(4, sat, 82.0, hs)
        daemon_text("IN " + fmt_bytes(v[0]) + "/S", x + 12.0, y + 84.0, 15.0)
        hbar(x + 12.0, y + 106.0, lw, lg(v[0], 9.0), 4, sat, 58.0, hs)
        col(1, sat, 82.0, hs)
        daemon_text("OUT " + fmt_bytes(v[1]) + "/S", x + 12.0, y + 126.0, 15.0)
        hbar(x + 12.0, y + 148.0, lw, lg(v[1], 9.0), 1, sat, 58.0, hs)
        bind pe = lg(v[6], 2.0)
        bind lines = ["LINK " + fmt_speed(ad[2]) + "  PKTS " + fmt_rate(v[2]) + " IN " + fmt_rate(v[3]) + " OUT",
                      "MULTICAST " + fmt_rate(v[4]) + " IN " + fmt_rate(v[5]) + " OUT  ERR " + fmt_rate(v[6]),
                      "V4 " + first_addr(nx, g, 4), "V6 " + first_addr(nx, g, 6),
                      "GW " + gw_of(nx, g, 4) + "  V6 GW " + str_slice(gw_of(nx, g, 6), 0, 12),
                      "TCP " + num_str(count_conns(nx, g, 4)) + " V4  " + num_str(count_conns(nx, g, 6)) + " V6   NEIGHBOURS " + num_str(count_neigh(nx, g, 4)) + "+" + num_str(count_neigh(nx, g, 6))]
        bind lh = min(12.0, lw / (0.74 * 38.0))
        bind q = 0
        while q < len(lines) {
            ink(66.0 - 3.0 * q)
            if q == 1 {
                if pe > 0.05 { col(0, sat, 80.0, hs) }
            }
            daemon_text(lines[q], x + 12.0, y + 184.0 + q * 24.0, lh)
            bind q = q + 1
        }
        if rows == 1 {
            ink(46.0)
            daemon_text("BYTES IN, LAST 50 S", x + 12.0, y + ch - 66.0, 10.0)
            spark(x + 12.0, y + ch - 56.0, lw, 44.0, hst[HN_DEV + g * 7], 100, k, sat, 64.0, hs)
        }
        bind g = g + 1
    }
}

# ───────────────────────────── view 2: FLOW ──────────────────────────────────
# the connections of the chosen device, in sectors by remote port; circle = IPv4, diamond = IPv6
fn nx_node_xy(nd, i, cx, cy, R) {
    return node_xy([nd[3], nd[4]], i, cx, cy, R)
}

fn view_net_flow2(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind cx = W / 2.0
    bind cy = H / 2.0
    bind R = min(W * 0.31, H * 0.5 - 16.0)
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind en = Z[Z_ENERGY]
    bind D = Z[Z_D]
    bind nx = D[0]
    bind sel = D[1]
    bind nodes = sel_conns(nx, sel)
    bind v = sel_vec(nx, sel)
    bind sec = TAU / 7.0
    bind counts = [0, 0, 0, 0, 0, 0, 0]
    bind n4 = 0
    bind n6 = 0
    bind i = 0
    while i < len(nodes) {
        bind c = port_class(nodes[i][4])
        bind counts = list_set(counts, c, counts[c] + 1)
        if nodes[i][0] == 6 { bind n6 = n6 + 1 } else { bind n4 = n4 + 1 }
        bind i = i + 1
    }
    bind c = 0
    while c < 7 {
        bind a0 = c * sec + 0.02
        bind a1 = (c + 1) * sec - 0.02
        col(c, sat * 0.8, 36.0 + 24.0 * en[c], hs)
        arc(cx, cy, R, a0, a1)
        arc(cx, cy, R * 0.915, a0, a1)
        col(c, sat * 0.6, 16.0, hs)
        pline(cx, cy, R * 0.17, c * sec, R * 0.915, c * sec)
        col(c, sat, 66.0, hs)
        arc_text(PORT_NAME[c] + " " + num_str(counts[c]), cx, cy, R * 0.955, (c + 0.5) * sec, R * 0.05)
        bind c = c + 1
    }
    bind i = 0
    while i < len(nodes) {
        bind nd = nodes[i]
        bind xy = nx_node_xy(nd, i, cx, cy, R)
        bind k = port_class(nd[4])
        col(k, sat * 0.7, 14.0, hs)
        pline(cx, cy, R * 0.17, xy[2], xy[3], xy[2])
        col(k, sat, 56.0, hs)
        if nd[0] == 6 {
            square(xy[0], xy[1], 4.5, 0.7854)
            draw_circle(xy[0], xy[1], 1.5)
        } else {
            draw_circle(xy[0], xy[1], 5.0)
            draw_circle(xy[0], xy[1], 2.0)
        }
        bind i = i + 1
    }
    bind hr = R * 0.17
    col(2, sat, 70.0, hs)
    draw_circle(cx, cy, hr)
    square(cx, cy, hr * 0.7, Z[Z_ROT] * 0.5)
    square(cx, cy, hr * 0.7, Z[Z_ROT] * 0.5 + 0.7854)
    bind s1 = "IN " + fmt_bytes(v[0])
    bind s2 = "OUT " + fmt_bytes(v[1])
    col(0, sat, 80.0, hs)
    bind h1 = fit_h(s1, hr * 1.5, hr * 0.3)
    daemon_text(s1, cx - text_w(s1, h1) / 2.0, cy - hr * 0.2, h1)
    col(1, sat, 80.0, hs)
    bind h2 = fit_h(s2, hr * 1.5, hr * 0.3)
    daemon_text(s2, cx - text_w(s2, h2) / 2.0, cy + hr * 0.22, h2)
    # pulses ride the spokes: in = circle travelling to the hub, out = square travelling out
    bind ev = Z[Z_EV]
    bind j = 0
    while j < len(ev) {
        bind ni = ev[j + 5]
        if ni < len(nodes) {
            bind p = (t - ev[j + 2]) / 1.8
            bind nd = nodes[ni]
            bind k = port_class(nd[4])
            bind xy = nx_node_xy(nd, ni, cx, cy, R)
            bind q = p
            if ev[j] == 0 { bind q = 1.0 - p }
            bind x = mix(px(cx, hr, xy[2]), xy[0], q)
            bind y = mix(py(cy, hr, xy[2]), xy[1], q)
            col(k, sat, (1.0 - p * 0.6) * (60.0 + 38.0 * ev[j + 4]), hs)
            if ev[j] == 0 { draw_circle(x, y, 6.0) } else { square(x, y, 5.0, p * 4.0) }
        }
        bind j = j + 6
    }
    # left: who and how many
    bind lx = 18.0
    bind ly = 112.0
    tile("D", lx, ly - 54.0, 44.0, 66.0, 6, sat, hs, 0.6)
    col(6, sat, 82.0, hs)
    daemon_text(dev_name(nx, sel), lx + 54.0, ly - 30.0, 13.0)
    bind lines = ["CONNECTIONS " + num_str(len(nodes)), "IPV4 " + num_str(n4) + "  IPV6 " + num_str(n6), "IN " + fmt_bytes(v[0]) + "/S", "OUT " + fmt_bytes(v[1]) + "/S",
                  "PKTS " + fmt_rate(v[2]) + " IN " + fmt_rate(v[3]) + " OUT"]
    bind q = 0
    while q < len(lines) {
        ink(70.0 - 4.0 * q)
        daemon_text(lines[q], lx, ly + 16.0 + q * 24.0, 13.0)
        bind q = q + 1
    }
    ink(52.0)
    daemon_text("CIRCLE IPV4", lx, ly + 160.0, 11.0)
    daemon_text("DIAMOND IPV6", lx, ly + 180.0, 11.0)
    bind rx0 = W - 18.0 - min(W * 0.22, 280.0)
    bind pw = min(W * 0.22, 280.0)
    col(6, sat, 70.0, hs)
    daemon_text("BUSIEST PROCESSES", rx0, 112.0, 13.0)
    bind tp = top_procs(nodes, 9)
    bind top = 1.0
    if len(tp[1]) > 0 { bind top = max(tp[1][0], 1.0) }
    bind i = 0
    while i < len(tp[0]) {
        bind yy = 144.0 + i * 44.0
        bind k = name_hue(tp[0][i])
        col(k, sat, 72.0, hs)
        daemon_text(str_slice(tp[0][i], 0, 14) + " " + num_str(tp[1][i]), rx0, yy, 12.0)
        hbar(rx0, yy + 16.0, pw, tp[1][i] / top, k, sat, 56.0, hs)
        bind i = i + 1
    }
}

# ───────────────────────────── view 3: PACKETS ───────────────────────────────
fn view_net_packets(Z) {
    bind D = Z[Z_D]
    bind nx = D[0]
    bind sel = D[1]
    bind v = sel_vec(nx, sel)
    bind base = HN_ALL
    if sel >= 0 { bind base = HN_DEV + sel * 7 }
    bind vals = [fmt_bytes(v[0]) + "/S", fmt_bytes(v[1]) + "/S", fmt_rate(v[2]) + "/S", fmt_rate(v[3]) + "/S", fmt_rate(v[4]) + "/S", fmt_rate(v[5]) + "/S", fmt_rate(v[6]) + "/S"]
    bind ids = [base, base + 1, base + 2, base + 3, base + 4, base + 5, base + 6]
    scope(Z, LANE_NAME, "RTIOMNE", ids, vals)
}

# ───────────────────────────── view 4: PROTOCOLS ─────────────────────────────
fn proto_panel(Z, x, y, w, vv, label, kc, est, pk) {
    bind sat = Z[Z_SAT]
    bind hs = Z[Z_HS]
    bind k = 4
    if vv == 6 { bind k = 5 }
    tile(num_str(vv), x, y, 44.0, 66.0, k, sat, hs, 0.6)
    col(k, sat, 84.0, hs)
    daemon_text(label, x + 56.0, y + 16.0, 16.0)
    ink(56.0)
    daemon_text(fmt_rate(pk) + " PACKETS/S", x + 56.0, y + 38.0, 11.0)
    bind lines = ["ESTABLISHED " + fmt_rate(kc[0]), "LISTENING " + fmt_rate(kc[1]), "TIME WAIT " + fmt_rate(kc[2]), "CLOSE WAIT " + fmt_rate(kc[3]), "SYN SENT " + fmt_rate(kc[4]), "OTHER " + fmt_rate(kc[5]), "LOOPBACK " + fmt_rate(kc[6])]
    bind q = 0
    while q < len(lines) {
        ink(68.0 - 3.0 * q)
        daemon_text(lines[q], x, y + 90.0 + q * 24.0, 13.0)
        hbar(x, y + 100.0 + q * 24.0, w, lg(kc[q], 3.0), k, sat, 52.0, hs)
        bind q = q + 1
    }
}

fn view_net_protocols(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind D = Z[Z_D]
    bind nx = D[0]
    bind p = nx[NX_PR]
    bind ks = nx[NX_K]
    bind total = p[0] + p[1]
    bind v6pct = 0.0
    if total > 0.0 { bind v6pct = p[1] / total * 100.0 }
    bind labels = []
    bind pv = [p[0], p[1], p[2], p[3], p[4], p[5], p[6] + p[7]]
    bind q = 0
    while q < 7 {
        bind labels = list_push(labels, PROTO_NAME[q] + " " + fmt_rate(pv[q]) + " PER S ")
        bind q = q + 1
    }
    bind R = min(W * 0.3, H * 0.5 - 16.0)
    ring_stack_at(Z, W / 2.0, H / 2.0, R, 7, IDM, labels, Z[Z_FRAC], pct(v6pct) + "%", "OF IP IS V6", 5)
    bind pw = min(W * 0.2, 250.0)
    proto_panel(Z, 18.0, 90.0, pw, 4, "IPV4", ks[0], p[8], p[0])
    proto_panel(Z, W - 18.0 - pw, 90.0, pw, 6, "IPV6", ks[1], p[9], p[1])
    ink(46.0)
    daemon_text("ALL DEVICES, COUNTED PER PROTOCOL BY WINDOWS", 18.0, H - 24.0, 11.0)
}

# ───────────────────────────── view 5: DNS ───────────────────────────────────
fn probe_for(nx, server, qtype) {
    bind pq = nx[NX_PQ]
    bind i = 0
    while i < len(pq) {
        if pq[i][1] == server {
            if pq[i][4] == qtype { return pq[i] }
        }
        bind i = i + 1
    }
    return []
}

fn view_net_dns(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind D = Z[Z_D]
    bind nx = D[0]
    bind sel = D[1]
    bind hst = D[D_HS]
    bind dc = nx[NX_DC]
    bind labels = []
    bind q = 0
    while q < 7 {
        bind labels = list_push(labels, DNS_NAME[q] + " " + fmt_rate(dc[q]) + " ")
        bind q = q + 1
    }
    bind R = min(W * 0.3, H * 0.5 - 16.0)
    ring_stack_at(Z, W / 2.0, H / 2.0, R, 7, IDM, labels, Z[Z_FRAC], fmt_rate(dc[7]), "IN CACHE", 6)
    # left: the servers this device uses, asked directly
    bind lx = 18.0
    bind lw = min(W * 0.24, 300.0)
    col(6, sat, 80.0, hs)
    daemon_text("DNS SERVERS", lx, 92.0, 14.0)
    ink(52.0)
    daemon_text(dev_name(nx, sel), lx, 112.0, 10.0)
    bind dn = nx[NX_DNS]
    bind row = 0
    bind i = 0
    while i < len(dn) {
        if sel < 0 || dn[i][0] == sel {
            if row < 5 {
                bind sv = dn[i][2]
                bind pr = probe_for(nx, sv, 1)
                bind vv = dn[i][1]
                bind k = 4
                if vv == 6 { bind k = 5 }
                bind yy = 150.0 + row * 54.0
                col(k, sat, 78.0, hs)
                daemon_text("V" + num_str(vv) + " " + str_slice(sv, 0, 24), lx, yy, min(12.0, lw / (0.74 * 28.0)))
                bind msg = "NOT PROBED"
                bind frac = 0.0
                if len(pr) > 0 {
                    if pr[3] > 0.5 {
                        bind msg = num_str(round(pr[2])) + " MS"
                        bind frac = clamp(pr[2] / 200.0, 0.0, 1.0)
                    } else {
                        bind msg = "NO ANSWER"
                    }
                }
                if msg == "NO ANSWER" { col(0, sat, 80.0, hs) } else { ink(62.0) }
                daemon_text(msg, lx, yy + 18.0, 11.0)
                hbar(lx, yy + 38.0, lw, frac, k, sat, 56.0, hs)
                bind row = row + 1
            }
        }
        bind i = i + 1
    }
    if row == 0 {
        ink(52.0)
        daemon_text("NO DNS SERVERS", lx, 150.0, 12.0)
    }
    # right: what the answers look like
    bind rx0 = W - 18.0 - min(W * 0.24, 300.0)
    bind pq = nx[NX_PQ]
    bind ok6 = 0
    bind aaaa = 0.0 - 1.0
    bind i = 0
    while i < len(pq) {
        if pq[i][4] == 28 {
            if pq[i][3] > 0.5 { bind aaaa = pq[i][2] }
        }
        if pq[i][0] == 6 {
            if pq[i][3] > 0.5 { bind ok6 = ok6 + 1 }
        }
        bind i = i + 1
    }
    bind v6s = 0
    bind i = 0
    while i < len(dn) {
        if dn[i][1] == 6 { bind v6s = v6s + 1 }
        bind i = i + 1
    }
    bind aq = "AAAA ANSWERED " + num_str(round(aaaa)) + " MS"
    if aaaa < 0.0 { bind aq = "AAAA NOT ANSWERED" }
    bind real = dc[0] + dc[1] + dc[2] + dc[3] + dc[4] + dc[5]
    bind v6share = 0.0
    if dc[0] + dc[1] > 0.0 { bind v6share = dc[1] / (dc[0] + dc[1]) * 100.0 }
    bind facts = ["CACHE " + num_str(dc[7]) + " ENTRIES", "ANSWERS " + num_str(real) + "  NEGATIVE " + num_str(dc[6]), "AAAA OF ADDRESSES " + pct(v6share) + "%", "V6 SERVERS " + num_str(v6s) + "  ANSWERING " + num_str(ok6), aq]
    col(6, sat, 80.0, hs)
    daemon_text("RESOLVER", rx0, 92.0, 14.0)
    bind q = 0
    while q < len(facts) {
        ink(68.0 - 3.0 * q)
        daemon_text(facts[q], rx0, 126.0 + q * 24.0, 12.0)
        bind q = q + 1
    }
    ink(46.0)
    daemon_text("FIRST SERVER LATENCY, LAST 50 S", rx0, 276.0, 10.0)
    spark(rx0, 286.0, min(W * 0.24, 300.0), 60.0, hst[HN_DNS], 100, 4, sat, 64.0, hs)
    ink(46.0)
    daemon_text("PROBES GO STRAIGHT TO EACH SERVER FOR DNS.MSFTNCSI.COM; NAMES IN YOUR CACHE ARE COUNTED, NEVER SHOWN", 18.0, H - 24.0, 10.0)
}

# ───────────────────────────── view 6: ADDRESSES ─────────────────────────────
fn view_net_addresses(Z) {
    bind W = Z[Z_W]
    bind H = Z[Z_H]
    bind t = Z[Z_T]
    bind hs = Z[Z_HS]
    bind sat = Z[Z_SAT]
    bind D = Z[Z_D]
    bind nx = D[0]
    bind sel = D[1]
    bind ad = nx[NX_ADDR]
    bind lx = 18.0
    bind lw = W * 0.55
    bind y = 92.0
    bind d = 0
    while d < len(nx[NX_AD]) {
        if sel < 0 || d == sel {
            bind k = FLEET_HUE[d % 8]
            col(k, sat, 82.0, hs)
            daemon_text(str_slice(nx[NX_AD][d][1], 0, 24) + "  " + fmt_speed(nx[NX_AD][d][2]), lx, y, 13.0)
            bind y = y + 22.0
            bind i = 0
            while i < len(ad) {
                if ad[i][0] == d {
                    bind sc = scope_hue(ad[i][4])
                    ink(66.0)
                    daemon_text("V" + num_str(ad[i][1]) + " " + str_slice(ad[i][2], 0, 30) + "/" + num_str(ad[i][3]), lx + 12.0, y, min(12.0, lw / (0.74 * 44.0)))
                    col(sc, sat, 80.0, hs)
                    daemon_text(ad[i][4], lx + lw * 0.62, y, 11.0)
                    ink(50.0)
                    daemon_text(origin_name(ad[i][5]), lx + lw * 0.62 + 100.0, y, 10.0)
                    bind y = y + 20.0
                }
                bind i = i + 1
            }
            bind ms = nx[NX_M]
            bind mtu = 0
            bind dhcp4 = 0
            bind dhcp6 = 0
            bind i = 0
            while i < len(ms) {
                if ms[i][0] == d {
                    bind mtu = ms[i][3]
                    if ms[i][1] == 4 { bind dhcp4 = ms[i][2] } else { bind dhcp6 = ms[i][2] }
                }
                bind i = i + 1
            }
            ink(56.0)
            daemon_text("GW " + gw_of(nx, d, 4) + "   V6 GW " + str_slice(gw_of(nx, d, 6), 0, 22), lx + 12.0, y, 11.0)
            bind y = y + 18.0
            bind dn = ""
            bind i = 0
            while i < len(nx[NX_DNS]) {
                if nx[NX_DNS][i][0] == d { bind dn = dn + str_slice(nx[NX_DNS][i][2], 0, 18) + "  " }
                bind i = i + 1
            }
            if str_len(dn) == 0 { bind dn = "NONE" }
            daemon_text("DNS " + str_slice(dn, 0, 64), lx + 12.0, y, 11.0)
            bind y = y + 18.0
            daemon_text("MTU " + num_str(mtu) + "   DHCP V4 " + yes_no(dhcp4) + " V6 " + yes_no(dhcp6), lx + 12.0, y, 11.0)
            bind y = y + 32.0
        }
        bind d = d + 1
    }
    # right: the neighbours (ARP and NDP tables): outer ring IPv4, inner ring IPv6
    bind cx = W - 18.0 - min(W * 0.2, H * 0.4)
    bind cy = H * 0.5
    bind R = min(W * 0.2, H * 0.4) - 6.0
    col(4, sat * 0.8, 40.0, hs)
    draw_circle(cx, cy, R)
    col(5, sat * 0.8, 40.0, hs)
    draw_circle(cx, cy, R * 0.62)
    ink(20.0)
    draw_circle(cx, cy, R * 0.25)
    bind nb = nx[NX_NB]
    bind c4 = 0
    bind c6 = 0
    bind i = 0
    while i < len(nb) {
        bind nd = nb[i]
        if sel < 0 || nd[0] == sel {
            bind a = nd[2] * TAU + Z[Z_ROT] * 0.04
            bind rr = R
            bind k = 4
            if nd[1] == 6 {
                bind rr = R * 0.62
                bind k = 5
                bind c6 = c6 + 1
            } else {
                bind c4 = c4 + 1
            }
            bind sk = 3
            if nd[3] == 1 { bind sk = 1 }
            if nd[3] == 2 { bind sk = 0 }
            bind x = px(cx, rr, a)
            bind yy = py(cy, rr, a)
            col(sk, sat, 66.0, hs)
            draw_circle(x, yy, 6.0)
            col(k, sat, 78.0, hs)
            draw_circle(x, yy, 2.5)
        }
        bind i = i + 1
    }
    bind big = num_str(c4 + c6)
    col(6, sat, 82.0, hs)
    bind bh = fit_h(big, R * 0.4, R * 0.2)
    daemon_text(big, cx - text_w(big, bh) / 2.0, cy - 4.0, bh)
    ink(56.0)
    daemon_text("NEIGHBOURS", cx - text_w("NEIGHBOURS", 10.0) / 2.0, cy + R * 0.14, 10.0)
    ink(56.0)
    daemon_text("OUTER V4 " + num_str(c4) + "  INNER V6 " + num_str(c6), cx - R, cy + R + 22.0, 11.0)
    col(3, sat, 70.0, hs)
    daemon_text("GREEN REACHABLE", cx - R, cy + R + 42.0, 10.0)
    col(1, sat, 70.0, hs)
    daemon_text("ORANGE STALE", cx - R + 150.0, cy + R + 42.0, 10.0)
    col(0, sat, 70.0, hs)
    daemon_text("RED NO ANSWER", cx - R + 270.0, cy + R + 42.0, 10.0)
}

fn yes_no(x) {
    if x > 0 { return "ON" }
    return "OFF"
}

# ───────────────────────────── LINGNET main ──────────────────────────────────
#   ling run --interp hatchnet.ling [WxH] [full] [sim] [shot]
# Keys: TAB / LEFT / RIGHT view, 1-6 view, UP / DOWN device (ALL, then each adapter), H hud, Q or ESC quit.
# The bar along the bottom is clickable: views on the left, devices on the right. On DEVICES, click a card.

# rects of the six view tabs
fn nbar_tabs(W, FH) {
    bind out = []
    bind x = 18.0
    bind j = 0
    while j < 6 {
        bind w = text_w(NET_VIEWS[j], 12.0) + 28.0
        bind out = list_push(out, [x, FH - 62.0, w, 26.0])
        bind x = x + w + 8.0
        bind j = j + 1
    }
    return out
}

# rects of the device chips: ALL first, then each adapter
fn nbar_chips(labels, W, FH) {
    bind tabs = nbar_tabs(W, FH)
    bind last = tabs[5]
    bind x = last[0] + last[2] + 26.0
    bind out = []
    bind i = 0
    while i < len(labels) {
        bind w = text_w(labels[i], 11.0) + 22.0
        bind out = list_push(out, [x, FH - 60.0, w, 22.0])
        bind x = x + w + 6.0
        bind i = i + 1
    }
    return out
}

# 0..5 a view tab, 10 + k device chip k (0 = ALL), -1 nothing
fn nbar_hit(mx, my, W, FH, labels) {
    bind tabs = nbar_tabs(W, FH)
    bind j = 0
    while j < 6 {
        if inr(mx, my, tabs[j]) == 1 { return j }
        bind j = j + 1
    }
    bind chips = nbar_chips(labels, W, FH)
    bind k = 0
    while k < len(chips) {
        if inr(mx, my, chips[k]) == 1 { return 10 + k }
        bind k = k + 1
    }
    return 0 - 1
}

fn net_bar(Z, view, sel, labels) {
    bind W = Z[Z_W]
    bind FH = Z[Z_FH]
    bind sat = Z[Z_SAT]
    bind hs = Z[Z_HS]
    bind mx = Z[Z_MX]
    bind my = Z[Z_MY]
    bind tabs = nbar_tabs(W, FH)
    bind j = 0
    while j < 6 {
        bind r = tabs[j]
        bind hov = inr(mx, my, r)
        bind on = 0
        if j == view { bind on = 1 }
        bind fl = 30.0 + 18.0 * hov
        if on == 1 { bind fl = 82.0 }
        col(6, sat * 0.8, fl, hs)
        brect(r[0], r[1], 1.0, 0.0, 0.0, r[2], r[3])
        if on == 1 {
            col(6, sat * 0.8, 26.0, hs)
            bhatch(r[0], r[1], 1.0, 2.0, 2.0, r[2] - 2.0, r[3] - 2.0, 5.0)
        }
        bind tl = 54.0 + 24.0 * hov
        if on == 1 { bind tl = 94.0 }
        col(6, sat, tl, hs)
        daemon_text(NET_VIEWS[j], r[0] + 14.0, r[1] + r[3] / 2.0, 12.0)
        bind j = j + 1
    }
    bind chips = nbar_chips(labels, W, FH)
    bind k = 0
    while k < len(chips) {
        bind r = chips[k]
        bind hov = inr(mx, my, r)
        bind hue = 2
        if k > 0 { bind hue = FLEET_HUE[(k - 1) % 8] }
        bind on = 0
        if k - 1 == sel { bind on = 1 }
        bind fl = 30.0 + 18.0 * hov
        if on == 1 { bind fl = 82.0 }
        col(hue, sat * 0.8, fl, hs)
        brect(r[0], r[1], 1.0, 0.0, 0.0, r[2], r[3])
        if on == 1 {
            col(hue, sat * 0.8, 26.0, hs)
            bhatch(r[0], r[1], 1.0, 2.0, 2.0, r[2] - 2.0, r[3] - 2.0, 5.0)
        }
        bind tl = 52.0 + 24.0 * hov
        if on == 1 { bind tl = 94.0 }
        col(hue, sat, tl, hs)
        daemon_text(labels[k], r[0] + 11.0, r[1] + r[3] / 2.0, 11.0)
        bind k = k + 1
    }
}

fn net_hud(Z, view, sel, nx, live, hudOn) {
    bind W = Z[Z_W]
    bind FH = Z[Z_FH]
    bind sat = Z[Z_SAT]
    bind hs = Z[Z_HS]
    col(2, sat, 72.0, hs)
    daemon_text("LINGNET", 18.0, 26.0, 20.0)
    bind mode = "SIM"
    if live == 1 { bind mode = "LIVE" }
    if live == 2 { bind mode = "STALE" }
    col(6, sat, 66.0, hs)
    daemon_text(NET_VIEWS[view] + "  " + dev_name(nx, sel) + "  " + mode, 18.0, 52.0, 13.0)
    if hudOn == 1 {
        ink(52.0)
        daemon_text(Z[Z_STAT], 18.0, FH - 18.0, 13.0)
        bind keyhint = "TAB VIEW  UP/DN DEVICE  H HUD  ESC"
        ink(40.0)
        daemon_text(keyhint, W - text_w(keyhint, 10.0) - 18.0, FH - 18.0, 10.0)
    }
}

bind NET_SHOT = [0, 1, 2, 3, 4, 5]

bind start = do {
    bind args = get_args()
    bind full = 0
    bind forceSim = 0
    bind shot = 0
    bind W = 1280
    bind H = 720
    bind ai = 0
    while ai < len(args) {
        bind a = args[ai]
        if a == "full" { bind full = 1 }
        if a == "sim" { bind forceSim = 1 }
        if a == "shot" { bind shot = 1 }
        bind pr = split(a, "x")
        if len(pr) == 2 {
            if to_number(pr[0]) > 0 {
                if to_number(pr[1]) > 0 {
                    bind W = floor(to_number(pr[0]))
                    bind H = floor(to_number(pr[1]))
                }
            }
        }
        bind ai = ai + 1
    }
    if full == 1 { open_fullscreen("LingNet") } else { open_window(W, H, "LingNet") }
    bind W = get_width()
    bind FH = get_height()
    bind Hc = FH - 66.0
    set_blend(1)
    set_frame_blur(0.45)

    bind t0 = time_now()
    bind tprev = 0.0
    bind lastRead = 0.0 - 10.0
    bind lastHs = 0.0
    bind view = 0
    bind sel = 0 - 1
    bind showHud = 1
    bind prevDown = 0
    bind shotIdx = 0
    bind shotN = 0
    bind live = 0
    bind tsN = 0.0
    bind chgN = 0.0
    bind tgt = sim_netx(0.0)
    bind vecsS = tgt[NX_VEC]
    bind protoS = tgt[NX_PR]
    bind hn = hn_new()
    bind energy = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    bind ev = []
    bind hist = []
    bind rot = 0.0
    bind hueShift = 0.0
    bind act = 0.0
    bind rdA = 0.0
    bind wrA = 0.0

    while window_is_open() {
        bind t = time_now() - t0
        bind dt = clamp(t - tprev, 0.001, 0.1)
        bind tprev = t
        bind n = len(tgt[NX_AD])

        # ---- input
        bind mx = mouse_x()
        bind my = mouse_y()
        bind click = 0
        if mouse_down() {
            if prevDown == 0 { bind click = 1 }
            bind prevDown = 1
        } else {
            bind prevDown = 0
        }
        if key_pressed("escape") { close_window() }
        if key_pressed("q") { close_window() }
        if key_pressed("tab") { bind view = (view + 1) % 6 }
        if key_pressed("right") { bind view = (view + 1) % 6 }
        if key_pressed("left") { bind view = (view + 5) % 6 }
        if key_pressed("1") { bind view = 0 }
        if key_pressed("2") { bind view = 1 }
        if key_pressed("3") { bind view = 2 }
        if key_pressed("4") { bind view = 3 }
        if key_pressed("5") { bind view = 4 }
        if key_pressed("6") { bind view = 5 }
        if key_pressed("down") {
            bind sel = sel + 1
            if sel >= n { bind sel = 0 - 1 }
        }
        if key_pressed("up") {
            bind sel = sel - 1
            if sel < 0 - 1 { bind sel = n - 1 }
        }
        if key_pressed("h") { bind showHud = 1 - showHud }
        bind labels = ["ALL"]
        bind li = 0
        while li < n {
            bind labels = list_push(labels, str_slice(tgt[NX_AD][li][1], 0, 10))
            bind li = li + 1
        }
        if click == 1 {
            bind bh = nbar_hit(mx, my, W, FH, labels)
            if bh >= 10 { bind sel = bh - 11 }
            if bh >= 0 {
                if bh < 6 { bind view = bh }
            }
            if bh < 0 {
                if view == 0 {
                    bind dh = dev_hit(mx, my, W, Hc, min(n, 6))
                    if dh >= 0 { bind sel = dh }
                }
            }
        }
        if shot == 1 {
            bind view = NET_SHOT[shotIdx]
            bind shotN = shotN + 1
            bind need = 200
            if shotIdx == 0 { bind need = 500 }
            if shotN == need {
                screenshot("hatchnet_" + NET_VIEWS[view])
                bind shotIdx = shotIdx + 1
                bind shotN = 0
                if shotIdx >= len(NET_SHOT) {
                    bind shotIdx = len(NET_SHOT) - 1
                    close_window()
                }
            }
        }

        # ---- data, about every 0.6 s
        if t - lastRead > 0.6 {
            bind lastRead = t
            bind live = 0
            if forceSim == 0 {
                if file_exists("netx.snap") {
                    bind nn = parse_netx(read_file("netx.snap"))
                    if nn[0] == 1 {
                        bind tgt = nn
                        bind live = 1
                        if nn[NX_T] != tsN {
                            bind tsN = nn[NX_T]
                            bind chgN = t
                        }
                        if t - chgN > 20.0 { bind live = 2 }
                    }
                }
            }
            if live == 0 { bind tgt = sim_netx(t) }
        }
        bind k = clamp(dt * 2.5, 0.0, 1.0)
        bind vecsS = glide_ll(vecsS, tgt[NX_VEC], k)
        bind protoS = glide(protoS, tgt[NX_PR], k)
        bind nx = [1, tgt[NX_AD], tgt[NX_M], tgt[NX_ADDR], tgt[NX_GW], tgt[NX_DNS], tgt[NX_NB], protoS, tgt[NX_CN], tgt[NX_K], tgt[NX_DC], tgt[NX_PQ], 0.0, vecsS]
        bind n = len(tgt[NX_AD])
        if sel >= n { bind sel = 0 - 1 }

        # ---- the numbers behind the look
        bind v = sel_vec(nx, sel)
        bind used = lg(v[0] + v[1], 9.0)
        bind kk = clamp(dt * 2.0, 0.0, 1.0)
        bind rdA = mix(rdA, lg(v[0], 8.0), kk)
        bind wrA = mix(wrA, lg(v[1], 8.0), kk)
        bind act = (rdA + wrA) / 2.0
        bind rot = rot + dt * (0.06 + 0.9 * act)
        bind hueShift = 10.0 * sin(t * 0.05) + 26.0 * wrA - 12.0 * rdA
        bind sat = 52.0 + 40.0 * used
        bind p = protoS
        bind enT = lane_fracs(v)
        if view == 3 { bind enT = [lg(p[0], 4.5), lg(p[1], 4.5), lg(p[2], 4.5), lg(p[3], 4.5), lg(p[4], 4.0), lg(p[5], 4.0), lg(p[6] + p[7], 3.0)] }
        if view == 4 {
            bind dc = tgt[NX_DC]
            bind enT = [lg(dc[0], 2.5), lg(dc[1], 2.5), lg(dc[2], 2.5), lg(dc[3], 2.5), lg(dc[4], 2.5), lg(dc[5], 2.5), lg(dc[6], 2.5)]
        }
        bind ne = []
        bind i = 0
        while i < 7 {
            bind ne = list_push(ne, mix(energy[i], enT[i], clamp(dt * 3.0, 0.0, 1.0)))
            bind i = i + 1
        }
        bind energy = ne

        # ---- pulses: bytes in are circles, bytes out are squares; they leave the connections shown in FLOW
        bind ev = ev_cull(ev, t, 1.9)
        bind hist = ev_cull(hist, t, 14.5)
        bind nodes = sel_conns(nx, sel)
        bind nn2 = max(len(nodes), 1)
        bind rate = [14.0 * rdA, 14.0 * wrA]
        bind src = 0
        while src < 2 {
            bind want = rate[src] * dt
            bind cnt = floor(want)
            if rand() < want - cnt { bind cnt = cnt + 1 }
            bind m = 0
            while m < cnt {
                bind cat = pick([0.6, 0.0, 0.3, 0.0, 0.1, 0.0, 0.0], rand())
                if src == 1 { bind cat = pick([0.0, 0.6, 0.0, 0.3, 0.0, 0.1, 0.0], rand()) }
                bind proc = floor(rand() * nn2)
                bind ev = ev_add(ev, src, cat, t, rand(), 0.3 + 0.7 * rand(), proc)
                bind hist = ev_add(hist, src, cat, t, rand(), 0.3 + 0.7 * rand(), proc)
                bind m = m + 1
            }
            bind src = src + 1
        }
        bind h4 = []
        bind i = 0
        while i < len(hist) {
            bind h4 = list_push(h4, hist[i])
            bind h4 = list_push(h4, hist[i + 1])
            bind h4 = list_push(h4, hist[i + 2])
            bind h4 = list_push(h4, hist[i + 4])
            bind i = i + 6
        }
        if t - lastHs > 0.5 {
            bind lastHs = t
            bind hn = hn_sample(hn, nx)
        }

        # ---- the status line
        bind dms = "---"
        bind pq = tgt[NX_PQ]
        bind i = 0
        while i < len(pq) {
            if pq[i][3] > 0.5 {
                if pq[i][4] == 1 {
                    if dms == "---" { bind dms = num_str(round(pq[i][2])) + " MS" }
                }
            }
            bind i = i + 1
        }
        bind stat = "IN " + fmt_bytes(v[0]) + "/S   OUT " + fmt_bytes(v[1]) + "/S   PKTS " + fmt_rate(v[2]) + " IN " + fmt_rate(v[3]) + " OUT   V4 " + fmt_rate(p[0]) + "/S  V6 " + fmt_rate(p[1]) + "/S   DNS " + dms

        bind Dp = [nx, sel, 0, hn, 0, 0, lastHs]
        bind Z = [t, rot, hueShift, sat, W, Hc, [], 1.0, act, 0, 0, 0, ev, [], [], [], [], [], h4, used, energy, [], live, FH, Dp, stat, mx, my, 0, view, enT, [], ["", 0.0 - 100.0, 0]]
        fill(0, 0, 0)
        if view == 0 { view_net_devices(Z) }
        if view == 1 { view_net_flow2(Z) }
        if view == 2 { view_net_packets(Z) }
        if view == 3 { view_net_protocols(Z) }
        if view == 4 { view_net_dns(Z) }
        if view == 5 { view_net_addresses(Z) }
        net_bar(Z, view, sel, labels)
        net_hud(Z, view, sel, nx, live, showHud)
        present()
    }
}

← Back to index