main.go raw
1 // Command cubegif generates an animated GIF of a cube rotating through
2 // its 6 canonical S_3 viewing angles. At each vertex, the projection
3 // glyph is drawn using the dendrite 8-color scheme.
4 //
5 // Usage: go run ./cmd/cubegif [-o output.gif]
6 package main
7
8 import (
9 "flag"
10 "fmt"
11 "image"
12 "image/color"
13 "image/gif"
14 "math"
15 "os"
16 )
17
18 const (
19 width = 480
20 height = 480
21 cx = width / 2 // center x
22 cy = height / 2 // center y
23 scale = 120.0 // cube half-size in pixels
24 )
25
26 // 8-color palette: black, red, orange, yellow, green, blue, purple, white.
27 // Plus background (dark navy) and wireframe gray.
28 var palette = []color.Color{
29 color.RGBA{15, 23, 42, 255}, // 0: background
30 color.RGBA{26, 26, 26, 255}, // 1: black (vertex 000)
31 color.RGBA{239, 68, 68, 255}, // 2: red (vertex 001)
32 color.RGBA{249, 115, 22, 255}, // 3: orange (vertex 010)
33 color.RGBA{234, 179, 8, 255}, // 4: yellow (vertex 011)
34 color.RGBA{34, 197, 94, 255}, // 5: green (vertex 100)
35 color.RGBA{59, 130, 246, 255}, // 6: blue (vertex 101)
36 color.RGBA{168, 85, 247, 255}, // 7: purple (vertex 110)
37 color.RGBA{240, 240, 240, 255}, // 8: white (vertex 111)
38 color.RGBA{71, 85, 105, 255}, // 9: wireframe gray
39 color.RGBA{148, 163, 184, 255}, // 10: label gray
40 color.RGBA{226, 232, 240, 255}, // 11: bright label
41 }
42
43 const (
44 colBG = 0
45 colBlack = 1
46 colRed = 2
47 colOrange = 3
48 colYellow = 4
49 colGreen = 5
50 colBlue = 6
51 colPurple = 7
52 colWhite = 8
53 colWireframe = 9
54 colLabel = 10
55 colBright = 11
56 )
57
58 // vertexColor maps vertex index (0-7) to palette index.
59 var vertexColor = [8]uint8{
60 colBlack, colRed, colOrange, colYellow,
61 colGreen, colBlue, colPurple, colWhite,
62 }
63
64 // Bit-to-color: bit₀ = red, bit₁ = orange, bit₂ = green.
65 var bitColor = [3]uint8{colRed, colOrange, colGreen}
66
67 // 3D point.
68 type vec3 struct{ x, y, z float64 }
69
70 // 8 cube vertices at (±1, ±1, ±1), indexed by 3-bit address.
71 // Bit 0 → x axis (bonding), bit 1 → y axis (constraint), bit 2 → z axis (energy).
72 var cubeVerts [8]vec3
73
74 func init() {
75 for i := range 8 {
76 cubeVerts[i] = vec3{
77 x: btof(i & 1),
78 y: btof((i >> 1) & 1),
79 z: btof((i >> 2) & 1),
80 }
81 }
82 }
83
84 // btof maps 0 → -1, 1 → +1.
85 func btof(b int) float64 {
86 return float64(b)*2 - 1
87 }
88
89 // 12 cube edges: pairs of vertices differing by exactly 1 bit.
90 var cubeEdges = [12][2]int{
91 {0, 1}, {0, 2}, {0, 4},
92 {1, 3}, {1, 5},
93 {2, 3}, {2, 6},
94 {3, 7},
95 {4, 5}, {4, 6},
96 {5, 7},
97 {6, 7},
98 }
99
100 // Rotation matrix: rotate by angle around a unit axis (ax, ay, az).
101 func rotateAxis(v vec3, ax, ay, az, angle float64) vec3 {
102 c := math.Cos(angle)
103 s := math.Sin(angle)
104 t := 1 - c
105 return vec3{
106 x: (t*ax*ax + c)*v.x + (t*ax*ay - s*az)*v.y + (t*ax*az + s*ay)*v.z,
107 y: (t*ax*ay + s*az)*v.x + (t*ay*ay + c)*v.y + (t*ay*az - s*ax)*v.z,
108 z: (t*ax*az - s*ay)*v.x + (t*ay*az + s*ax)*v.y + (t*az*az + c)*v.z,
109 }
110 }
111
112 // Composite rotation: first around Y by yaw, then around X by pitch.
113 func rotate(v vec3, yaw, pitch float64) vec3 {
114 // Yaw around Y axis.
115 v = rotateAxis(v, 0, 1, 0, yaw)
116 // Pitch around X axis.
117 v = rotateAxis(v, 1, 0, 0, pitch)
118 return v
119 }
120
121 // project maps a 3D point to 2D screen coordinates (orthographic).
122 func project(v vec3) (int, int) {
123 return int(cx + v.x*scale), int(cy - v.y*scale)
124 }
125
126 // viewAngle defines a viewing direction for one frame.
127 type viewAngle struct {
128 yaw, pitch float64
129 name string
130 permName string
131 }
132
133 // tilt adds a small offset so vertices never perfectly overlap.
134 // This ensures all 8 vertices are distinguishable in every frame,
135 // making all 48 = 2^n × n! configurations visible across 6 frames.
136 const tilt = 0.21 // ~12 degrees
137
138 // 6 canonical viewing angles, one per S_3 permutation.
139 // Each has a small tilt so back-face vertices are displaced from front ones.
140 var views = []viewAngle{
141 // Face-on views: tilted slightly so all 8 vertices separate.
142 {tilt, tilt, "Face XY", "Identity"}, // near -Z axis
143 {math.Pi/2 + tilt, tilt, "Face XZ", "Swap(C,E)"}, // near -Y axis
144 {tilt, math.Pi/2 + tilt, "Face YZ", "Swap(B,E)"}, // near -X axis
145 {math.Pi/4 + tilt/2, tilt, "Edge-on", "Swap(B,C)"}, // edge view
146 {math.Pi / 4, math.Atan(1/math.Sqrt2) + tilt/3, "Vertex A", "Cycle(012)"}, // isometric A
147 {-math.Pi / 4, math.Atan(1/math.Sqrt2) + tilt/3, "Vertex B", "Cycle(021)"}, // isometric B
148 }
149
150 func main() {
151 outPath := flag.String("o", "projection_cube.gif", "output file path")
152 flag.Parse()
153
154 g := &gif.GIF{}
155
156 for _, view := range views {
157 img := image.NewPaletted(image.Rect(0, 0, width, height), palette)
158 // Fill background.
159 for y := range height {
160 for x := range width {
161 img.SetColorIndex(x, y, colBG)
162 }
163 }
164
165 // Transform all vertices.
166 var projected [8]vec3
167 var screenX, screenY [8]int
168 for i := range 8 {
169 projected[i] = rotate(cubeVerts[i], view.yaw, view.pitch)
170 screenX[i], screenY[i] = project(projected[i])
171 }
172
173 // Draw edges (sorted by average depth — back edges first).
174 type edgeDepth struct {
175 i, j int
176 depth float64
177 }
178 var edges []edgeDepth
179 for _, e := range cubeEdges {
180 avgZ := (projected[e[0]].z + projected[e[1]].z) / 2
181 edges = append(edges, edgeDepth{e[0], e[1], avgZ})
182 }
183 // Sort: deeper edges first (they get drawn underneath).
184 for i := range len(edges) {
185 for j := i + 1; j < len(edges); j++ {
186 if edges[j].depth < edges[i].depth {
187 edges[i], edges[j] = edges[j], edges[i]
188 }
189 }
190 }
191 for _, e := range edges {
192 drawLine(img, screenX[e.i], screenY[e.i], screenX[e.j], screenY[e.j], colWireframe)
193 }
194
195 // Sort vertices by depth (back to front) for drawing.
196 type vertDepth struct {
197 idx int
198 depth float64
199 }
200 var verts []vertDepth
201 for i := range 8 {
202 verts = append(verts, vertDepth{i, projected[i].z})
203 }
204 for i := range len(verts) {
205 for j := i + 1; j < len(verts); j++ {
206 if verts[j].depth < verts[i].depth {
207 verts[i], verts[j] = verts[j], verts[i]
208 }
209 }
210 }
211
212 // Draw glyphs and vertices (back to front).
213 for _, vd := range verts {
214 v := vd.idx
215 sx, sy := screenX[v], screenY[v]
216
217 // Draw glyph arms: for each set bit, draw a short line in that bit's color.
218 armLen := 18.0
219 for bit := range 3 {
220 if v&(1<<bit) == 0 {
221 continue
222 }
223 // Arm direction in 3D: unit vector along the bit's axis.
224 var dir vec3
225 switch bit {
226 case 0:
227 dir = vec3{1, 0, 0}
228 case 1:
229 dir = vec3{0, 1, 0}
230 case 2:
231 dir = vec3{0, 0, 1}
232 }
233 // Rotate the direction by the same view angle.
234 dir = rotate(dir, view.yaw, view.pitch)
235 ex := sx + int(dir.x*armLen)
236 ey := sy - int(dir.y*armLen) // y is inverted
237 // Line thickness: bit₀ widest (3 px), bit₂ narrowest (1 px).
238 thickness := 3 - bit
239 drawThickLine(img, sx, sy, ex, ey, bitColor[bit], thickness)
240 }
241
242 // Draw vertex dot.
243 r := 5
244 fillCircle(img, sx, sy, r, vertexColor[v])
245 }
246
247 // Frame label.
248 drawString(img, 10, 18, fmt.Sprintf("%s (%s)", view.name, view.permName), colBright)
249 drawString(img, 10, height-12, "dendrite - cubic projection", colLabel)
250
251 g.Image = append(g.Image, img)
252 g.Delay = append(g.Delay, 150) // 1.5 seconds per frame
253 }
254
255 f, err := os.Create(*outPath)
256 if err != nil {
257 fmt.Fprintf(os.Stderr, "error: %v\n", err)
258 os.Exit(1)
259 }
260 defer f.Close()
261
262 if err := gif.EncodeAll(f, g); err != nil {
263 fmt.Fprintf(os.Stderr, "error encoding gif: %v\n", err)
264 os.Exit(1)
265 }
266 fmt.Printf("wrote %s (%d frames)\n", *outPath, len(g.Image))
267 }
268
269 // --- Drawing primitives (no external deps) ---
270
271 // drawLine draws a 1-pixel line using Bresenham's algorithm.
272 func drawLine(img *image.Paletted, x0, y0, x1, y1 int, col uint8) {
273 dx := abs(x1 - x0)
274 dy := -abs(y1 - y0)
275 sx, sy := 1, 1
276 if x0 >= x1 {
277 sx = -1
278 }
279 if y0 >= y1 {
280 sy = -1
281 }
282 err := dx + dy
283 for {
284 if x0 >= 0 && x0 < width && y0 >= 0 && y0 < height {
285 img.SetColorIndex(x0, y0, col)
286 }
287 if x0 == x1 && y0 == y1 {
288 break
289 }
290 e2 := 2 * err
291 if e2 >= dy {
292 err += dy
293 x0 += sx
294 }
295 if e2 <= dx {
296 err += dx
297 y0 += sy
298 }
299 }
300 }
301
302 // drawThickLine draws a line with thickness (by offsetting perpendicular).
303 func drawThickLine(img *image.Paletted, x0, y0, x1, y1 int, col uint8, thickness int) {
304 if thickness <= 1 {
305 drawLine(img, x0, y0, x1, y1, col)
306 return
307 }
308 dx := float64(x1 - x0)
309 dy := float64(y1 - y0)
310 length := math.Sqrt(dx*dx + dy*dy)
311 if length < 0.5 {
312 return
313 }
314 // Perpendicular unit vector.
315 px := -dy / length
316 py := dx / length
317
318 half := float64(thickness) / 2
319 for t := -half; t <= half; t += 0.5 {
320 ox := int(math.Round(px * t))
321 oy := int(math.Round(py * t))
322 drawLine(img, x0+ox, y0+oy, x1+ox, y1+oy, col)
323 }
324 }
325
326 // fillCircle draws a filled circle.
327 func fillCircle(img *image.Paletted, cx, cy, r int, col uint8) {
328 for dy := -r; dy <= r; dy++ {
329 for dx := -r; dx <= r; dx++ {
330 if dx*dx+dy*dy <= r*r {
331 px, py := cx+dx, cy+dy
332 if px >= 0 && px < width && py >= 0 && py < height {
333 img.SetColorIndex(px, py, col)
334 }
335 }
336 }
337 }
338 }
339
340 // drawString renders text using a simple built-in 5x7 bitmap font.
341 // Only supports ASCII printable characters.
342 func drawString(img *image.Paletted, x, y int, s string, col uint8) {
343 for _, ch := range s {
344 if ch < 32 || ch > 126 {
345 ch = '?'
346 }
347 glyph := font5x7[ch-32]
348 for row := range 7 {
349 for col_bit := range 5 {
350 if glyph[row]&(1<<(4-col_bit)) != 0 {
351 px, py := x+col_bit, y+row
352 if px >= 0 && px < width && py >= 0 && py < height {
353 img.SetColorIndex(px, py, col)
354 }
355 }
356 }
357 }
358 x += 6 // 5 pixels + 1 space
359 }
360 }
361
362 func abs(x int) int {
363 if x < 0 {
364 return -x
365 }
366 return x
367 }
368
369 // font5x7 is a minimal 5×7 bitmap font for ASCII 32-126.
370 // Each character is 7 rows of 5-bit bitmaps (MSB = leftmost pixel).
371 var font5x7 = [95][7]uint8{
372 // ' ' (32)
373 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
374 // '!' (33)
375 {0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04},
376 // '"' (34)
377 {0x0A, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00},
378 // '#' (35)
379 {0x0A, 0x1F, 0x0A, 0x0A, 0x1F, 0x0A, 0x00},
380 // '$' (36)
381 {0x04, 0x0F, 0x14, 0x0E, 0x05, 0x1E, 0x04},
382 // '%' (37)
383 {0x18, 0x19, 0x02, 0x04, 0x08, 0x13, 0x03},
384 // '&' (38)
385 {0x08, 0x14, 0x14, 0x08, 0x15, 0x12, 0x0D},
386 // '\'' (39)
387 {0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00},
388 // '(' (40)
389 {0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02},
390 // ')' (41)
391 {0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08},
392 // '*' (42)
393 {0x00, 0x0A, 0x04, 0x1F, 0x04, 0x0A, 0x00},
394 // '+' (43)
395 {0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00},
396 // ',' (44)
397 {0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08},
398 // '-' (45)
399 {0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00},
400 // '.' (46)
401 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04},
402 // '/' (47)
403 {0x01, 0x02, 0x02, 0x04, 0x08, 0x08, 0x10},
404 // '0' (48)
405 {0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E},
406 // '1' (49)
407 {0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E},
408 // '2' (50)
409 {0x0E, 0x11, 0x01, 0x06, 0x08, 0x10, 0x1F},
410 // '3' (51)
411 {0x0E, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0E},
412 // '4' (52)
413 {0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02},
414 // '5' (53)
415 {0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E},
416 // '6' (54)
417 {0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E},
418 // '7' (55)
419 {0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08},
420 // '8' (56)
421 {0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E},
422 // '9' (57)
423 {0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C},
424 // ':' (58)
425 {0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00},
426 // ';' (59)
427 {0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x08},
428 // '<' (60)
429 {0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02},
430 // '=' (61)
431 {0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00},
432 // '>' (62)
433 {0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08},
434 // '?' (63)
435 {0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04},
436 // '@' (64)
437 {0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0E},
438 // 'A' (65)
439 {0x0E, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
440 // 'B' (66)
441 {0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E},
442 // 'C' (67)
443 {0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E},
444 // 'D' (68)
445 {0x1E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x1E},
446 // 'E' (69)
447 {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x1F},
448 // 'F' (70)
449 {0x1F, 0x10, 0x10, 0x1E, 0x10, 0x10, 0x10},
450 // 'G' (71)
451 {0x0E, 0x11, 0x10, 0x17, 0x11, 0x11, 0x0F},
452 // 'H' (72)
453 {0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11},
454 // 'I' (73)
455 {0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E},
456 // 'J' (74)
457 {0x01, 0x01, 0x01, 0x01, 0x11, 0x11, 0x0E},
458 // 'K' (75)
459 {0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11},
460 // 'L' (76)
461 {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F},
462 // 'M' (77)
463 {0x11, 0x1B, 0x15, 0x15, 0x11, 0x11, 0x11},
464 // 'N' (78)
465 {0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x11},
466 // 'O' (79)
467 {0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
468 // 'P' (80)
469 {0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10},
470 // 'Q' (81)
471 {0x0E, 0x11, 0x11, 0x11, 0x15, 0x12, 0x0D},
472 // 'R' (82)
473 {0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11},
474 // 'S' (83)
475 {0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E},
476 // 'T' (84)
477 {0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
478 // 'U' (85)
479 {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E},
480 // 'V' (86)
481 {0x11, 0x11, 0x11, 0x11, 0x0A, 0x0A, 0x04},
482 // 'W' (87)
483 {0x11, 0x11, 0x11, 0x15, 0x15, 0x1B, 0x11},
484 // 'X' (88)
485 {0x11, 0x11, 0x0A, 0x04, 0x0A, 0x11, 0x11},
486 // 'Y' (89)
487 {0x11, 0x11, 0x0A, 0x04, 0x04, 0x04, 0x04},
488 // 'Z' (90)
489 {0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F},
490 // '[' (91)
491 {0x0E, 0x08, 0x08, 0x08, 0x08, 0x08, 0x0E},
492 // '\' (92)
493 {0x10, 0x08, 0x08, 0x04, 0x02, 0x02, 0x01},
494 // ']' (93)
495 {0x0E, 0x02, 0x02, 0x02, 0x02, 0x02, 0x0E},
496 // '^' (94)
497 {0x04, 0x0A, 0x11, 0x00, 0x00, 0x00, 0x00},
498 // '_' (95)
499 {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F},
500 // '`' (96)
501 {0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00},
502 // 'a' (97)
503 {0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F},
504 // 'b' (98)
505 {0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x1E},
506 // 'c' (99)
507 {0x00, 0x00, 0x0E, 0x11, 0x10, 0x11, 0x0E},
508 // 'd' (100)
509 {0x01, 0x01, 0x0F, 0x11, 0x11, 0x11, 0x0F},
510 // 'e' (101)
511 {0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0E},
512 // 'f' (102)
513 {0x06, 0x08, 0x1E, 0x08, 0x08, 0x08, 0x08},
514 // 'g' (103)
515 {0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x0E},
516 // 'h' (104)
517 {0x10, 0x10, 0x1E, 0x11, 0x11, 0x11, 0x11},
518 // 'i' (105)
519 {0x04, 0x00, 0x0C, 0x04, 0x04, 0x04, 0x0E},
520 // 'j' (106)
521 {0x02, 0x00, 0x02, 0x02, 0x02, 0x12, 0x0C},
522 // 'k' (107)
523 {0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12},
524 // 'l' (108)
525 {0x0C, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E},
526 // 'm' (109)
527 {0x00, 0x00, 0x1A, 0x15, 0x15, 0x11, 0x11},
528 // 'n' (110)
529 {0x00, 0x00, 0x1E, 0x11, 0x11, 0x11, 0x11},
530 // 'o' (111)
531 {0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E},
532 // 'p' (112)
533 {0x00, 0x00, 0x1E, 0x11, 0x1E, 0x10, 0x10},
534 // 'q' (113)
535 {0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x01},
536 // 'r' (114)
537 {0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10},
538 // 's' (115)
539 {0x00, 0x00, 0x0F, 0x10, 0x0E, 0x01, 0x1E},
540 // 't' (116)
541 {0x08, 0x08, 0x1E, 0x08, 0x08, 0x09, 0x06},
542 // 'u' (117)
543 {0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D},
544 // 'v' (118)
545 {0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04},
546 // 'w' (119)
547 {0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A},
548 // 'x' (120)
549 {0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11},
550 // 'y' (121)
551 {0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E},
552 // 'z' (122)
553 {0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F},
554 // '{' (123)
555 {0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02},
556 // '|' (124)
557 {0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04},
558 // '}' (125)
559 {0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08},
560 // '~' (126)
561 {0x00, 0x00, 0x08, 0x15, 0x02, 0x00, 0x00},
562 }
563