package ui import "strings" // Half-width katakana + digits for the iconic Matrix digital rain. var matrixChars = []rune{ 'ヲ', 'ァ', 'ィ', 'ゥ', 'ェ', 'ォ', 'ャ', 'ュ', 'ョ', 'ッ', 'ー', 'ア', 'イ', 'ウ', 'エ', 'オ', 'カ', 'キ', 'ク', 'ケ', 'コ', 'サ', 'シ', 'ス', 'セ', 'ソ', 'タ', 'チ', 'ツ', 'テ', 'ト', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', } // renderMatrix draws falling character streams reminiscent of the Matrix // digital rain. Each column has a fixed fall speed derived from its position. // Band energy controls how many columns are active (rain density). func (v *Visualizer) renderMatrix(bands []float64) string { height := v.Rows lines := make([]string, height) bandCount := len(bands) for row := range height { var sb, run strings.Builder tag := -1 col := 0 for b := range bandCount { w := visBandWidth(bandCount, b) for range w { energy := bands[b] seed := uint64(col)*7919 + 104729 // Column activity: stable gate, changes every ~20 frames. // Higher energy activates more columns. if scatterHash(b, 0, col, v.frame/20) > energy*1.5+0.1 { if tag != -1 { flushStyleRun(&sb, &run, tag) tag = -1 } run.WriteByte(' ') col++ continue } // Fixed speed per column (2-4 frames per row step), derived // from column position so each drop falls steadily. speed := 2 + int(seed%3) // Trail length: 3-5 characters. trailLen := 3 + int((seed/7)%3) // Cycle length large enough for a visible gap between drops. cycleLen := height + trailLen + 4 offset := int((seed / 13) % uint64(cycleLen)) pos := (int(v.frame)/speed + offset) % cycleLen dist := pos - row if dist < 0 || dist > trailLen { if tag != -1 { flushStyleRun(&sb, &run, tag) tag = -1 } run.WriteByte(' ') } else { // Character mutates slowly (~every 4 frames). charSeed := seed ^ (uint64(row)*31 + (v.frame/4)*17) ch := matrixChars[charSeed%uint64(len(matrixChars))] var newTag int switch { case dist == 0: newTag = 2 case dist <= 2: newTag = 1 default: newTag = 0 } if newTag != tag { flushStyleRun(&sb, &run, tag) tag = newTag } run.WriteRune(ch) } col++ } if b < bandCount-1 { if tag != -1 { flushStyleRun(&sb, &run, tag) tag = -1 } run.WriteByte(' ') col++ } } flushStyleRun(&sb, &run, tag) lines[row] = sb.String() } return strings.Join(lines, "\n") }