test(oracle): add differential oracle and isolate benchmark setup
Seeded workloads drive the public VT, history, codec, ANSI and HTML pipeline and hash every observable result into a golden file. Benchmarks no longer allocate a Screen or input slices inside the measured loop.
- commit
- dca82451e2bd7b65049428e6e80ab65422ba42f9
- author
- brice · · 2026-10-02 09:12 UTC
- parent
- 8f2d811
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +1090 -4
M benchmark_test.go +6 -4
| @@ -248,8 +248,8 @@ func BenchmarkScreenKittyAPC(b *testing.B) { | |||
| 248 | 248 | b.ReportAllocs() | |
| 249 | 249 | for b.Loop() { | |
| 250 | 250 | if fragmented { | |
| 251 | - | for _, part := range apc { | |
| 252 | - | s.Write([]byte{part}) | |
| 251 | + | for i := range apc { | |
| 252 | + | s.Write(apc[i : i+1 : i+1]) | |
| 253 | 253 | } | |
| 254 | 254 | } else { | |
| 255 | 255 | s.Write(apc) | |
| @@ -301,9 +301,10 @@ func BenchmarkScreenCaptureDamage(b *testing.B) { | |||
| 301 | 301 | ||
| 302 | 302 | func BenchmarkScreenShellRedrawBurst(b *testing.B) { | |
| 303 | 303 | chunk := []byte("\r\x1b[K❯ abc\x1b[90m autosuggestion\x1b[39m\r\x1b[5C") | |
| 304 | + | s := NewScreen(120, 40) | |
| 305 | + | s.ClearDamage() | |
| 304 | 306 | b.ReportAllocs() | |
| 305 | 307 | for b.Loop() { | |
| 306 | - | s := NewScreen(120, 40) | |
| 307 | 308 | for range 200 { | |
| 308 | 309 | s.Write(chunk) | |
| 309 | 310 | s.ClearDamage() | |
| @@ -313,9 +314,10 @@ func BenchmarkScreenShellRedrawBurst(b *testing.B) { | |||
| 313 | 314 | ||
| 314 | 315 | func BenchmarkScreenFullscreenScrollRegion(b *testing.B) { | |
| 315 | 316 | chunk := []byte("\x1b[2;39r\x1b[39;1Hline\n") | |
| 317 | + | s := NewScreen(120, 40) | |
| 318 | + | s.ClearDamage() | |
| 316 | 319 | b.ReportAllocs() | |
| 317 | 320 | for b.Loop() { | |
| 318 | - | s := NewScreen(120, 40) | |
| 319 | 321 | for range 500 { | |
| 320 | 322 | s.Write(chunk) | |
| 321 | 323 | s.ClearDamage() | |
| @@ -248,8 +248,8 @@ func BenchmarkScreenKittyAPC(b *testing.B) { | |||||
| 248 | b.ReportAllocs() | 248 | b.ReportAllocs() | ||
| 249 | for b.Loop() { | 249 | for b.Loop() { | ||
| 250 | if fragmented { | 250 | if fragmented { | ||
| 251 | - | for _, part := range apc { | 251 | + | for i := range apc { |
| 252 | - | s.Write([]byte{part}) | 252 | + | s.Write(apc[i : i+1 : i+1]) |
| 253 | } | 253 | } | ||
| 254 | } else { | 254 | } else { | ||
| 255 | s.Write(apc) | 255 | s.Write(apc) | ||
| @@ -301,9 +301,10 @@ func BenchmarkScreenCaptureDamage(b *testing.B) { | |||||
| 301 | 301 | ||||
| 302 | func BenchmarkScreenShellRedrawBurst(b *testing.B) { | 302 | func BenchmarkScreenShellRedrawBurst(b *testing.B) { | ||
| 303 | chunk := []byte("\r\x1b[K❯ abc\x1b[90m autosuggestion\x1b[39m\r\x1b[5C") | 303 | chunk := []byte("\r\x1b[K❯ abc\x1b[90m autosuggestion\x1b[39m\r\x1b[5C") | ||
| 304 | + | s := NewScreen(120, 40) | |||
| 305 | + | s.ClearDamage() | |||
| 304 | b.ReportAllocs() | 306 | b.ReportAllocs() | ||
| 305 | for b.Loop() { | 307 | for b.Loop() { | ||
| 306 | - | s := NewScreen(120, 40) | |||
| 307 | for range 200 { | 308 | for range 200 { | ||
| 308 | s.Write(chunk) | 309 | s.Write(chunk) | ||
| 309 | s.ClearDamage() | 310 | s.ClearDamage() | ||
| @@ -313,9 +314,10 @@ func BenchmarkScreenShellRedrawBurst(b *testing.B) { | |||||
| 313 | 314 | ||||
| 314 | func BenchmarkScreenFullscreenScrollRegion(b *testing.B) { | 315 | func BenchmarkScreenFullscreenScrollRegion(b *testing.B) { | ||
| 315 | chunk := []byte("\x1b[2;39r\x1b[39;1Hline\n") | 316 | chunk := []byte("\x1b[2;39r\x1b[39;1Hline\n") | ||
| 317 | + | s := NewScreen(120, 40) | |||
| 318 | + | s.ClearDamage() | |||
| 316 | b.ReportAllocs() | 319 | b.ReportAllocs() | ||
| 317 | for b.Loop() { | 320 | for b.Loop() { | ||
| 318 | - | s := NewScreen(120, 40) | |||
| 319 | for range 500 { | 321 | for range 500 { | ||
| 320 | s.Write(chunk) | 322 | s.Write(chunk) | ||
| 321 | s.ClearDamage() | 323 | s.ClearDamage() | ||
A oracle_test.go +616 -0
| @@ -0,0 +1,616 @@ | |||
| 1 | + | package vt_test | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bufio" | |
| 5 | + | "bytes" | |
| 6 | + | "crypto/sha256" | |
| 7 | + | "encoding/hex" | |
| 8 | + | "flag" | |
| 9 | + | "fmt" | |
| 10 | + | "hash" | |
| 11 | + | "math/rand/v2" | |
| 12 | + | "os" | |
| 13 | + | "path/filepath" | |
| 14 | + | "strings" | |
| 15 | + | "testing" | |
| 16 | + | ||
| 17 | + | vt "github.com/bnema/vev-vt" | |
| 18 | + | "github.com/bnema/vev-vt/ansi" | |
| 19 | + | core "github.com/bnema/vev-vt/core" | |
| 20 | + | "github.com/bnema/vev-vt/graphics" | |
| 21 | + | "github.com/bnema/vev-vt/html" | |
| 22 | + | ) | |
| 23 | + | ||
| 24 | + | // The differential oracle drives seeded terminal workloads through the public | |
| 25 | + | // pipeline (VT parsing, damage, history, codecs, ANSI and HTML renderers) and | |
| 26 | + | // hashes every observable result at checkpoints. Performance work must keep | |
| 27 | + | // the golden file byte-identical; behavior changes must regenerate it with | |
| 28 | + | // -oracle.update and justify the difference. | |
| 29 | + | var ( | |
| 30 | + | oracleUpdate = flag.Bool("oracle.update", false, "rewrite testdata/oracle/golden.txt") | |
| 31 | + | oracleDump = flag.String("oracle.dump", "", "write full plain-text transcripts to this directory") | |
| 32 | + | ) | |
| 33 | + | ||
| 34 | + | const ( | |
| 35 | + | oracleScenarios = 36 | |
| 36 | + | oracleSteps = 240 | |
| 37 | + | oracleCheckEvery = 20 | |
| 38 | + | oracleGolden = "testdata/oracle/golden.txt" | |
| 39 | + | ) | |
| 40 | + | ||
| 41 | + | type oracleScenario struct { | |
| 42 | + | seed uint64 | |
| 43 | + | cols int | |
| 44 | + | rows int | |
| 45 | + | history *vt.HistoryConfig | |
| 46 | + | byteSplit bool | |
| 47 | + | evictHook bool | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | func oracleScenarioFor(i int) oracleScenario { | |
| 51 | + | r := rand.New(rand.NewPCG(uint64(i), 0x5eed)) | |
| 52 | + | sc := oracleScenario{seed: uint64(i), cols: 8 + r.IntN(110), rows: 3 + r.IntN(40)} | |
| 53 | + | switch i % 4 { | |
| 54 | + | case 1: | |
| 55 | + | sc.history = &vt.HistoryConfig{MaxRows: 50 + r.IntN(400), ChunkRows: 1 + r.IntN(16)} | |
| 56 | + | case 2: | |
| 57 | + | sc.history = &vt.HistoryConfig{MaxBytes: uint64(64<<10 + r.IntN(512<<10)), ChunkRows: 1 + r.IntN(64)} | |
| 58 | + | case 3: | |
| 59 | + | sc.history = &vt.HistoryConfig{MaxRows: 2000, MaxBytes: 4 << 20} | |
| 60 | + | } | |
| 61 | + | sc.byteSplit = i%5 == 0 | |
| 62 | + | sc.evictHook = i%3 == 0 | |
| 63 | + | return sc | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | type oracleRun struct { | |
| 67 | + | t *testing.T | |
| 68 | + | sc oracleScenario | |
| 69 | + | r *rand.Rand | |
| 70 | + | screen *vt.Screen | |
| 71 | + | ansiR *ansi.Renderer | |
| 72 | + | ansi256 *ansi.Renderer | |
| 73 | + | htmlR *html.Renderer | |
| 74 | + | replay *vt.Screen | |
| 75 | + | // mirror reproduces the vev daemon consumer: one core.Frame reused and | |
| 76 | + | // mutated in place between draws, so renderer shadows must not alias it. | |
| 77 | + | mirror core.Frame | |
| 78 | + | h hash.Hash | |
| 79 | + | dump *bufio.Writer | |
| 80 | + | events []string | |
| 81 | + | ||
| 82 | + | replayMismatches int | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | func (o *oracleRun) emit(format string, args ...any) { | |
| 86 | + | line := fmt.Sprintf(format, args...) | |
| 87 | + | o.h.Write([]byte(line)) | |
| 88 | + | o.h.Write([]byte{'\n'}) | |
| 89 | + | if o.dump != nil { | |
| 90 | + | o.dump.WriteString(line) | |
| 91 | + | o.dump.WriteByte('\n') | |
| 92 | + | } | |
| 93 | + | } | |
| 94 | + | ||
| 95 | + | func formatCell(c core.Cell) string { | |
| 96 | + | return fmt.Sprintf("%q|%+v|%q|%q|%t", c.Rune, c.Style, c.Payload.Grapheme(), c.Payload.Hyperlink(), c.Continuation) | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | // formatRow is a compact exact digest of a row: every Cell field contributes. | |
| 100 | + | // Full text is only produced for dumps, where it is needed for diagnosis. | |
| 101 | + | func formatRow(row []core.Cell) string { | |
| 102 | + | h := sha256.New() | |
| 103 | + | var buf []byte | |
| 104 | + | for _, c := range row { | |
| 105 | + | buf = fmt.Appendf(buf[:0], "%d|%+v|%s|%s|%t;", c.Rune, c.Style, c.Payload.Grapheme(), c.Payload.Hyperlink(), c.Continuation) | |
| 106 | + | h.Write(buf) | |
| 107 | + | } | |
| 108 | + | return hex.EncodeToString(h.Sum(nil)[:12]) | |
| 109 | + | } | |
| 110 | + | ||
| 111 | + | func rowText(row []core.Cell) string { | |
| 112 | + | var b strings.Builder | |
| 113 | + | for _, c := range row { | |
| 114 | + | if c.Continuation { | |
| 115 | + | continue | |
| 116 | + | } | |
| 117 | + | if g := c.Payload.Grapheme(); g != "" { | |
| 118 | + | b.WriteString(g) | |
| 119 | + | } else if c.Rune != 0 { | |
| 120 | + | b.WriteRune(c.Rune) | |
| 121 | + | } | |
| 122 | + | } | |
| 123 | + | return b.String() | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | func (o *oracleRun) genChunk() []byte { | |
| 127 | + | r := o.r | |
| 128 | + | var b bytes.Buffer | |
| 129 | + | for range 1 + r.IntN(6) { | |
| 130 | + | switch op := r.IntN(100); { | |
| 131 | + | case op < 22: | |
| 132 | + | words := []string{"hello", "world", "vev", "ls -la", "❯", "foo/bar", "λx", "0123456789"} | |
| 133 | + | for range 1 + r.IntN(8) { | |
| 134 | + | b.WriteString(words[r.IntN(len(words))]) | |
| 135 | + | b.WriteByte(' ') | |
| 136 | + | } | |
| 137 | + | if r.IntN(2) == 0 { | |
| 138 | + | b.WriteString("\r\n") | |
| 139 | + | } | |
| 140 | + | case op < 30: | |
| 141 | + | uni := []string{"界", "🙂", "é", "e\u0301", "한국어", "👍🏽", "a\u200db", "ff"} | |
| 142 | + | for range 1 + r.IntN(10) { | |
| 143 | + | b.WriteString(uni[r.IntN(len(uni))]) | |
| 144 | + | } | |
| 145 | + | case op < 42: | |
| 146 | + | b.WriteString(randomSGR(r)) | |
| 147 | + | case op < 50: | |
| 148 | + | fmt.Fprintf(&b, "\x1b[%d;%dH", 1+r.IntN(o.sc.rows+2), 1+r.IntN(o.sc.cols+2)) | |
| 149 | + | case op < 56: | |
| 150 | + | seqs := []string{"\x1b[K", "\x1b[1K", "\x1b[2K", "\x1b[J", "\x1b[2J", "\x1b[3@", "\x1b[2P", "\x1b[L", "\x1b[2M", "\x1b[4X", "\x1b[S", "\x1b[2T", "\x1bM", "\x1bD", "\x1bE", "\t", "\b"} | |
| 151 | + | b.WriteString(seqs[r.IntN(len(seqs))]) | |
| 152 | + | case op < 64: | |
| 153 | + | for range 1 + r.IntN(3*o.sc.rows) { | |
| 154 | + | fmt.Fprintf(&b, "line %d %s\r\n", r.IntN(1000), strings.Repeat("=", r.IntN(o.sc.cols+5))) | |
| 155 | + | } | |
| 156 | + | case op < 68: | |
| 157 | + | top := 1 + r.IntN(o.sc.rows) | |
| 158 | + | bottom := top + r.IntN(o.sc.rows-top+1) | |
| 159 | + | fmt.Fprintf(&b, "\x1b[%d;%dr\x1b[%d;1H", top, bottom, bottom) | |
| 160 | + | for range 1 + r.IntN(6) { | |
| 161 | + | b.WriteString("region\n") | |
| 162 | + | } | |
| 163 | + | if r.IntN(2) == 0 { | |
| 164 | + | b.WriteString("\x1b[r") | |
| 165 | + | } | |
| 166 | + | case op < 71: | |
| 167 | + | if r.IntN(2) == 0 { | |
| 168 | + | b.WriteString("\x1b[?1049h") | |
| 169 | + | } else { | |
| 170 | + | b.WriteString("\x1b[?1049l") | |
| 171 | + | } | |
| 172 | + | case op < 74: | |
| 173 | + | fmt.Fprintf(&b, "\x1b]8;;https://example.invalid/%d\x1b\\link\x1b]8;;\x1b\\", r.IntN(9)) | |
| 174 | + | case op < 76: | |
| 175 | + | fmt.Fprintf(&b, "\x1b]0;title %d\x07", r.IntN(99)) | |
| 176 | + | case op < 81: | |
| 177 | + | b.WriteString(randomKitty(r)) | |
| 178 | + | case op < 84: | |
| 179 | + | queries := []string{"\x1b[6n", "\x1b[c", "\x1b[?2004$p", "\x1b[5n"} | |
| 180 | + | b.WriteString(queries[r.IntN(len(queries))]) | |
| 181 | + | case op < 86: | |
| 182 | + | if r.IntN(2) == 0 { | |
| 183 | + | b.WriteString("\x1b[?7l") | |
| 184 | + | } else { | |
| 185 | + | b.WriteString("\x1b[?7h") | |
| 186 | + | } | |
| 187 | + | case op < 91: | |
| 188 | + | // High-cardinality truecolor burst. | |
| 189 | + | for range 1 + r.IntN(40) { | |
| 190 | + | fmt.Fprintf(&b, "\x1b[38;2;%d;%d;%dm#", r.IntN(256), r.IntN(256), r.IntN(256)) | |
| 191 | + | } | |
| 192 | + | b.WriteString("\x1b[0m") | |
| 193 | + | case op < 94: | |
| 194 | + | // Styled blanks and erases inherit the current background. | |
| 195 | + | fmt.Fprintf(&b, "\x1b[48;5;%dm\x1b[K \x1b[0m", r.IntN(256)) | |
| 196 | + | default: | |
| 197 | + | fmt.Fprintf(&b, "\r\x1b[K❯ abc\x1b[90m suggestion\x1b[39m\r\x1b[%dC", r.IntN(8)) | |
| 198 | + | } | |
| 199 | + | } | |
| 200 | + | return b.Bytes() | |
| 201 | + | } | |
| 202 | + | ||
| 203 | + | func randomSGR(r *rand.Rand) string { | |
| 204 | + | parts := []string{"0", "1", "2", "3", "4", "4:3", "5", "7", "9", "22", "23", "24", "27", "39", "49", "59"} | |
| 205 | + | switch r.IntN(6) { | |
| 206 | + | case 0: | |
| 207 | + | return fmt.Sprintf("\x1b[%dm", 30+r.IntN(8)) | |
| 208 | + | case 1: | |
| 209 | + | return fmt.Sprintf("\x1b[38;5;%d;48;5;%dm", r.IntN(256), r.IntN(256)) | |
| 210 | + | case 2: | |
| 211 | + | return fmt.Sprintf("\x1b[38;2;%d;%d;%dm", r.IntN(256), r.IntN(256), r.IntN(256)) | |
| 212 | + | case 3: | |
| 213 | + | return fmt.Sprintf("\x1b[58;2;%d;%d;%d;4:%dm", r.IntN(256), r.IntN(256), r.IntN(256), r.IntN(6)) | |
| 214 | + | default: | |
| 215 | + | return "\x1b[" + parts[r.IntN(len(parts))] + "m" | |
| 216 | + | } | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | func randomKitty(r *rand.Rand) string { | |
| 220 | + | switch r.IntN(4) { | |
| 221 | + | case 0: | |
| 222 | + | w, h := 1+r.IntN(4), 1+r.IntN(4) | |
| 223 | + | pixels := make([]byte, w*h*4) | |
| 224 | + | for i := range pixels { | |
| 225 | + | pixels[i] = byte(r.IntN(256)) | |
| 226 | + | } | |
| 227 | + | enc := encodeBase64(pixels, r.IntN(2) == 0) | |
| 228 | + | return fmt.Sprintf("\x1b_Ga=T,i=%d,f=32,s=%d,v=%d,c=%d,r=%d,q=%d;%s\x1b\\", 1+r.IntN(4), w, h, 1+r.IntN(3), 1+r.IntN(2), r.IntN(3), enc) | |
| 229 | + | case 1: | |
| 230 | + | // Chunked transmission. | |
| 231 | + | pixels := make([]byte, 4*4*4) | |
| 232 | + | for i := range pixels { | |
| 233 | + | pixels[i] = byte(r.IntN(256)) | |
| 234 | + | } | |
| 235 | + | enc := encodeBase64(pixels, true) | |
| 236 | + | mid := (len(enc) / 8) * 4 | |
| 237 | + | return fmt.Sprintf("\x1b_Ga=t,i=%d,f=32,s=4,v=4,m=1;%s\x1b\\\x1b_Gm=0;%s\x1b\\\x1b_Ga=p,i=%d\x1b\\", 5+r.IntN(3), enc[:mid], enc[mid:], 5+r.IntN(3)) | |
| 238 | + | case 2: | |
| 239 | + | return fmt.Sprintf("\x1b_Ga=d,d=%s\x1b\\", []string{"a", "A", "i", "I"}[r.IntN(4)]) | |
| 240 | + | default: | |
| 241 | + | // Invalid base64 must be rejected identically. | |
| 242 | + | return "\x1b_Ga=T,f=32,s=1,v=1;!!!\x1b\\" | |
| 243 | + | } | |
| 244 | + | } | |
| 245 | + | ||
| 246 | + | func encodeBase64(data []byte, padded bool) string { | |
| 247 | + | const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
| 248 | + | var b strings.Builder | |
| 249 | + | for i := 0; i < len(data); i += 3 { | |
| 250 | + | var chunk [3]byte | |
| 251 | + | n := copy(chunk[:], data[i:]) | |
| 252 | + | v := uint(chunk[0])<<16 | uint(chunk[1])<<8 | uint(chunk[2]) | |
| 253 | + | for j := range 4 { | |
| 254 | + | if j <= n { | |
| 255 | + | b.WriteByte(alphabet[(v>>(18-6*j))&63]) | |
| 256 | + | } else if padded { | |
| 257 | + | b.WriteByte('=') | |
| 258 | + | } | |
| 259 | + | } | |
| 260 | + | } | |
| 261 | + | return b.String() | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | func (o *oracleRun) write(data []byte) { | |
| 265 | + | if o.sc.byteSplit { | |
| 266 | + | for i := range data { | |
| 267 | + | o.screen.Write(data[i : i+1 : i+1]) | |
| 268 | + | } | |
| 269 | + | return | |
| 270 | + | } | |
| 271 | + | for len(data) > 0 { | |
| 272 | + | n := 1 + o.r.IntN(len(data)) | |
| 273 | + | if o.r.IntN(3) != 0 { | |
| 274 | + | n = len(data) | |
| 275 | + | } | |
| 276 | + | o.screen.Write(data[:n:n]) | |
| 277 | + | data = data[n:] | |
| 278 | + | } | |
| 279 | + | } | |
| 280 | + | ||
| 281 | + | func (o *oracleRun) maybeResize(step int) { | |
| 282 | + | if o.r.IntN(40) != 0 { | |
| 283 | + | return | |
| 284 | + | } | |
| 285 | + | cols, rows := 8+o.r.IntN(110), 3+o.r.IntN(40) | |
| 286 | + | o.emit("step %d resize %dx%d", step, cols, rows) | |
| 287 | + | o.screen.Resize(cols, rows) | |
| 288 | + | } | |
| 289 | + | ||
| 290 | + | func (o *oracleRun) checkScreen() { | |
| 291 | + | s := o.screen | |
| 292 | + | o.emit("screen %dx%d cursor=%d,%d vis=%t title=%q alt=%t", s.Columns(), s.Rows(), s.CursorRow(), s.CursorCol(), s.CursorVisible(), s.TerminalTitle(), s.AltScreenActive()) | |
| 293 | + | snap := s.Snapshot() | |
| 294 | + | o.emit("modes %+v cursor %+v next=%d", snap.Modes(), snap.Cursor(), snap.NextRowID()) | |
| 295 | + | for y := range s.Rows() { | |
| 296 | + | o.emit("row %d id=%d bound=%+v %s %q", y, s.RowID(y), snap.Bound(y), formatRow(s.RowCells(y)), rowText(s.RowCells(y))) | |
| 297 | + | snapRow := snap.Row(y) | |
| 298 | + | if formatRow(snapRow) != formatRow(s.RowCells(y)) { | |
| 299 | + | o.t.Fatalf("seed %d: snapshot row %d differs from live row", o.sc.seed, y) | |
| 300 | + | } | |
| 301 | + | } | |
| 302 | + | o.emit("bounds %+v", s.LineBounds()) | |
| 303 | + | } | |
| 304 | + | ||
| 305 | + | func (o *oracleRun) checkGraphics() { | |
| 306 | + | g := o.screen.GraphicsSnapshot() | |
| 307 | + | if g == nil { | |
| 308 | + | o.emit("graphics nil") | |
| 309 | + | return | |
| 310 | + | } | |
| 311 | + | o.emit("graphics gen=%d usage=%+v", g.Generation(), g.Usage()) | |
| 312 | + | for _, a := range g.Assets() { | |
| 313 | + | blob := a.Blob() | |
| 314 | + | sum := sha256.Sum256(blob.Encoded) | |
| 315 | + | o.emit("asset fmt=%v %dx%d px=%d sha=%x", blob.Format, blob.Width, blob.Height, blob.DecodedPixels, sum[:8]) | |
| 316 | + | } | |
| 317 | + | for _, p := range g.Placements() { | |
| 318 | + | o.emit("placement %+v", p.Spec()) | |
| 319 | + | } | |
| 320 | + | o.emit("fragments %+v", g.VisibleCellFragments(graphics.CellRect{Width: int64(o.screen.Columns()), Height: int64(o.screen.Rows())})) | |
| 321 | + | } | |
| 322 | + | ||
| 323 | + | func (o *oracleRun) checkHistory(deep bool) { | |
| 324 | + | h := o.screen.History() | |
| 325 | + | if h == nil { | |
| 326 | + | return | |
| 327 | + | } | |
| 328 | + | o.emit("history len=%d cells=%d bytes=%d next=%d stats=%+v", h.Len(), h.Cells(), h.LogicalBytes(), h.NextRowID(), h.CompressionStats()) | |
| 329 | + | if !deep { | |
| 330 | + | return | |
| 331 | + | } | |
| 332 | + | snapView := h.SnapshotView() | |
| 333 | + | tail, err := vt.MarshalHistoryTail(snapView) | |
| 334 | + | if err != nil { | |
| 335 | + | o.t.Fatalf("seed %d: MarshalHistoryTail: %v", o.sc.seed, err) | |
| 336 | + | } | |
| 337 | + | sum := sha256.Sum256(tail) | |
| 338 | + | o.emit("snapview len=%d chunks=%d tail=%x", snapView.Len(), snapView.ChunkCount(), sum) | |
| 339 | + | var sealed [][]byte | |
| 340 | + | for i := range snapView.ChunkCount() { | |
| 341 | + | blob, err := vt.MarshalHistoryChunk(snapView.Chunk(i)) | |
| 342 | + | if err != nil { | |
| 343 | + | o.t.Fatalf("seed %d: MarshalHistoryChunk: %v", o.sc.seed, err) | |
| 344 | + | } | |
| 345 | + | sum := sha256.Sum256(blob) | |
| 346 | + | o.emit("chunk %d %x", i, sum) | |
| 347 | + | sealed = append(sealed, blob) | |
| 348 | + | } | |
| 349 | + | view := h.View() | |
| 350 | + | var rows []string | |
| 351 | + | var texts []string | |
| 352 | + | if err := view.Range(func(row []core.Cell) bool { | |
| 353 | + | rows = append(rows, formatRow(row)) | |
| 354 | + | texts = append(texts, rowText(row)) | |
| 355 | + | return true | |
| 356 | + | }); err != nil { | |
| 357 | + | o.t.Fatalf("seed %d: history Range: %v", o.sc.seed, err) | |
| 358 | + | } | |
| 359 | + | for i, row := range rows { | |
| 360 | + | o.emit("hrow %d id=%d bound=%+v %s %q", i, view.RowID(i), view.Bound(i), row, texts[i]) | |
| 361 | + | if got := formatRow(view.Row(i)); got != row { | |
| 362 | + | o.t.Fatalf("seed %d: history Row(%d) differs from Range", o.sc.seed, i) | |
| 363 | + | } | |
| 364 | + | dst := make([]core.Cell, view.RowWidth(i)) | |
| 365 | + | view.CopyRow(i, dst) | |
| 366 | + | if formatRow(dst) != row { | |
| 367 | + | o.t.Fatalf("seed %d: history CopyRow(%d) differs from Range", o.sc.seed, i) | |
| 368 | + | } | |
| 369 | + | } | |
| 370 | + | restored, err := vt.HistoryFromBlobs(h.Limits(), sealed, tail) | |
| 371 | + | if err != nil { | |
| 372 | + | o.t.Fatalf("seed %d: HistoryFromBlobs: %v", o.sc.seed, err) | |
| 373 | + | } | |
| 374 | + | rview := restored.View() | |
| 375 | + | if rview.Len() != view.Len() || restored.NextRowID() != h.NextRowID() { | |
| 376 | + | o.t.Fatalf("seed %d: restored history len=%d next=%d want len=%d next=%d", o.sc.seed, rview.Len(), restored.NextRowID(), view.Len(), h.NextRowID()) | |
| 377 | + | } | |
| 378 | + | for i, row := range rows { | |
| 379 | + | if formatRow(rview.Row(i)) != row || rview.RowID(i) != view.RowID(i) || rview.Bound(i) != view.Bound(i) { | |
| 380 | + | o.t.Fatalf("seed %d: restored history row %d differs", o.sc.seed, i) | |
| 381 | + | } | |
| 382 | + | } | |
| 383 | + | transcript, err := o.screen.RecoveryTranscriptSnapshot().Marshal() | |
| 384 | + | if err != nil { | |
| 385 | + | o.t.Fatalf("seed %d: recovery transcript: %v", o.sc.seed, err) | |
| 386 | + | } | |
| 387 | + | tsum := sha256.Sum256(transcript) | |
| 388 | + | o.emit("transcript %x", tsum) | |
| 389 | + | recovered, err := vt.NewScreenWithRecoveryTranscript(o.screen.Columns(), o.screen.Rows(), h.Limits(), sealed, tail, transcript) | |
| 390 | + | if err != nil { | |
| 391 | + | o.t.Fatalf("seed %d: NewScreenWithRecoveryTranscript: %v", o.sc.seed, err) | |
| 392 | + | } | |
| 393 | + | rh := recovered.History() | |
| 394 | + | o.emit("recovered len=%d next=%d", rh.Len(), rh.NextRowID()) | |
| 395 | + | if rh.Len() > 0 { | |
| 396 | + | rv := rh.View() | |
| 397 | + | o.emit("recovered last %s", formatRow(rv.Row(rv.Len()-1))) | |
| 398 | + | } | |
| 399 | + | } | |
| 400 | + | ||
| 401 | + | func (o *oracleRun) render(step int) { | |
| 402 | + | s := o.screen | |
| 403 | + | damage := append([]core.Damage(nil), s.Damage()...) | |
| 404 | + | capture := s.CaptureDamage() | |
| 405 | + | if len(capture.Damage) != len(damage) { | |
| 406 | + | o.t.Fatalf("seed %d: CaptureDamage length %d want %d", o.sc.seed, len(capture.Damage), len(damage)) | |
| 407 | + | } | |
| 408 | + | o.emit("damage %+v", damage) | |
| 409 | + | ||
| 410 | + | if o.mirror.Width != s.Columns() || o.mirror.Height != s.Rows() { | |
| 411 | + | o.mirror = core.NewFrame(s.Columns(), s.Rows()) | |
| 412 | + | } | |
| 413 | + | for y := range s.Rows() { | |
| 414 | + | for x := range s.Columns() { | |
| 415 | + | o.mirror.Set(x, y, s.Cell(x, y)) | |
| 416 | + | } | |
| 417 | + | } | |
| 418 | + | var source core.CellSource = o.mirror | |
| 419 | + | switch o.sc.seed % 3 { | |
| 420 | + | case 1: | |
| 421 | + | source = &o.mirror | |
| 422 | + | case 2: | |
| 423 | + | source = s // generic CellSource path | |
| 424 | + | } | |
| 425 | + | prepared, err := o.ansiR.Prepare(source, damage, false) | |
| 426 | + | if err != nil { | |
| 427 | + | o.t.Fatalf("seed %d step %d: ansi Prepare: %v", o.sc.seed, step, err) | |
| 428 | + | } | |
| 429 | + | out := prepared.Bytes() | |
| 430 | + | o.emit("ansi %q", out) | |
| 431 | + | if o.r.IntN(7) == 0 { | |
| 432 | + | // Discarded draw: renderer must keep its committed shadow and damage | |
| 433 | + | // must remain pending for the next draw. | |
| 434 | + | o.emit("ansi discard") | |
| 435 | + | } else { | |
| 436 | + | prepared.Commit() | |
| 437 | + | o.applyReplay(out, step) | |
| 438 | + | if !s.AcknowledgeDamage(capture.Generation) { | |
| 439 | + | o.t.Fatalf("seed %d: acknowledge failed", o.sc.seed) | |
| 440 | + | } | |
| 441 | + | } | |
| 442 | + | other, err := o.ansi256.Draw(source, damage) | |
| 443 | + | if err != nil { | |
| 444 | + | o.t.Fatalf("seed %d: ansi256 Draw: %v", o.sc.seed, err) | |
| 445 | + | } | |
| 446 | + | o.emit("ansi256 %q", other) | |
| 447 | + | ||
| 448 | + | snap := s.Snapshot() | |
| 449 | + | cur := snap.Cursor() | |
| 450 | + | var htmlSource core.CellSource = o.mirror | |
| 451 | + | if o.sc.seed%3 == 1 { | |
| 452 | + | htmlSource = snap | |
| 453 | + | } | |
| 454 | + | hp, err := o.htmlR.Prepare(htmlSource, damage, false, html.Cursor{Row: cur.Row, Column: cur.Col, Visible: cur.Visible, Style: html.CursorStyle(cur.Style), StyleSet: cur.StyleSet}) | |
| 455 | + | if err != nil { | |
| 456 | + | o.emit("html err %v", err) | |
| 457 | + | return | |
| 458 | + | } | |
| 459 | + | o.emit("html %s", hp.JSON()) | |
| 460 | + | if o.r.IntN(6) == 0 { | |
| 461 | + | if err := hp.Abort(); err != nil { | |
| 462 | + | o.t.Fatalf("seed %d: html abort: %v", o.sc.seed, err) | |
| 463 | + | } | |
| 464 | + | o.emit("html abort") | |
| 465 | + | } else if err := hp.Commit(); err != nil { | |
| 466 | + | o.t.Fatalf("seed %d: html commit: %v", o.sc.seed, err) | |
| 467 | + | } | |
| 468 | + | } | |
| 469 | + | ||
| 470 | + | // applyReplay feeds committed ANSI output to an independent VT and compares | |
| 471 | + | // the visible grid with the source. Mismatches are known renderer defects on | |
| 472 | + | // the baseline (for example two scroll regions in one damage batch), so they | |
| 473 | + | // are hashed into the transcript instead of failing: a performance change must | |
| 474 | + | // neither add nor remove one. The replay is then resynchronized from a full | |
| 475 | + | // draw, matching the renderer's committed belief. | |
| 476 | + | func (o *oracleRun) applyReplay(out []byte, step int) { | |
| 477 | + | s := o.screen | |
| 478 | + | if o.replay == nil || o.replay.Columns() != s.Columns() || o.replay.Rows() != s.Rows() { | |
| 479 | + | o.replay = vt.NewScreen(s.Columns(), s.Rows()) | |
| 480 | + | } | |
| 481 | + | o.replay.Write(out) | |
| 482 | + | for y := range s.Rows() { | |
| 483 | + | for x := range s.Columns() { | |
| 484 | + | want, got := s.Cell(x, y), o.replay.Cell(x, y) | |
| 485 | + | if want.Rune != got.Rune || want.Continuation != got.Continuation || !want.Style.Equal(got.Style) { | |
| 486 | + | o.replayMismatches++ | |
| 487 | + | o.emit("replay-mismatch step %d at (%d,%d): got %s want %s", step, x, y, formatCell(got), formatCell(want)) | |
| 488 | + | o.replay = vt.NewScreen(s.Columns(), s.Rows()) | |
| 489 | + | full, err := ansi.New(ansi.Capabilities{}).Draw(s, []core.Damage{core.FullRedraw()}) | |
| 490 | + | if err != nil { | |
| 491 | + | o.t.Fatalf("seed %d: resync draw: %v", o.sc.seed, err) | |
| 492 | + | } | |
| 493 | + | o.replay.Write(full) | |
| 494 | + | return | |
| 495 | + | } | |
| 496 | + | } | |
| 497 | + | } | |
| 498 | + | } | |
| 499 | + | ||
| 500 | + | func runOracleScenario(t *testing.T, idx int) string { | |
| 501 | + | sc := oracleScenarioFor(idx) | |
| 502 | + | o := &oracleRun{t: t, sc: sc, r: rand.New(rand.NewPCG(sc.seed, 0xdecaf)), h: sha256.New()} | |
| 503 | + | if *oracleDump != "" { | |
| 504 | + | if err := os.MkdirAll(*oracleDump, 0o755); err != nil { | |
| 505 | + | t.Fatal(err) | |
| 506 | + | } | |
| 507 | + | f, err := os.Create(filepath.Join(*oracleDump, fmt.Sprintf("scenario-%02d.txt", idx))) | |
| 508 | + | if err != nil { | |
| 509 | + | t.Fatal(err) | |
| 510 | + | } | |
| 511 | + | defer f.Close() | |
| 512 | + | o.dump = bufio.NewWriter(f) | |
| 513 | + | defer o.dump.Flush() | |
| 514 | + | } | |
| 515 | + | if sc.history != nil { | |
| 516 | + | o.screen = vt.NewScreenWithHistory(sc.cols, sc.rows, *sc.history) | |
| 517 | + | } else { | |
| 518 | + | o.screen = vt.NewScreen(sc.cols, sc.rows) | |
| 519 | + | } | |
| 520 | + | o.screen.OnResponse = func(b []byte) { o.events = append(o.events, fmt.Sprintf("response %q", b)) } | |
| 521 | + | o.screen.OnBell = func() { o.events = append(o.events, "bell") } | |
| 522 | + | if sc.evictHook { | |
| 523 | + | // The callback owns each row: retain it, verify it is never rewritten by | |
| 524 | + | // later evictions, then scribble on it to prove VT kept its own copy. | |
| 525 | + | var retained []core.Cell | |
| 526 | + | var retainedDigest string | |
| 527 | + | o.screen.OnLineEvicted = func(row []core.Cell) { | |
| 528 | + | if retained != nil && formatRow(retained) != retainedDigest { | |
| 529 | + | t.Fatalf("seed %d: evicted row storage was reused after the callback", sc.seed) | |
| 530 | + | } | |
| 531 | + | digest := formatRow(row) | |
| 532 | + | o.events = append(o.events, "evicted "+digest+" "+rowText(row)) | |
| 533 | + | if prev := retained; prev != nil { | |
| 534 | + | for i := range prev { | |
| 535 | + | prev[i] = core.Cell{Rune: 'X'} | |
| 536 | + | } | |
| 537 | + | } | |
| 538 | + | retained, retainedDigest = row, digest | |
| 539 | + | } | |
| 540 | + | } | |
| 541 | + | o.ansiR = ansi.New(ansi.Capabilities{SynchronizedOutput: idx%2 == 0}) | |
| 542 | + | o.ansi256 = ansi.NewWithColorProfile(ansi.Capabilities{}, ansi.ColorProfileANSI256) | |
| 543 | + | var err error | |
| 544 | + | o.htmlR, err = html.New(html.Options{}) | |
| 545 | + | if err != nil { | |
| 546 | + | t.Fatal(err) | |
| 547 | + | } | |
| 548 | + | var historyConfig vt.HistoryConfig | |
| 549 | + | if sc.history != nil { | |
| 550 | + | historyConfig = *sc.history | |
| 551 | + | } | |
| 552 | + | o.emit("scenario %d %dx%d history=%+v byteSplit=%t evictHook=%t", idx, sc.cols, sc.rows, historyConfig, sc.byteSplit, sc.evictHook) | |
| 553 | + | ||
| 554 | + | var digests []string | |
| 555 | + | for step := range oracleSteps { | |
| 556 | + | o.maybeResize(step) | |
| 557 | + | chunk := o.genChunk() | |
| 558 | + | o.emit("step %d write %q", step, chunk) | |
| 559 | + | o.write(chunk) | |
| 560 | + | for _, e := range o.events { | |
| 561 | + | o.emit("%s", e) | |
| 562 | + | } | |
| 563 | + | o.events = o.events[:0] | |
| 564 | + | o.render(step) | |
| 565 | + | if step%oracleCheckEvery == oracleCheckEvery-1 { | |
| 566 | + | o.checkScreen() | |
| 567 | + | o.checkGraphics() | |
| 568 | + | o.checkHistory(step%(2*oracleCheckEvery) == 2*oracleCheckEvery-1) | |
| 569 | + | digests = append(digests, fmt.Sprintf("%02d/%03d %s", idx, step, hex.EncodeToString(o.h.Sum(nil))[:24])) | |
| 570 | + | } | |
| 571 | + | } | |
| 572 | + | digests = append(digests, fmt.Sprintf("%02d replay-mismatches=%d", idx, o.replayMismatches)) | |
| 573 | + | return strings.Join(digests, "\n") | |
| 574 | + | } | |
| 575 | + | ||
| 576 | + | func TestDifferentialOracle(t *testing.T) { | |
| 577 | + | if testing.Short() { | |
| 578 | + | t.Skip("differential oracle skipped in -short mode") | |
| 579 | + | } | |
| 580 | + | got := make([]string, oracleScenarios) | |
| 581 | + | t.Run("scenarios", func(t *testing.T) { | |
| 582 | + | for i := range oracleScenarios { | |
| 583 | + | t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { | |
| 584 | + | t.Parallel() | |
| 585 | + | got[i] = runOracleScenario(t, i) | |
| 586 | + | }) | |
| 587 | + | } | |
| 588 | + | }) | |
| 589 | + | if t.Failed() { | |
| 590 | + | return | |
| 591 | + | } | |
| 592 | + | text := strings.Join(got, "\n") + "\n" | |
| 593 | + | if *oracleUpdate { | |
| 594 | + | if err := os.MkdirAll(filepath.Dir(oracleGolden), 0o755); err != nil { | |
| 595 | + | t.Fatal(err) | |
| 596 | + | } | |
| 597 | + | if err := os.WriteFile(oracleGolden, []byte(text), 0o644); err != nil { | |
| 598 | + | t.Fatal(err) | |
| 599 | + | } | |
| 600 | + | return | |
| 601 | + | } | |
| 602 | + | want, err := os.ReadFile(oracleGolden) | |
| 603 | + | if err != nil { | |
| 604 | + | t.Fatalf("read golden (run with -oracle.update on a trusted baseline): %v", err) | |
| 605 | + | } | |
| 606 | + | if string(want) == text { | |
| 607 | + | return | |
| 608 | + | } | |
| 609 | + | wantLines, gotLines := strings.Split(string(want), "\n"), strings.Split(text, "\n") | |
| 610 | + | for i := range min(len(wantLines), len(gotLines)) { | |
| 611 | + | if wantLines[i] != gotLines[i] { | |
| 612 | + | t.Fatalf("oracle diverged at checkpoint %q (want %q); rerun with -oracle.dump=DIR on both trees and diff the scenario transcript", gotLines[i], wantLines[i]) | |
| 613 | + | } | |
| 614 | + | } | |
| 615 | + | t.Fatalf("oracle checkpoint count differs: got %d want %d", len(gotLines), len(wantLines)) | |
| 616 | + | } | |
| @@ -0,0 +1,616 @@ | |||||
| 1 | + | package vt_test | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bufio" | |||
| 5 | + | "bytes" | |||
| 6 | + | "crypto/sha256" | |||
| 7 | + | "encoding/hex" | |||
| 8 | + | "flag" | |||
| 9 | + | "fmt" | |||
| 10 | + | "hash" | |||
| 11 | + | "math/rand/v2" | |||
| 12 | + | "os" | |||
| 13 | + | "path/filepath" | |||
| 14 | + | "strings" | |||
| 15 | + | "testing" | |||
| 16 | + | ||||
| 17 | + | vt "github.com/bnema/vev-vt" | |||
| 18 | + | "github.com/bnema/vev-vt/ansi" | |||
| 19 | + | core "github.com/bnema/vev-vt/core" | |||
| 20 | + | "github.com/bnema/vev-vt/graphics" | |||
| 21 | + | "github.com/bnema/vev-vt/html" | |||
| 22 | + | ) | |||
| 23 | + | ||||
| 24 | + | // The differential oracle drives seeded terminal workloads through the public | |||
| 25 | + | // pipeline (VT parsing, damage, history, codecs, ANSI and HTML renderers) and | |||
| 26 | + | // hashes every observable result at checkpoints. Performance work must keep | |||
| 27 | + | // the golden file byte-identical; behavior changes must regenerate it with | |||
| 28 | + | // -oracle.update and justify the difference. | |||
| 29 | + | var ( | |||
| 30 | + | oracleUpdate = flag.Bool("oracle.update", false, "rewrite testdata/oracle/golden.txt") | |||
| 31 | + | oracleDump = flag.String("oracle.dump", "", "write full plain-text transcripts to this directory") | |||
| 32 | + | ) | |||
| 33 | + | ||||
| 34 | + | const ( | |||
| 35 | + | oracleScenarios = 36 | |||
| 36 | + | oracleSteps = 240 | |||
| 37 | + | oracleCheckEvery = 20 | |||
| 38 | + | oracleGolden = "testdata/oracle/golden.txt" | |||
| 39 | + | ) | |||
| 40 | + | ||||
| 41 | + | type oracleScenario struct { | |||
| 42 | + | seed uint64 | |||
| 43 | + | cols int | |||
| 44 | + | rows int | |||
| 45 | + | history *vt.HistoryConfig | |||
| 46 | + | byteSplit bool | |||
| 47 | + | evictHook bool | |||
| 48 | + | } | |||
| 49 | + | ||||
| 50 | + | func oracleScenarioFor(i int) oracleScenario { | |||
| 51 | + | r := rand.New(rand.NewPCG(uint64(i), 0x5eed)) | |||
| 52 | + | sc := oracleScenario{seed: uint64(i), cols: 8 + r.IntN(110), rows: 3 + r.IntN(40)} | |||
| 53 | + | switch i % 4 { | |||
| 54 | + | case 1: | |||
| 55 | + | sc.history = &vt.HistoryConfig{MaxRows: 50 + r.IntN(400), ChunkRows: 1 + r.IntN(16)} | |||
| 56 | + | case 2: | |||
| 57 | + | sc.history = &vt.HistoryConfig{MaxBytes: uint64(64<<10 + r.IntN(512<<10)), ChunkRows: 1 + r.IntN(64)} | |||
| 58 | + | case 3: | |||
| 59 | + | sc.history = &vt.HistoryConfig{MaxRows: 2000, MaxBytes: 4 << 20} | |||
| 60 | + | } | |||
| 61 | + | sc.byteSplit = i%5 == 0 | |||
| 62 | + | sc.evictHook = i%3 == 0 | |||
| 63 | + | return sc | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | type oracleRun struct { | |||
| 67 | + | t *testing.T | |||
| 68 | + | sc oracleScenario | |||
| 69 | + | r *rand.Rand | |||
| 70 | + | screen *vt.Screen | |||
| 71 | + | ansiR *ansi.Renderer | |||
| 72 | + | ansi256 *ansi.Renderer | |||
| 73 | + | htmlR *html.Renderer | |||
| 74 | + | replay *vt.Screen | |||
| 75 | + | // mirror reproduces the vev daemon consumer: one core.Frame reused and | |||
| 76 | + | // mutated in place between draws, so renderer shadows must not alias it. | |||
| 77 | + | mirror core.Frame | |||
| 78 | + | h hash.Hash | |||
| 79 | + | dump *bufio.Writer | |||
| 80 | + | events []string | |||
| 81 | + | ||||
| 82 | + | replayMismatches int | |||
| 83 | + | } | |||
| 84 | + | ||||
| 85 | + | func (o *oracleRun) emit(format string, args ...any) { | |||
| 86 | + | line := fmt.Sprintf(format, args...) | |||
| 87 | + | o.h.Write([]byte(line)) | |||
| 88 | + | o.h.Write([]byte{'\n'}) | |||
| 89 | + | if o.dump != nil { | |||
| 90 | + | o.dump.WriteString(line) | |||
| 91 | + | o.dump.WriteByte('\n') | |||
| 92 | + | } | |||
| 93 | + | } | |||
| 94 | + | ||||
| 95 | + | func formatCell(c core.Cell) string { | |||
| 96 | + | return fmt.Sprintf("%q|%+v|%q|%q|%t", c.Rune, c.Style, c.Payload.Grapheme(), c.Payload.Hyperlink(), c.Continuation) | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | // formatRow is a compact exact digest of a row: every Cell field contributes. | |||
| 100 | + | // Full text is only produced for dumps, where it is needed for diagnosis. | |||
| 101 | + | func formatRow(row []core.Cell) string { | |||
| 102 | + | h := sha256.New() | |||
| 103 | + | var buf []byte | |||
| 104 | + | for _, c := range row { | |||
| 105 | + | buf = fmt.Appendf(buf[:0], "%d|%+v|%s|%s|%t;", c.Rune, c.Style, c.Payload.Grapheme(), c.Payload.Hyperlink(), c.Continuation) | |||
| 106 | + | h.Write(buf) | |||
| 107 | + | } | |||
| 108 | + | return hex.EncodeToString(h.Sum(nil)[:12]) | |||
| 109 | + | } | |||
| 110 | + | ||||
| 111 | + | func rowText(row []core.Cell) string { | |||
| 112 | + | var b strings.Builder | |||
| 113 | + | for _, c := range row { | |||
| 114 | + | if c.Continuation { | |||
| 115 | + | continue | |||
| 116 | + | } | |||
| 117 | + | if g := c.Payload.Grapheme(); g != "" { | |||
| 118 | + | b.WriteString(g) | |||
| 119 | + | } else if c.Rune != 0 { | |||
| 120 | + | b.WriteRune(c.Rune) | |||
| 121 | + | } | |||
| 122 | + | } | |||
| 123 | + | return b.String() | |||
| 124 | + | } | |||
| 125 | + | ||||
| 126 | + | func (o *oracleRun) genChunk() []byte { | |||
| 127 | + | r := o.r | |||
| 128 | + | var b bytes.Buffer | |||
| 129 | + | for range 1 + r.IntN(6) { | |||
| 130 | + | switch op := r.IntN(100); { | |||
| 131 | + | case op < 22: | |||
| 132 | + | words := []string{"hello", "world", "vev", "ls -la", "❯", "foo/bar", "λx", "0123456789"} | |||
| 133 | + | for range 1 + r.IntN(8) { | |||
| 134 | + | b.WriteString(words[r.IntN(len(words))]) | |||
| 135 | + | b.WriteByte(' ') | |||
| 136 | + | } | |||
| 137 | + | if r.IntN(2) == 0 { | |||
| 138 | + | b.WriteString("\r\n") | |||
| 139 | + | } | |||
| 140 | + | case op < 30: | |||
| 141 | + | uni := []string{"界", "🙂", "é", "e\u0301", "한국어", "👍🏽", "a\u200db", "ff"} | |||
| 142 | + | for range 1 + r.IntN(10) { | |||
| 143 | + | b.WriteString(uni[r.IntN(len(uni))]) | |||
| 144 | + | } | |||
| 145 | + | case op < 42: | |||
| 146 | + | b.WriteString(randomSGR(r)) | |||
| 147 | + | case op < 50: | |||
| 148 | + | fmt.Fprintf(&b, "\x1b[%d;%dH", 1+r.IntN(o.sc.rows+2), 1+r.IntN(o.sc.cols+2)) | |||
| 149 | + | case op < 56: | |||
| 150 | + | seqs := []string{"\x1b[K", "\x1b[1K", "\x1b[2K", "\x1b[J", "\x1b[2J", "\x1b[3@", "\x1b[2P", "\x1b[L", "\x1b[2M", "\x1b[4X", "\x1b[S", "\x1b[2T", "\x1bM", "\x1bD", "\x1bE", "\t", "\b"} | |||
| 151 | + | b.WriteString(seqs[r.IntN(len(seqs))]) | |||
| 152 | + | case op < 64: | |||
| 153 | + | for range 1 + r.IntN(3*o.sc.rows) { | |||
| 154 | + | fmt.Fprintf(&b, "line %d %s\r\n", r.IntN(1000), strings.Repeat("=", r.IntN(o.sc.cols+5))) | |||
| 155 | + | } | |||
| 156 | + | case op < 68: | |||
| 157 | + | top := 1 + r.IntN(o.sc.rows) | |||
| 158 | + | bottom := top + r.IntN(o.sc.rows-top+1) | |||
| 159 | + | fmt.Fprintf(&b, "\x1b[%d;%dr\x1b[%d;1H", top, bottom, bottom) | |||
| 160 | + | for range 1 + r.IntN(6) { | |||
| 161 | + | b.WriteString("region\n") | |||
| 162 | + | } | |||
| 163 | + | if r.IntN(2) == 0 { | |||
| 164 | + | b.WriteString("\x1b[r") | |||
| 165 | + | } | |||
| 166 | + | case op < 71: | |||
| 167 | + | if r.IntN(2) == 0 { | |||
| 168 | + | b.WriteString("\x1b[?1049h") | |||
| 169 | + | } else { | |||
| 170 | + | b.WriteString("\x1b[?1049l") | |||
| 171 | + | } | |||
| 172 | + | case op < 74: | |||
| 173 | + | fmt.Fprintf(&b, "\x1b]8;;https://example.invalid/%d\x1b\\link\x1b]8;;\x1b\\", r.IntN(9)) | |||
| 174 | + | case op < 76: | |||
| 175 | + | fmt.Fprintf(&b, "\x1b]0;title %d\x07", r.IntN(99)) | |||
| 176 | + | case op < 81: | |||
| 177 | + | b.WriteString(randomKitty(r)) | |||
| 178 | + | case op < 84: | |||
| 179 | + | queries := []string{"\x1b[6n", "\x1b[c", "\x1b[?2004$p", "\x1b[5n"} | |||
| 180 | + | b.WriteString(queries[r.IntN(len(queries))]) | |||
| 181 | + | case op < 86: | |||
| 182 | + | if r.IntN(2) == 0 { | |||
| 183 | + | b.WriteString("\x1b[?7l") | |||
| 184 | + | } else { | |||
| 185 | + | b.WriteString("\x1b[?7h") | |||
| 186 | + | } | |||
| 187 | + | case op < 91: | |||
| 188 | + | // High-cardinality truecolor burst. | |||
| 189 | + | for range 1 + r.IntN(40) { | |||
| 190 | + | fmt.Fprintf(&b, "\x1b[38;2;%d;%d;%dm#", r.IntN(256), r.IntN(256), r.IntN(256)) | |||
| 191 | + | } | |||
| 192 | + | b.WriteString("\x1b[0m") | |||
| 193 | + | case op < 94: | |||
| 194 | + | // Styled blanks and erases inherit the current background. | |||
| 195 | + | fmt.Fprintf(&b, "\x1b[48;5;%dm\x1b[K \x1b[0m", r.IntN(256)) | |||
| 196 | + | default: | |||
| 197 | + | fmt.Fprintf(&b, "\r\x1b[K❯ abc\x1b[90m suggestion\x1b[39m\r\x1b[%dC", r.IntN(8)) | |||
| 198 | + | } | |||
| 199 | + | } | |||
| 200 | + | return b.Bytes() | |||
| 201 | + | } | |||
| 202 | + | ||||
| 203 | + | func randomSGR(r *rand.Rand) string { | |||
| 204 | + | parts := []string{"0", "1", "2", "3", "4", "4:3", "5", "7", "9", "22", "23", "24", "27", "39", "49", "59"} | |||
| 205 | + | switch r.IntN(6) { | |||
| 206 | + | case 0: | |||
| 207 | + | return fmt.Sprintf("\x1b[%dm", 30+r.IntN(8)) | |||
| 208 | + | case 1: | |||
| 209 | + | return fmt.Sprintf("\x1b[38;5;%d;48;5;%dm", r.IntN(256), r.IntN(256)) | |||
| 210 | + | case 2: | |||
| 211 | + | return fmt.Sprintf("\x1b[38;2;%d;%d;%dm", r.IntN(256), r.IntN(256), r.IntN(256)) | |||
| 212 | + | case 3: | |||
| 213 | + | return fmt.Sprintf("\x1b[58;2;%d;%d;%d;4:%dm", r.IntN(256), r.IntN(256), r.IntN(256), r.IntN(6)) | |||
| 214 | + | default: | |||
| 215 | + | return "\x1b[" + parts[r.IntN(len(parts))] + "m" | |||
| 216 | + | } | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | func randomKitty(r *rand.Rand) string { | |||
| 220 | + | switch r.IntN(4) { | |||
| 221 | + | case 0: | |||
| 222 | + | w, h := 1+r.IntN(4), 1+r.IntN(4) | |||
| 223 | + | pixels := make([]byte, w*h*4) | |||
| 224 | + | for i := range pixels { | |||
| 225 | + | pixels[i] = byte(r.IntN(256)) | |||
| 226 | + | } | |||
| 227 | + | enc := encodeBase64(pixels, r.IntN(2) == 0) | |||
| 228 | + | return fmt.Sprintf("\x1b_Ga=T,i=%d,f=32,s=%d,v=%d,c=%d,r=%d,q=%d;%s\x1b\\", 1+r.IntN(4), w, h, 1+r.IntN(3), 1+r.IntN(2), r.IntN(3), enc) | |||
| 229 | + | case 1: | |||
| 230 | + | // Chunked transmission. | |||
| 231 | + | pixels := make([]byte, 4*4*4) | |||
| 232 | + | for i := range pixels { | |||
| 233 | + | pixels[i] = byte(r.IntN(256)) | |||
| 234 | + | } | |||
| 235 | + | enc := encodeBase64(pixels, true) | |||
| 236 | + | mid := (len(enc) / 8) * 4 | |||
| 237 | + | return fmt.Sprintf("\x1b_Ga=t,i=%d,f=32,s=4,v=4,m=1;%s\x1b\\\x1b_Gm=0;%s\x1b\\\x1b_Ga=p,i=%d\x1b\\", 5+r.IntN(3), enc[:mid], enc[mid:], 5+r.IntN(3)) | |||
| 238 | + | case 2: | |||
| 239 | + | return fmt.Sprintf("\x1b_Ga=d,d=%s\x1b\\", []string{"a", "A", "i", "I"}[r.IntN(4)]) | |||
| 240 | + | default: | |||
| 241 | + | // Invalid base64 must be rejected identically. | |||
| 242 | + | return "\x1b_Ga=T,f=32,s=1,v=1;!!!\x1b\\" | |||
| 243 | + | } | |||
| 244 | + | } | |||
| 245 | + | ||||
| 246 | + | func encodeBase64(data []byte, padded bool) string { | |||
| 247 | + | const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |||
| 248 | + | var b strings.Builder | |||
| 249 | + | for i := 0; i < len(data); i += 3 { | |||
| 250 | + | var chunk [3]byte | |||
| 251 | + | n := copy(chunk[:], data[i:]) | |||
| 252 | + | v := uint(chunk[0])<<16 | uint(chunk[1])<<8 | uint(chunk[2]) | |||
| 253 | + | for j := range 4 { | |||
| 254 | + | if j <= n { | |||
| 255 | + | b.WriteByte(alphabet[(v>>(18-6*j))&63]) | |||
| 256 | + | } else if padded { | |||
| 257 | + | b.WriteByte('=') | |||
| 258 | + | } | |||
| 259 | + | } | |||
| 260 | + | } | |||
| 261 | + | return b.String() | |||
| 262 | + | } | |||
| 263 | + | ||||
| 264 | + | func (o *oracleRun) write(data []byte) { | |||
| 265 | + | if o.sc.byteSplit { | |||
| 266 | + | for i := range data { | |||
| 267 | + | o.screen.Write(data[i : i+1 : i+1]) | |||
| 268 | + | } | |||
| 269 | + | return | |||
| 270 | + | } | |||
| 271 | + | for len(data) > 0 { | |||
| 272 | + | n := 1 + o.r.IntN(len(data)) | |||
| 273 | + | if o.r.IntN(3) != 0 { | |||
| 274 | + | n = len(data) | |||
| 275 | + | } | |||
| 276 | + | o.screen.Write(data[:n:n]) | |||
| 277 | + | data = data[n:] | |||
| 278 | + | } | |||
| 279 | + | } | |||
| 280 | + | ||||
| 281 | + | func (o *oracleRun) maybeResize(step int) { | |||
| 282 | + | if o.r.IntN(40) != 0 { | |||
| 283 | + | return | |||
| 284 | + | } | |||
| 285 | + | cols, rows := 8+o.r.IntN(110), 3+o.r.IntN(40) | |||
| 286 | + | o.emit("step %d resize %dx%d", step, cols, rows) | |||
| 287 | + | o.screen.Resize(cols, rows) | |||
| 288 | + | } | |||
| 289 | + | ||||
| 290 | + | func (o *oracleRun) checkScreen() { | |||
| 291 | + | s := o.screen | |||
| 292 | + | o.emit("screen %dx%d cursor=%d,%d vis=%t title=%q alt=%t", s.Columns(), s.Rows(), s.CursorRow(), s.CursorCol(), s.CursorVisible(), s.TerminalTitle(), s.AltScreenActive()) | |||
| 293 | + | snap := s.Snapshot() | |||
| 294 | + | o.emit("modes %+v cursor %+v next=%d", snap.Modes(), snap.Cursor(), snap.NextRowID()) | |||
| 295 | + | for y := range s.Rows() { | |||
| 296 | + | o.emit("row %d id=%d bound=%+v %s %q", y, s.RowID(y), snap.Bound(y), formatRow(s.RowCells(y)), rowText(s.RowCells(y))) | |||
| 297 | + | snapRow := snap.Row(y) | |||
| 298 | + | if formatRow(snapRow) != formatRow(s.RowCells(y)) { | |||
| 299 | + | o.t.Fatalf("seed %d: snapshot row %d differs from live row", o.sc.seed, y) | |||
| 300 | + | } | |||
| 301 | + | } | |||
| 302 | + | o.emit("bounds %+v", s.LineBounds()) | |||
| 303 | + | } | |||
| 304 | + | ||||
| 305 | + | func (o *oracleRun) checkGraphics() { | |||
| 306 | + | g := o.screen.GraphicsSnapshot() | |||
| 307 | + | if g == nil { | |||
| 308 | + | o.emit("graphics nil") | |||
| 309 | + | return | |||
| 310 | + | } | |||
| 311 | + | o.emit("graphics gen=%d usage=%+v", g.Generation(), g.Usage()) | |||
| 312 | + | for _, a := range g.Assets() { | |||
| 313 | + | blob := a.Blob() | |||
| 314 | + | sum := sha256.Sum256(blob.Encoded) | |||
| 315 | + | o.emit("asset fmt=%v %dx%d px=%d sha=%x", blob.Format, blob.Width, blob.Height, blob.DecodedPixels, sum[:8]) | |||
| 316 | + | } | |||
| 317 | + | for _, p := range g.Placements() { | |||
| 318 | + | o.emit("placement %+v", p.Spec()) | |||
| 319 | + | } | |||
| 320 | + | o.emit("fragments %+v", g.VisibleCellFragments(graphics.CellRect{Width: int64(o.screen.Columns()), Height: int64(o.screen.Rows())})) | |||
| 321 | + | } | |||
| 322 | + | ||||
| 323 | + | func (o *oracleRun) checkHistory(deep bool) { | |||
| 324 | + | h := o.screen.History() | |||
| 325 | + | if h == nil { | |||
| 326 | + | return | |||
| 327 | + | } | |||
| 328 | + | o.emit("history len=%d cells=%d bytes=%d next=%d stats=%+v", h.Len(), h.Cells(), h.LogicalBytes(), h.NextRowID(), h.CompressionStats()) | |||
| 329 | + | if !deep { | |||
| 330 | + | return | |||
| 331 | + | } | |||
| 332 | + | snapView := h.SnapshotView() | |||
| 333 | + | tail, err := vt.MarshalHistoryTail(snapView) | |||
| 334 | + | if err != nil { | |||
| 335 | + | o.t.Fatalf("seed %d: MarshalHistoryTail: %v", o.sc.seed, err) | |||
| 336 | + | } | |||
| 337 | + | sum := sha256.Sum256(tail) | |||
| 338 | + | o.emit("snapview len=%d chunks=%d tail=%x", snapView.Len(), snapView.ChunkCount(), sum) | |||
| 339 | + | var sealed [][]byte | |||
| 340 | + | for i := range snapView.ChunkCount() { | |||
| 341 | + | blob, err := vt.MarshalHistoryChunk(snapView.Chunk(i)) | |||
| 342 | + | if err != nil { | |||
| 343 | + | o.t.Fatalf("seed %d: MarshalHistoryChunk: %v", o.sc.seed, err) | |||
| 344 | + | } | |||
| 345 | + | sum := sha256.Sum256(blob) | |||
| 346 | + | o.emit("chunk %d %x", i, sum) | |||
| 347 | + | sealed = append(sealed, blob) | |||
| 348 | + | } | |||
| 349 | + | view := h.View() | |||
| 350 | + | var rows []string | |||
| 351 | + | var texts []string | |||
| 352 | + | if err := view.Range(func(row []core.Cell) bool { | |||
| 353 | + | rows = append(rows, formatRow(row)) | |||
| 354 | + | texts = append(texts, rowText(row)) | |||
| 355 | + | return true | |||
| 356 | + | }); err != nil { | |||
| 357 | + | o.t.Fatalf("seed %d: history Range: %v", o.sc.seed, err) | |||
| 358 | + | } | |||
| 359 | + | for i, row := range rows { | |||
| 360 | + | o.emit("hrow %d id=%d bound=%+v %s %q", i, view.RowID(i), view.Bound(i), row, texts[i]) | |||
| 361 | + | if got := formatRow(view.Row(i)); got != row { | |||
| 362 | + | o.t.Fatalf("seed %d: history Row(%d) differs from Range", o.sc.seed, i) | |||
| 363 | + | } | |||
| 364 | + | dst := make([]core.Cell, view.RowWidth(i)) | |||
| 365 | + | view.CopyRow(i, dst) | |||
| 366 | + | if formatRow(dst) != row { | |||
| 367 | + | o.t.Fatalf("seed %d: history CopyRow(%d) differs from Range", o.sc.seed, i) | |||
| 368 | + | } | |||
| 369 | + | } | |||
| 370 | + | restored, err := vt.HistoryFromBlobs(h.Limits(), sealed, tail) | |||
| 371 | + | if err != nil { | |||
| 372 | + | o.t.Fatalf("seed %d: HistoryFromBlobs: %v", o.sc.seed, err) | |||
| 373 | + | } | |||
| 374 | + | rview := restored.View() | |||
| 375 | + | if rview.Len() != view.Len() || restored.NextRowID() != h.NextRowID() { | |||
| 376 | + | o.t.Fatalf("seed %d: restored history len=%d next=%d want len=%d next=%d", o.sc.seed, rview.Len(), restored.NextRowID(), view.Len(), h.NextRowID()) | |||
| 377 | + | } | |||
| 378 | + | for i, row := range rows { | |||
| 379 | + | if formatRow(rview.Row(i)) != row || rview.RowID(i) != view.RowID(i) || rview.Bound(i) != view.Bound(i) { | |||
| 380 | + | o.t.Fatalf("seed %d: restored history row %d differs", o.sc.seed, i) | |||
| 381 | + | } | |||
| 382 | + | } | |||
| 383 | + | transcript, err := o.screen.RecoveryTranscriptSnapshot().Marshal() | |||
| 384 | + | if err != nil { | |||
| 385 | + | o.t.Fatalf("seed %d: recovery transcript: %v", o.sc.seed, err) | |||
| 386 | + | } | |||
| 387 | + | tsum := sha256.Sum256(transcript) | |||
| 388 | + | o.emit("transcript %x", tsum) | |||
| 389 | + | recovered, err := vt.NewScreenWithRecoveryTranscript(o.screen.Columns(), o.screen.Rows(), h.Limits(), sealed, tail, transcript) | |||
| 390 | + | if err != nil { | |||
| 391 | + | o.t.Fatalf("seed %d: NewScreenWithRecoveryTranscript: %v", o.sc.seed, err) | |||
| 392 | + | } | |||
| 393 | + | rh := recovered.History() | |||
| 394 | + | o.emit("recovered len=%d next=%d", rh.Len(), rh.NextRowID()) | |||
| 395 | + | if rh.Len() > 0 { | |||
| 396 | + | rv := rh.View() | |||
| 397 | + | o.emit("recovered last %s", formatRow(rv.Row(rv.Len()-1))) | |||
| 398 | + | } | |||
| 399 | + | } | |||
| 400 | + | ||||
| 401 | + | func (o *oracleRun) render(step int) { | |||
| 402 | + | s := o.screen | |||
| 403 | + | damage := append([]core.Damage(nil), s.Damage()...) | |||
| 404 | + | capture := s.CaptureDamage() | |||
| 405 | + | if len(capture.Damage) != len(damage) { | |||
| 406 | + | o.t.Fatalf("seed %d: CaptureDamage length %d want %d", o.sc.seed, len(capture.Damage), len(damage)) | |||
| 407 | + | } | |||
| 408 | + | o.emit("damage %+v", damage) | |||
| 409 | + | ||||
| 410 | + | if o.mirror.Width != s.Columns() || o.mirror.Height != s.Rows() { | |||
| 411 | + | o.mirror = core.NewFrame(s.Columns(), s.Rows()) | |||
| 412 | + | } | |||
| 413 | + | for y := range s.Rows() { | |||
| 414 | + | for x := range s.Columns() { | |||
| 415 | + | o.mirror.Set(x, y, s.Cell(x, y)) | |||
| 416 | + | } | |||
| 417 | + | } | |||
| 418 | + | var source core.CellSource = o.mirror | |||
| 419 | + | switch o.sc.seed % 3 { | |||
| 420 | + | case 1: | |||
| 421 | + | source = &o.mirror | |||
| 422 | + | case 2: | |||
| 423 | + | source = s // generic CellSource path | |||
| 424 | + | } | |||
| 425 | + | prepared, err := o.ansiR.Prepare(source, damage, false) | |||
| 426 | + | if err != nil { | |||
| 427 | + | o.t.Fatalf("seed %d step %d: ansi Prepare: %v", o.sc.seed, step, err) | |||
| 428 | + | } | |||
| 429 | + | out := prepared.Bytes() | |||
| 430 | + | o.emit("ansi %q", out) | |||
| 431 | + | if o.r.IntN(7) == 0 { | |||
| 432 | + | // Discarded draw: renderer must keep its committed shadow and damage | |||
| 433 | + | // must remain pending for the next draw. | |||
| 434 | + | o.emit("ansi discard") | |||
| 435 | + | } else { | |||
| 436 | + | prepared.Commit() | |||
| 437 | + | o.applyReplay(out, step) | |||
| 438 | + | if !s.AcknowledgeDamage(capture.Generation) { | |||
| 439 | + | o.t.Fatalf("seed %d: acknowledge failed", o.sc.seed) | |||
| 440 | + | } | |||
| 441 | + | } | |||
| 442 | + | other, err := o.ansi256.Draw(source, damage) | |||
| 443 | + | if err != nil { | |||
| 444 | + | o.t.Fatalf("seed %d: ansi256 Draw: %v", o.sc.seed, err) | |||
| 445 | + | } | |||
| 446 | + | o.emit("ansi256 %q", other) | |||
| 447 | + | ||||
| 448 | + | snap := s.Snapshot() | |||
| 449 | + | cur := snap.Cursor() | |||
| 450 | + | var htmlSource core.CellSource = o.mirror | |||
| 451 | + | if o.sc.seed%3 == 1 { | |||
| 452 | + | htmlSource = snap | |||
| 453 | + | } | |||
| 454 | + | hp, err := o.htmlR.Prepare(htmlSource, damage, false, html.Cursor{Row: cur.Row, Column: cur.Col, Visible: cur.Visible, Style: html.CursorStyle(cur.Style), StyleSet: cur.StyleSet}) | |||
| 455 | + | if err != nil { | |||
| 456 | + | o.emit("html err %v", err) | |||
| 457 | + | return | |||
| 458 | + | } | |||
| 459 | + | o.emit("html %s", hp.JSON()) | |||
| 460 | + | if o.r.IntN(6) == 0 { | |||
| 461 | + | if err := hp.Abort(); err != nil { | |||
| 462 | + | o.t.Fatalf("seed %d: html abort: %v", o.sc.seed, err) | |||
| 463 | + | } | |||
| 464 | + | o.emit("html abort") | |||
| 465 | + | } else if err := hp.Commit(); err != nil { | |||
| 466 | + | o.t.Fatalf("seed %d: html commit: %v", o.sc.seed, err) | |||
| 467 | + | } | |||
| 468 | + | } | |||
| 469 | + | ||||
| 470 | + | // applyReplay feeds committed ANSI output to an independent VT and compares | |||
| 471 | + | // the visible grid with the source. Mismatches are known renderer defects on | |||
| 472 | + | // the baseline (for example two scroll regions in one damage batch), so they | |||
| 473 | + | // are hashed into the transcript instead of failing: a performance change must | |||
| 474 | + | // neither add nor remove one. The replay is then resynchronized from a full | |||
| 475 | + | // draw, matching the renderer's committed belief. | |||
| 476 | + | func (o *oracleRun) applyReplay(out []byte, step int) { | |||
| 477 | + | s := o.screen | |||
| 478 | + | if o.replay == nil || o.replay.Columns() != s.Columns() || o.replay.Rows() != s.Rows() { | |||
| 479 | + | o.replay = vt.NewScreen(s.Columns(), s.Rows()) | |||
| 480 | + | } | |||
| 481 | + | o.replay.Write(out) | |||
| 482 | + | for y := range s.Rows() { | |||
| 483 | + | for x := range s.Columns() { | |||
| 484 | + | want, got := s.Cell(x, y), o.replay.Cell(x, y) | |||
| 485 | + | if want.Rune != got.Rune || want.Continuation != got.Continuation || !want.Style.Equal(got.Style) { | |||
| 486 | + | o.replayMismatches++ | |||
| 487 | + | o.emit("replay-mismatch step %d at (%d,%d): got %s want %s", step, x, y, formatCell(got), formatCell(want)) | |||
| 488 | + | o.replay = vt.NewScreen(s.Columns(), s.Rows()) | |||
| 489 | + | full, err := ansi.New(ansi.Capabilities{}).Draw(s, []core.Damage{core.FullRedraw()}) | |||
| 490 | + | if err != nil { | |||
| 491 | + | o.t.Fatalf("seed %d: resync draw: %v", o.sc.seed, err) | |||
| 492 | + | } | |||
| 493 | + | o.replay.Write(full) | |||
| 494 | + | return | |||
| 495 | + | } | |||
| 496 | + | } | |||
| 497 | + | } | |||
| 498 | + | } | |||
| 499 | + | ||||
| 500 | + | func runOracleScenario(t *testing.T, idx int) string { | |||
| 501 | + | sc := oracleScenarioFor(idx) | |||
| 502 | + | o := &oracleRun{t: t, sc: sc, r: rand.New(rand.NewPCG(sc.seed, 0xdecaf)), h: sha256.New()} | |||
| 503 | + | if *oracleDump != "" { | |||
| 504 | + | if err := os.MkdirAll(*oracleDump, 0o755); err != nil { | |||
| 505 | + | t.Fatal(err) | |||
| 506 | + | } | |||
| 507 | + | f, err := os.Create(filepath.Join(*oracleDump, fmt.Sprintf("scenario-%02d.txt", idx))) | |||
| 508 | + | if err != nil { | |||
| 509 | + | t.Fatal(err) | |||
| 510 | + | } | |||
| 511 | + | defer f.Close() | |||
| 512 | + | o.dump = bufio.NewWriter(f) | |||
| 513 | + | defer o.dump.Flush() | |||
| 514 | + | } | |||
| 515 | + | if sc.history != nil { | |||
| 516 | + | o.screen = vt.NewScreenWithHistory(sc.cols, sc.rows, *sc.history) | |||
| 517 | + | } else { | |||
| 518 | + | o.screen = vt.NewScreen(sc.cols, sc.rows) | |||
| 519 | + | } | |||
| 520 | + | o.screen.OnResponse = func(b []byte) { o.events = append(o.events, fmt.Sprintf("response %q", b)) } | |||
| 521 | + | o.screen.OnBell = func() { o.events = append(o.events, "bell") } | |||
| 522 | + | if sc.evictHook { | |||
| 523 | + | // The callback owns each row: retain it, verify it is never rewritten by | |||
| 524 | + | // later evictions, then scribble on it to prove VT kept its own copy. | |||
| 525 | + | var retained []core.Cell | |||
| 526 | + | var retainedDigest string | |||
| 527 | + | o.screen.OnLineEvicted = func(row []core.Cell) { | |||
| 528 | + | if retained != nil && formatRow(retained) != retainedDigest { | |||
| 529 | + | t.Fatalf("seed %d: evicted row storage was reused after the callback", sc.seed) | |||
| 530 | + | } | |||
| 531 | + | digest := formatRow(row) | |||
| 532 | + | o.events = append(o.events, "evicted "+digest+" "+rowText(row)) | |||
| 533 | + | if prev := retained; prev != nil { | |||
| 534 | + | for i := range prev { | |||
| 535 | + | prev[i] = core.Cell{Rune: 'X'} | |||
| 536 | + | } | |||
| 537 | + | } | |||
| 538 | + | retained, retainedDigest = row, digest | |||
| 539 | + | } | |||
| 540 | + | } | |||
| 541 | + | o.ansiR = ansi.New(ansi.Capabilities{SynchronizedOutput: idx%2 == 0}) | |||
| 542 | + | o.ansi256 = ansi.NewWithColorProfile(ansi.Capabilities{}, ansi.ColorProfileANSI256) | |||
| 543 | + | var err error | |||
| 544 | + | o.htmlR, err = html.New(html.Options{}) | |||
| 545 | + | if err != nil { | |||
| 546 | + | t.Fatal(err) | |||
| 547 | + | } | |||
| 548 | + | var historyConfig vt.HistoryConfig | |||
| 549 | + | if sc.history != nil { | |||
| 550 | + | historyConfig = *sc.history | |||
| 551 | + | } | |||
| 552 | + | o.emit("scenario %d %dx%d history=%+v byteSplit=%t evictHook=%t", idx, sc.cols, sc.rows, historyConfig, sc.byteSplit, sc.evictHook) | |||
| 553 | + | ||||
| 554 | + | var digests []string | |||
| 555 | + | for step := range oracleSteps { | |||
| 556 | + | o.maybeResize(step) | |||
| 557 | + | chunk := o.genChunk() | |||
| 558 | + | o.emit("step %d write %q", step, chunk) | |||
| 559 | + | o.write(chunk) | |||
| 560 | + | for _, e := range o.events { | |||
| 561 | + | o.emit("%s", e) | |||
| 562 | + | } | |||
| 563 | + | o.events = o.events[:0] | |||
| 564 | + | o.render(step) | |||
| 565 | + | if step%oracleCheckEvery == oracleCheckEvery-1 { | |||
| 566 | + | o.checkScreen() | |||
| 567 | + | o.checkGraphics() | |||
| 568 | + | o.checkHistory(step%(2*oracleCheckEvery) == 2*oracleCheckEvery-1) | |||
| 569 | + | digests = append(digests, fmt.Sprintf("%02d/%03d %s", idx, step, hex.EncodeToString(o.h.Sum(nil))[:24])) | |||
| 570 | + | } | |||
| 571 | + | } | |||
| 572 | + | digests = append(digests, fmt.Sprintf("%02d replay-mismatches=%d", idx, o.replayMismatches)) | |||
| 573 | + | return strings.Join(digests, "\n") | |||
| 574 | + | } | |||
| 575 | + | ||||
| 576 | + | func TestDifferentialOracle(t *testing.T) { | |||
| 577 | + | if testing.Short() { | |||
| 578 | + | t.Skip("differential oracle skipped in -short mode") | |||
| 579 | + | } | |||
| 580 | + | got := make([]string, oracleScenarios) | |||
| 581 | + | t.Run("scenarios", func(t *testing.T) { | |||
| 582 | + | for i := range oracleScenarios { | |||
| 583 | + | t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { | |||
| 584 | + | t.Parallel() | |||
| 585 | + | got[i] = runOracleScenario(t, i) | |||
| 586 | + | }) | |||
| 587 | + | } | |||
| 588 | + | }) | |||
| 589 | + | if t.Failed() { | |||
| 590 | + | return | |||
| 591 | + | } | |||
| 592 | + | text := strings.Join(got, "\n") + "\n" | |||
| 593 | + | if *oracleUpdate { | |||
| 594 | + | if err := os.MkdirAll(filepath.Dir(oracleGolden), 0o755); err != nil { | |||
| 595 | + | t.Fatal(err) | |||
| 596 | + | } | |||
| 597 | + | if err := os.WriteFile(oracleGolden, []byte(text), 0o644); err != nil { | |||
| 598 | + | t.Fatal(err) | |||
| 599 | + | } | |||
| 600 | + | return | |||
| 601 | + | } | |||
| 602 | + | want, err := os.ReadFile(oracleGolden) | |||
| 603 | + | if err != nil { | |||
| 604 | + | t.Fatalf("read golden (run with -oracle.update on a trusted baseline): %v", err) | |||
| 605 | + | } | |||
| 606 | + | if string(want) == text { | |||
| 607 | + | return | |||
| 608 | + | } | |||
| 609 | + | wantLines, gotLines := strings.Split(string(want), "\n"), strings.Split(text, "\n") | |||
| 610 | + | for i := range min(len(wantLines), len(gotLines)) { | |||
| 611 | + | if wantLines[i] != gotLines[i] { | |||
| 612 | + | t.Fatalf("oracle diverged at checkpoint %q (want %q); rerun with -oracle.dump=DIR on both trees and diff the scenario transcript", gotLines[i], wantLines[i]) | |||
| 613 | + | } | |||
| 614 | + | } | |||
| 615 | + | t.Fatalf("oracle checkpoint count differs: got %d want %d", len(gotLines), len(wantLines)) | |||
| 616 | + | } | |||
A testdata/oracle/golden.txt +468 -0
| @@ -0,0 +1,468 @@ | |||
| 1 | + | 00/019 55a72c4c8d1c1a0e00550a15 | |
| 2 | + | 00/039 4beaddfc77e4110f80524b38 | |
| 3 | + | 00/059 a5c2085be8baec4f551edd92 | |
| 4 | + | 00/079 e0ab87b780d0639fe8b96d12 | |
| 5 | + | 00/099 b079ef26a0bb72ced471f499 | |
| 6 | + | 00/119 f1ab642fcb4bfd4b3a7a03e6 | |
| 7 | + | 00/139 27629663856dfa65244e102d | |
| 8 | + | 00/159 b3aaf605acd5327d41968195 | |
| 9 | + | 00/179 9bb85c80ee152ab540d7ddab | |
| 10 | + | 00/199 6b4469404f496d823ad9e125 | |
| 11 | + | 00/219 729e470dc8268f158fd3fe65 | |
| 12 | + | 00/239 4c7ace24a712cd597c20d2e4 | |
| 13 | + | 00 replay-mismatches=1 | |
| 14 | + | 01/019 92d98f04143705d463e00d5a | |
| 15 | + | 01/039 0c887b0db21e564acbeb140b | |
| 16 | + | 01/059 4a1d21d32fb2e2dbb8a0a048 | |
| 17 | + | 01/079 8c494398433b3f3292105b68 | |
| 18 | + | 01/099 878ad5680b0b65efb2788c78 | |
| 19 | + | 01/119 eba0201db6d4a0a544c7bc26 | |
| 20 | + | 01/139 c366adc9bda774bbe629aa7f | |
| 21 | + | 01/159 82c5bf666042fdf83dea91dc | |
| 22 | + | 01/179 6c8e00e2c5851df5fc4d4531 | |
| 23 | + | 01/199 e02a755cc1869e5c2135b076 | |
| 24 | + | 01/219 d641dd26646f5cd455575cb9 | |
| 25 | + | 01/239 7b59cd0114a7b8a5d044dd5b | |
| 26 | + | 01 replay-mismatches=1 | |
| 27 | + | 02/019 3e82372b6cb2ef730fbd402c | |
| 28 | + | 02/039 398deed5e521d0788101775d | |
| 29 | + | 02/059 21401dc4b8bee3c8c6dce2ff | |
| 30 | + | 02/079 22edd985876e2267fd4a6896 | |
| 31 | + | 02/099 e6c6c10fcee0f3be94cdc63e | |
| 32 | + | 02/119 4cc31105a1fd3951dbc19e78 | |
| 33 | + | 02/139 ff2da75e170b01030791f703 | |
| 34 | + | 02/159 ee1b1a0baa2e690f004bac7c | |
| 35 | + | 02/179 70044ab48fec67d54592058a | |
| 36 | + | 02/199 73e6310d3e4c2a02a0aac70f | |
| 37 | + | 02/219 38ffab48e284151fed6243a1 | |
| 38 | + | 02/239 dee6444cff6f5bdda9f190a1 | |
| 39 | + | 02 replay-mismatches=0 | |
| 40 | + | 03/019 23e37ad503fc5be744f00967 | |
| 41 | + | 03/039 ec34adbb74097df59cc75e32 | |
| 42 | + | 03/059 85c664d4a3ad1d484c5118b3 | |
| 43 | + | 03/079 902369e3cf92b7cada2e9f20 | |
| 44 | + | 03/099 008d777b9692c94433ca31e0 | |
| 45 | + | 03/119 83d72044067f5276a4f75a4b | |
| 46 | + | 03/139 0cdea02f941f932699118d8e | |
| 47 | + | 03/159 dfd6d2abbad3a1be1e3d6ecc | |
| 48 | + | 03/179 99e59e60677d6d5ec88631a0 | |
| 49 | + | 03/199 8fe8fc36ce8901c3bf214f23 | |
| 50 | + | 03/219 20b7f922c656a2e197d4589e | |
| 51 | + | 03/239 045ae146ae096692a82f7211 | |
| 52 | + | 03 replay-mismatches=1 | |
| 53 | + | 04/019 4faa96c672855d38d5f009b9 | |
| 54 | + | 04/039 45daf6da341e0be752176143 | |
| 55 | + | 04/059 b210cfeb355ca92703eb5a7d | |
| 56 | + | 04/079 567e6224445b47f639d67406 | |
| 57 | + | 04/099 36dfe5e3817a756a77610280 | |
| 58 | + | 04/119 28f266be513510780e5298a0 | |
| 59 | + | 04/139 77159aaa349724f6a3468083 | |
| 60 | + | 04/159 454932ee14333813f68147b2 | |
| 61 | + | 04/179 5dc2da5983a46d4d8c045463 | |
| 62 | + | 04/199 33b567344c5ba4cbb11073b7 | |
| 63 | + | 04/219 98d41d64e511dc4f992f9ddd | |
| 64 | + | 04/239 a873589f7cbf01ab97712424 | |
| 65 | + | 04 replay-mismatches=0 | |
| 66 | + | 05/019 68d508ff98ffe9c97d491512 | |
| 67 | + | 05/039 22833aa6ac5590fd5c51a10c | |
| 68 | + | 05/059 f37316a5530d28ea4d386599 | |
| 69 | + | 05/079 72697ed3d723a5e4e2bf127e | |
| 70 | + | 05/099 b8128a9baa2e24f9cc2b10d0 | |
| 71 | + | 05/119 4f7d25dbe1b50ecf1ff67901 | |
| 72 | + | 05/139 c269b4f1d67c5238d12fabc0 | |
| 73 | + | 05/159 1ebdd92e38aeadf77e3d6c72 | |
| 74 | + | 05/179 ffc342703ae6c3a8c4cd9f57 | |
| 75 | + | 05/199 cb964d13d6ed459f435eca69 | |
| 76 | + | 05/219 945a552afa3fc038c0d0d49d | |
| 77 | + | 05/239 bb39be7fce9c2c1f3a7dd6a3 | |
| 78 | + | 05 replay-mismatches=0 | |
| 79 | + | 06/019 9f70eb84e027a0da12cf789e | |
| 80 | + | 06/039 fd1b4d60360d797d01dcf980 | |
| 81 | + | 06/059 e957cf36ed0da231cc781732 | |
| 82 | + | 06/079 ef5a1e45228ec8d02b4339fb | |
| 83 | + | 06/099 8aee61a050bf383a74a62a8b | |
| 84 | + | 06/119 b63b5ac60fb59413621ca130 | |
| 85 | + | 06/139 08d32c2a7d6a3d47faf8ad7e | |
| 86 | + | 06/159 ae180c731417adc39a3ac92e | |
| 87 | + | 06/179 50ecc3cc8de94af4fb6256e6 | |
| 88 | + | 06/199 bcad1567aa6b13757337ca65 | |
| 89 | + | 06/219 6d9e62599ea93901fbb98bda | |
| 90 | + | 06/239 12ad1caedd5346ab013bd936 | |
| 91 | + | 06 replay-mismatches=0 | |
| 92 | + | 07/019 771e7f8f79ce9d77f0bb56ff | |
| 93 | + | 07/039 031ed4ce8ac59a36ac7c1365 | |
| 94 | + | 07/059 2516663f39582c63aa218218 | |
| 95 | + | 07/079 3cd9f1f67cef906806422bdd | |
| 96 | + | 07/099 c3709b3f1a08717fc304d159 | |
| 97 | + | 07/119 9ca36d78bfa961ac711edb07 | |
| 98 | + | 07/139 fea50cc9d78951ce47ab7b5c | |
| 99 | + | 07/159 ac33e3c1365cc8c449a0d77b | |
| 100 | + | 07/179 92874af432d8106e7d39dd73 | |
| 101 | + | 07/199 6a9cdfd7f92ce4e2fc62bc5d | |
| 102 | + | 07/219 b3f6403504ede15a9d1f283b | |
| 103 | + | 07/239 a38515a32d34854dc6e2f4f7 | |
| 104 | + | 07 replay-mismatches=1 | |
| 105 | + | 08/019 29e2816bc5d015ce2d23c05c | |
| 106 | + | 08/039 2a86fe7ff64d840cd8569737 | |
| 107 | + | 08/059 51239ba60dc3b8f1b5078c45 | |
| 108 | + | 08/079 abb48c671fe1388ddd3dbe91 | |
| 109 | + | 08/099 bcfe3784a4f3cbe24d417126 | |
| 110 | + | 08/119 039ca7a36109843550b4bbe2 | |
| 111 | + | 08/139 0e6735e9400e34df32a24bc6 | |
| 112 | + | 08/159 4cdce3dbee4325befd425faf | |
| 113 | + | 08/179 005758f8650f3cec82710cfe | |
| 114 | + | 08/199 52482d634ba2b06bf34693dd | |
| 115 | + | 08/219 1b3e53ed97faccd2061b9e26 | |
| 116 | + | 08/239 c4ea1ea89908641b93e41266 | |
| 117 | + | 08 replay-mismatches=0 | |
| 118 | + | 09/019 4c119ea37ab5f5aaf7ab50f3 | |
| 119 | + | 09/039 a1efea7a141d9b5481317d21 | |
| 120 | + | 09/059 a0cce6141ccb877fe5e71f53 | |
| 121 | + | 09/079 614e9de8720ed8292a34242d | |
| 122 | + | 09/099 fb69e78cf4ccaeba35c42a8a | |
| 123 | + | 09/119 52f16f33c2b608d3d6b59394 | |
| 124 | + | 09/139 b9f02f6852f5de3b13640508 | |
| 125 | + | 09/159 72b12cd005097c0ba898111c | |
| 126 | + | 09/179 06937b4778221356408d2ed2 | |
| 127 | + | 09/199 369339d7370f38fcc22a12b9 | |
| 128 | + | 09/219 b9eec1e382e0500d5f1edde7 | |
| 129 | + | 09/239 c99a2183f2174d1e39502356 | |
| 130 | + | 09 replay-mismatches=0 | |
| 131 | + | 10/019 f1b345ef33635fe6028f81b4 | |
| 132 | + | 10/039 bb0ee5ddb6ba0461e8747c48 | |
| 133 | + | 10/059 7b0a1d3c4fcef2b20317695f | |
| 134 | + | 10/079 9d002061162fa4fab0050bcd | |
| 135 | + | 10/099 3878530870a8fb56b8328017 | |
| 136 | + | 10/119 620d17d70aa0ae092bfe7fe0 | |
| 137 | + | 10/139 5d8a36f80fa538d941383259 | |
| 138 | + | 10/159 4f464ea644caecec04f35afc | |
| 139 | + | 10/179 c60434e2aa9d7a64724ca147 | |
| 140 | + | 10/199 b9f0bdae9c7a31f6ca8bcbe6 | |
| 141 | + | 10/219 14e56199cd1442fc6793d65b | |
| 142 | + | 10/239 8c43ab2d5bde3b2fda977ddd | |
| 143 | + | 10 replay-mismatches=0 | |
| 144 | + | 11/019 e3eb02d62746d50757d63d18 | |
| 145 | + | 11/039 070fa14e017eb9ba3056672d | |
| 146 | + | 11/059 8b016d3845f5d47e91b1826b | |
| 147 | + | 11/079 75ce3f04d3082bc4788d6c96 | |
| 148 | + | 11/099 2b7cf678237f6988de3396a3 | |
| 149 | + | 11/119 230ebcd9a41d473aaed1896e | |
| 150 | + | 11/139 a0473a1fc49d7c12f20b41be | |
| 151 | + | 11/159 b9ef5849eaeefbe486770b76 | |
| 152 | + | 11/179 b3c43da50034693c385b844b | |
| 153 | + | 11/199 7145d045e0724186ce73059a | |
| 154 | + | 11/219 ee6f5fc91e84ee8214060439 | |
| 155 | + | 11/239 f4307d6e12b1cd2db37a8032 | |
| 156 | + | 11 replay-mismatches=0 | |
| 157 | + | 12/019 4fade3921f5735064355eec1 | |
| 158 | + | 12/039 e2aab8e88f7241073e73eb05 | |
| 159 | + | 12/059 2b1492eeb901a30dddcecfea | |
| 160 | + | 12/079 809ab3f1339fe2c10da81dc6 | |
| 161 | + | 12/099 09759c1960ee25243130cec7 | |
| 162 | + | 12/119 d40b61390be6b1ade0fc9798 | |
| 163 | + | 12/139 dd23c4487248a1236597baf8 | |
| 164 | + | 12/159 6ccc55f91c960571ebc0f2dc | |
| 165 | + | 12/179 2165c2c3b2a0fd7a597c1251 | |
| 166 | + | 12/199 679db69d3aee6356dafac627 | |
| 167 | + | 12/219 eff11bcec70127843617c651 | |
| 168 | + | 12/239 af6d8b16a1eb290229dd6000 | |
| 169 | + | 12 replay-mismatches=1 | |
| 170 | + | 13/019 ef8a3432b61607340356a764 | |
| 171 | + | 13/039 4e823b712c6aab07bcd5764e | |
| 172 | + | 13/059 90b7e616d2c528faeee0f72b | |
| 173 | + | 13/079 b1330a7079b795346ee9e317 | |
| 174 | + | 13/099 9f0d10089131edf34cceb0a1 | |
| 175 | + | 13/119 069d5dc52bcfba2eaa2211bd | |
| 176 | + | 13/139 08b97ef26faddb637a424bef | |
| 177 | + | 13/159 c29b028480ce1c097e8c3066 | |
| 178 | + | 13/179 85d6cc0d0267ce57c4f438d7 | |
| 179 | + | 13/199 84a14ed35e312f14e0c4d220 | |
| 180 | + | 13/219 8382c4d6025f22cddd967535 | |
| 181 | + | 13/239 4e3ac44cff6dfa7d7a843378 | |
| 182 | + | 13 replay-mismatches=0 | |
| 183 | + | 14/019 747d54cdf75a492242809318 | |
| 184 | + | 14/039 e6530a9282bd6095e15a9677 | |
| 185 | + | 14/059 715de56023e1afc30c1feea1 | |
| 186 | + | 14/079 f5c6253e95d471ccf7892526 | |
| 187 | + | 14/099 09063bb10e3e19d067c9c85c | |
| 188 | + | 14/119 f8753c5d5010cd747e5dcc31 | |
| 189 | + | 14/139 8a726baa028acc349f76fcb5 | |
| 190 | + | 14/159 6707de5fd29ec469ba8913b9 | |
| 191 | + | 14/179 3914c9045ccb2d16f6dfb386 | |
| 192 | + | 14/199 f5992df3d18c885bcaab4496 | |
| 193 | + | 14/219 38e1a9fe90723b701c438464 | |
| 194 | + | 14/239 c90fb86719f7e4613e3720c2 | |
| 195 | + | 14 replay-mismatches=0 | |
| 196 | + | 15/019 95e07748c709fa34a80ceff7 | |
| 197 | + | 15/039 4186ba13b89b2952cb8407b1 | |
| 198 | + | 15/059 e113b092eeaeb7ba23f36f0b | |
| 199 | + | 15/079 c9ad53bbc38faf68af448dbe | |
| 200 | + | 15/099 6207b08ba91d6e0c93f27540 | |
| 201 | + | 15/119 edffb952f657ba998d07de73 | |
| 202 | + | 15/139 5c874878a9a518b08627f52d | |
| 203 | + | 15/159 e7c63b49c6f3ebb122ab821b | |
| 204 | + | 15/179 edb533ed1dcf2d238f7db6a9 | |
| 205 | + | 15/199 80fa50afb7b21ddcc958e13e | |
| 206 | + | 15/219 f602f7cb0510d54ab8c94ff9 | |
| 207 | + | 15/239 3ac7cd4affba36e038a2254b | |
| 208 | + | 15 replay-mismatches=2 | |
| 209 | + | 16/019 4314babccb7675abd6328c32 | |
| 210 | + | 16/039 3e6ee1b339dab4ff5e0c8b34 | |
| 211 | + | 16/059 1afd8504d6a0be4d6cc9c3c3 | |
| 212 | + | 16/079 50f71b7a9d7c444810cf1ba6 | |
| 213 | + | 16/099 65a1c2e6e41adcddbc510efc | |
| 214 | + | 16/119 3f7c33fe85ccde2210b37f8f | |
| 215 | + | 16/139 1b47397842763e30313e66de | |
| 216 | + | 16/159 3d9b4904b96244df64051cc7 | |
| 217 | + | 16/179 60ae20b4cabf7c1a02192010 | |
| 218 | + | 16/199 210f0c4ad73131d2b465b362 | |
| 219 | + | 16/219 4b62676c69410e9b6f9d8f26 | |
| 220 | + | 16/239 72f70f009e426f88b33ebdae | |
| 221 | + | 16 replay-mismatches=0 | |
| 222 | + | 17/019 1a06602479d8e795ee95c965 | |
| 223 | + | 17/039 c7e7eb54ee63d9e7e23ceaca | |
| 224 | + | 17/059 51c34ba1a80d55b1f9ae42fa | |
| 225 | + | 17/079 d5f10d70e10e0e87e54ac6f3 | |
| 226 | + | 17/099 0a3784787eb2a97cce6c8309 | |
| 227 | + | 17/119 7ccd8bedfbf26111e264baa5 | |
| 228 | + | 17/139 f028c427efd1ce9c0b9828d1 | |
| 229 | + | 17/159 fb8b3eb57ac4186345d52918 | |
| 230 | + | 17/179 1c7da7757137c15d7a05d3d4 | |
| 231 | + | 17/199 55b35801df62b3250963613d | |
| 232 | + | 17/219 1cb11bbb2c5dd8995094be53 | |
| 233 | + | 17/239 6a146a19d7c534c4766e98f8 | |
| 234 | + | 17 replay-mismatches=0 | |
| 235 | + | 18/019 a1d2576637f54892892ea0b4 | |
| 236 | + | 18/039 4415a2923a28b75a5aa0dbfb | |
| 237 | + | 18/059 2bac49c23f1c09f170c68dc2 | |
| 238 | + | 18/079 eeebf8f57f35ee0b110fc200 | |
| 239 | + | 18/099 e3673a14664b853fa9613c64 | |
| 240 | + | 18/119 57aa1250c9bb46869c8fc1a7 | |
| 241 | + | 18/139 abaca7067643db7db4dfe105 | |
| 242 | + | 18/159 6e878b08b5635eac7ab9816c | |
| 243 | + | 18/179 031a000b34a4ba3bc6cedc53 | |
| 244 | + | 18/199 c523b55eb720c065fc6134ac | |
| 245 | + | 18/219 0f367277325b630ce5bc634f | |
| 246 | + | 18/239 e04b873c6820a46bfd2f4873 | |
| 247 | + | 18 replay-mismatches=0 | |
| 248 | + | 19/019 2db955914f2e068e72cf8dfa | |
| 249 | + | 19/039 ec344560f2bb7de5f25fdb78 | |
| 250 | + | 19/059 d1a41ae321f1449d256cf19d | |
| 251 | + | 19/079 c0d8a7ed784bd9d278c61bfa | |
| 252 | + | 19/099 f91e7a807eea1aac3f05da1f | |
| 253 | + | 19/119 36d47e23f93682f8f8bd720b | |
| 254 | + | 19/139 b227a962815dfd349d06722f | |
| 255 | + | 19/159 5552419605856137ee5e3384 | |
| 256 | + | 19/179 0c7cf6585dcbaa42d05758c1 | |
| 257 | + | 19/199 ebcee2469216bd045f79d9a7 | |
| 258 | + | 19/219 d5bdec7e45121f8f757c12df | |
| 259 | + | 19/239 ae4abbf3362cbcba26878acd | |
| 260 | + | 19 replay-mismatches=1 | |
| 261 | + | 20/019 3c776bd6d460be5f6bcd060e | |
| 262 | + | 20/039 ef65b757bd137499a352d5ae | |
| 263 | + | 20/059 1cfcab4ffbc08c619e97bcd5 | |
| 264 | + | 20/079 8879e2856d1a555f07aa60d7 | |
| 265 | + | 20/099 e16919d7e8c9f20f7dfa5956 | |
| 266 | + | 20/119 cb7f9cf61ef176f01706ac77 | |
| 267 | + | 20/139 f9edefe5603a815455b563ff | |
| 268 | + | 20/159 cf8862d058a774e0fdb18628 | |
| 269 | + | 20/179 82daa9b9d7f4edc82a424a4a | |
| 270 | + | 20/199 fe78853df2a403308904f8e2 | |
| 271 | + | 20/219 0895c29599a30db493020dd2 | |
| 272 | + | 20/239 0e2310234476bea5dbb44cf0 | |
| 273 | + | 20 replay-mismatches=1 | |
| 274 | + | 21/019 357ece3eab75324d3823a6a4 | |
| 275 | + | 21/039 fe4501bedc7017bc40ff0509 | |
| 276 | + | 21/059 c97dc13dbcd996b58361777e | |
| 277 | + | 21/079 478d3efcfcca35aac31016e5 | |
| 278 | + | 21/099 3311dde300dcf85626edb8b7 | |
| 279 | + | 21/119 67ff6fbebbb9976f9f5191d8 | |
| 280 | + | 21/139 034216a3e7d38b221d5a8811 | |
| 281 | + | 21/159 adc62b539970ad3123cb4175 | |
| 282 | + | 21/179 0323505afc602891ba38d7ac | |
| 283 | + | 21/199 577472eed259f3abc0aaa526 | |
| 284 | + | 21/219 5d1dc3faa423707ac2ca4ba7 | |
| 285 | + | 21/239 068abdef9483e1760da84d41 | |
| 286 | + | 21 replay-mismatches=1 | |
| 287 | + | 22/019 6fc626ac7796049f3457a9a7 | |
| 288 | + | 22/039 e9214d74742e6ab1a1c8baf0 | |
| 289 | + | 22/059 72e63b2a0100e4153851a23d | |
| 290 | + | 22/079 9b1a8ebe1c432db2fedb68e3 | |
| 291 | + | 22/099 924e3f255fb856ff7b7dc8ed | |
| 292 | + | 22/119 53fcd1df652d602fe412c5a0 | |
| 293 | + | 22/139 7414a5f8fdbd506ab15f698d | |
| 294 | + | 22/159 bc2dd2281c12435224135073 | |
| 295 | + | 22/179 dfeef83df0e865e875132be1 | |
| 296 | + | 22/199 9fe1387f10258c696fd9e783 | |
| 297 | + | 22/219 3009560abe632e04450f7f41 | |
| 298 | + | 22/239 fe8d129db62a73ae9b9650bf | |
| 299 | + | 22 replay-mismatches=0 | |
| 300 | + | 23/019 20cb94d03695ad09763e03d2 | |
| 301 | + | 23/039 6c18ee020345a7699fd75494 | |
| 302 | + | 23/059 f85c5ecebab82f1126f3c964 | |
| 303 | + | 23/079 2fbeda367e9d56072d5b0edc | |
| 304 | + | 23/099 307896059fd55149f9d7c7e3 | |
| 305 | + | 23/119 73bafc1697a5e247b2d3c75f | |
| 306 | + | 23/139 214cbbca9ce4b1d0c98ea569 | |
| 307 | + | 23/159 82259d749685c0f294be858f | |
| 308 | + | 23/179 8d7b14f89c5f5d3113fbb3d2 | |
| 309 | + | 23/199 ddee2d89e0897e90f9a6df78 | |
| 310 | + | 23/219 65e4440830b9f98e48fe3a11 | |
| 311 | + | 23/239 e8ca9cf0699b2c6333e0cb3e | |
| 312 | + | 23 replay-mismatches=0 | |
| 313 | + | 24/019 b7fdbacf57cb0af4d47225ba | |
| 314 | + | 24/039 2a75c514d5b6525aaae38b3c | |
| 315 | + | 24/059 25cfe1b7ea38dca8b287dfcb | |
| 316 | + | 24/079 f26ad4606c3aa14fdd999af4 | |
| 317 | + | 24/099 3e2f4c94e77ee245d7b1feb2 | |
| 318 | + | 24/119 17ea49879ca006a1a9cc6f7a | |
| 319 | + | 24/139 90243c3b758308f0dd7982d3 | |
| 320 | + | 24/159 c734adf4c419ccaccb7b344b | |
| 321 | + | 24/179 f4f0b4818a87d2d735361f5b | |
| 322 | + | 24/199 16acbd765f02caece9941217 | |
| 323 | + | 24/219 a4605530cb75474769714cf4 | |
| 324 | + | 24/239 ea3ffdb6ec8b58fe625b928a | |
| 325 | + | 24 replay-mismatches=1 | |
| 326 | + | 25/019 eb73f8f3bb46422a6a12fc8f | |
| 327 | + | 25/039 823c96945f71196e67f10f65 | |
| 328 | + | 25/059 c04f006b9f600ecd075cec6a | |
| 329 | + | 25/079 a21fe1e939c9fa2ddbc2d385 | |
| 330 | + | 25/099 c20fca1d42014ae0701d6d0b | |
| 331 | + | 25/119 3b6b3af160572b8ebc24277f | |
| 332 | + | 25/139 e6151bf8cfffc442059ea129 | |
| 333 | + | 25/159 5209600fc448f453bd687cb2 | |
| 334 | + | 25/179 043175bc5eea52c09fb8f884 | |
| 335 | + | 25/199 4b7c1cd9781ec41bf596b764 | |
| 336 | + | 25/219 ad62688c776574ab5c442de7 | |
| 337 | + | 25/239 22e025d483651540c55002c6 | |
| 338 | + | 25 replay-mismatches=0 | |
| 339 | + | 26/019 122c0cce3cc0ece13ce6f89a | |
| 340 | + | 26/039 0999ef9b5da72bc75cef9d49 | |
| 341 | + | 26/059 2efdbf6da4b726f10f2878d1 | |
| 342 | + | 26/079 f899906f635ddb86d5c73984 | |
| 343 | + | 26/099 46367bf24897cad56dd5235c | |
| 344 | + | 26/119 a97b04bc18332773595951da | |
| 345 | + | 26/139 c5ae15a2b57146ef155e27b9 | |
| 346 | + | 26/159 51ab1eaae8be298680367404 | |
| 347 | + | 26/179 e35bdd23c19b6295280181ed | |
| 348 | + | 26/199 5f21c27026ba98b0a9d5d51c | |
| 349 | + | 26/219 2fcb984adf0b347e0da88d33 | |
| 350 | + | 26/239 200f40ed44f86ce04857c1ef | |
| 351 | + | 26 replay-mismatches=0 | |
| 352 | + | 27/019 ee2c010c41b9f0e5f70ce2b3 | |
| 353 | + | 27/039 91ab08fb270466abbfb06c54 | |
| 354 | + | 27/059 2e73fe488420a5de3866e7e3 | |
| 355 | + | 27/079 a72a80db0e8ee9bffb891b38 | |
| 356 | + | 27/099 917cde1321cfce583a63d9f2 | |
| 357 | + | 27/119 b8056ec6e08df1e2f7033b6a | |
| 358 | + | 27/139 22214c1a34de027d97f18ad9 | |
| 359 | + | 27/159 18debaf4cd5dfd2bb665bf34 | |
| 360 | + | 27/179 b8c41633f2ea27468edc411a | |
| 361 | + | 27/199 85bc90436e42fca40bd64d3c | |
| 362 | + | 27/219 f0dbe3da68c8dd144c3dd9b7 | |
| 363 | + | 27/239 214002b3d936e2fe396aae01 | |
| 364 | + | 27 replay-mismatches=0 | |
| 365 | + | 28/019 3ee1c2f031af3ad67ae50ebc | |
| 366 | + | 28/039 07508b3af21ec20fadeefe55 | |
| 367 | + | 28/059 dfa63544d3d6a817a9212067 | |
| 368 | + | 28/079 1855612e92d140c47b8abd1d | |
| 369 | + | 28/099 bcb387773cfd286bf68b7b7d | |
| 370 | + | 28/119 1f742786b7a0328e4e513984 | |
| 371 | + | 28/139 491e0249e093af7d1fa703e3 | |
| 372 | + | 28/159 7e72e1e6ffbd2f8191ff7116 | |
| 373 | + | 28/179 daa22f39fc629726df0fd80c | |
| 374 | + | 28/199 ad54c46914aa19908c0a9880 | |
| 375 | + | 28/219 54e180efdd3796a13c1c7670 | |
| 376 | + | 28/239 8f0e0e94c590745fc2dc7678 | |
| 377 | + | 28 replay-mismatches=1 | |
| 378 | + | 29/019 72e981fc506bfdd3bbf15c37 | |
| 379 | + | 29/039 8c46bcf213fe0e9cf159a7d2 | |
| 380 | + | 29/059 213c8dd3d5bcda3abb11ab05 | |
| 381 | + | 29/079 5c42ca0bf2bdde3f4e8beb0e | |
| 382 | + | 29/099 9da53eccca7406686de96ec0 | |
| 383 | + | 29/119 c91f989577473d295e74d700 | |
| 384 | + | 29/139 6d5ff6bc1b1b73cae023806b | |
| 385 | + | 29/159 a1e1bfef4df06dc65e727a5e | |
| 386 | + | 29/179 afb819e69c5c53d51d49d446 | |
| 387 | + | 29/199 78df663b24d8507a469a7959 | |
| 388 | + | 29/219 e40592fde61b5e14bf73242a | |
| 389 | + | 29/239 9500a168ffd9142d6bdbde5a | |
| 390 | + | 29 replay-mismatches=0 | |
| 391 | + | 30/019 ea74787caee42ed3665575ed | |
| 392 | + | 30/039 91b5e5afa5d3bb06b087f6ad | |
| 393 | + | 30/059 ab536abe78f0cf27cdf94a75 | |
| 394 | + | 30/079 cb0e90a99459de9ed8493a87 | |
| 395 | + | 30/099 24cda2b6890297d9254603d1 | |
| 396 | + | 30/119 dc93cdf4c2014d22ac4d1ef2 | |
| 397 | + | 30/139 66e5417af5336b68e70cfa14 | |
| 398 | + | 30/159 c10f3ab9c58aa3f43caa4f94 | |
| 399 | + | 30/179 a7d6695e7add8ed6e7912161 | |
| 400 | + | 30/199 f1fe2d3081c9ccfc7413e3a9 | |
| 401 | + | 30/219 5c1dad4fffc15226a5eebe51 | |
| 402 | + | 30/239 966ff4c469452f7951326c5e | |
| 403 | + | 30 replay-mismatches=0 | |
| 404 | + | 31/019 869af9273a068555a7c65e06 | |
| 405 | + | 31/039 af9002bdf228a2e1f034df74 | |
| 406 | + | 31/059 375fd592eea7fdb3ce4a7563 | |
| 407 | + | 31/079 8d6f8e9a242c0da7a8707d06 | |
| 408 | + | 31/099 f4e05fd0482cb95b1141b933 | |
| 409 | + | 31/119 0499dd21ead9843ec3668589 | |
| 410 | + | 31/139 8fab9f1300b2d8c6757a9587 | |
| 411 | + | 31/159 e5ea9bef6d0ff097b0b0f00f | |
| 412 | + | 31/179 2d21760a2b468c1f8e101b86 | |
| 413 | + | 31/199 1c76b93d064c496485782148 | |
| 414 | + | 31/219 8a23244f3e0b25b774aaabbb | |
| 415 | + | 31/239 6b47e6b3998cf340d47f2dc9 | |
| 416 | + | 31 replay-mismatches=0 | |
| 417 | + | 32/019 ed11b0c6e02dfce865d49934 | |
| 418 | + | 32/039 bd3bbe698e10adf1f5fbc490 | |
| 419 | + | 32/059 fb530e83cb69ec498ccd32a7 | |
| 420 | + | 32/079 f54210cbb277563ac36c611d | |
| 421 | + | 32/099 9f2557fef0b33c2da3b1e22b | |
| 422 | + | 32/119 4b02e8f56434d83e8f5234f6 | |
| 423 | + | 32/139 39feab897a9120d585c20a8e | |
| 424 | + | 32/159 d0953ad716ea690de47dfc3d | |
| 425 | + | 32/179 f6a7b77c5991d37449e779b1 | |
| 426 | + | 32/199 2c188342e677e30c8ffe7f9e | |
| 427 | + | 32/219 7fc717e4fbfd57748ab342e3 | |
| 428 | + | 32/239 506a3d22b3e2ff760873e0fb | |
| 429 | + | 32 replay-mismatches=0 | |
| 430 | + | 33/019 75e34d5589c8f836a06d090e | |
| 431 | + | 33/039 372c230667e378656b2af4ff | |
| 432 | + | 33/059 938b86084dbb06afa4454b27 | |
| 433 | + | 33/079 7fa7265281d43e73b797d780 | |
| 434 | + | 33/099 ac83290b711b8dec3003df47 | |
| 435 | + | 33/119 021ac2bab909abdd6e5c77b5 | |
| 436 | + | 33/139 ed69b8d8881a85100d32c4cd | |
| 437 | + | 33/159 c1566ad91ec422c65923fe34 | |
| 438 | + | 33/179 9275113b9df0b001112ad573 | |
| 439 | + | 33/199 a269fac1fd59ac746cdcc3ed | |
| 440 | + | 33/219 8efc32f7b432b011f6862afb | |
| 441 | + | 33/239 360719a27cebdd0863b2e775 | |
| 442 | + | 33 replay-mismatches=0 | |
| 443 | + | 34/019 93dcae1fc2bfd6779aff1aff | |
| 444 | + | 34/039 e4c849c2eb75db26ab3e4a69 | |
| 445 | + | 34/059 f44f95efd2e73205143c656c | |
| 446 | + | 34/079 500ac8d59cbff22ad8cedf95 | |
| 447 | + | 34/099 8cde09d8fb843f0771e27c68 | |
| 448 | + | 34/119 8888149eafa436929ba9002f | |
| 449 | + | 34/139 9c9180251a5d41fb6bb84e0e | |
| 450 | + | 34/159 a8c9c943771f1bac64729951 | |
| 451 | + | 34/179 147dc1d5ce1aa53965747a76 | |
| 452 | + | 34/199 d60cdab9e926b1a975ab7c68 | |
| 453 | + | 34/219 70311db1c213b30d8622a677 | |
| 454 | + | 34/239 16c8a61f803eef432762550b | |
| 455 | + | 34 replay-mismatches=0 | |
| 456 | + | 35/019 6ce6075f205b75f864a11ca6 | |
| 457 | + | 35/039 8375248e74a353fc4ec62d73 | |
| 458 | + | 35/059 794e546c0d3346dc3965043f | |
| 459 | + | 35/079 f4a4a12dd0c59cfa3a7036b4 | |
| 460 | + | 35/099 a424a229392a35da8d937062 | |
| 461 | + | 35/119 28415d77a2772ddc9ff46361 | |
| 462 | + | 35/139 b142770cfec4946c445f1925 | |
| 463 | + | 35/159 a2c6d28bb5442fa733cf8ea3 | |
| 464 | + | 35/179 0c4f1446420ee3280f18090c | |
| 465 | + | 35/199 d8ab4d4487ea3bb519574059 | |
| 466 | + | 35/219 f3f1d026b78975accc285f57 | |
| 467 | + | 35/239 a734eb4db0f69306dc7a5630 | |
| 468 | + | 35 replay-mismatches=1 | |
| @@ -0,0 +1,468 @@ | |||||
| 1 | + | 00/019 55a72c4c8d1c1a0e00550a15 | |||
| 2 | + | 00/039 4beaddfc77e4110f80524b38 | |||
| 3 | + | 00/059 a5c2085be8baec4f551edd92 | |||
| 4 | + | 00/079 e0ab87b780d0639fe8b96d12 | |||
| 5 | + | 00/099 b079ef26a0bb72ced471f499 | |||
| 6 | + | 00/119 f1ab642fcb4bfd4b3a7a03e6 | |||
| 7 | + | 00/139 27629663856dfa65244e102d | |||
| 8 | + | 00/159 b3aaf605acd5327d41968195 | |||
| 9 | + | 00/179 9bb85c80ee152ab540d7ddab | |||
| 10 | + | 00/199 6b4469404f496d823ad9e125 | |||
| 11 | + | 00/219 729e470dc8268f158fd3fe65 | |||
| 12 | + | 00/239 4c7ace24a712cd597c20d2e4 | |||
| 13 | + | 00 replay-mismatches=1 | |||
| 14 | + | 01/019 92d98f04143705d463e00d5a | |||
| 15 | + | 01/039 0c887b0db21e564acbeb140b | |||
| 16 | + | 01/059 4a1d21d32fb2e2dbb8a0a048 | |||
| 17 | + | 01/079 8c494398433b3f3292105b68 | |||
| 18 | + | 01/099 878ad5680b0b65efb2788c78 | |||
| 19 | + | 01/119 eba0201db6d4a0a544c7bc26 | |||
| 20 | + | 01/139 c366adc9bda774bbe629aa7f | |||
| 21 | + | 01/159 82c5bf666042fdf83dea91dc | |||
| 22 | + | 01/179 6c8e00e2c5851df5fc4d4531 | |||
| 23 | + | 01/199 e02a755cc1869e5c2135b076 | |||
| 24 | + | 01/219 d641dd26646f5cd455575cb9 | |||
| 25 | + | 01/239 7b59cd0114a7b8a5d044dd5b | |||
| 26 | + | 01 replay-mismatches=1 | |||
| 27 | + | 02/019 3e82372b6cb2ef730fbd402c | |||
| 28 | + | 02/039 398deed5e521d0788101775d | |||
| 29 | + | 02/059 21401dc4b8bee3c8c6dce2ff | |||
| 30 | + | 02/079 22edd985876e2267fd4a6896 | |||
| 31 | + | 02/099 e6c6c10fcee0f3be94cdc63e | |||
| 32 | + | 02/119 4cc31105a1fd3951dbc19e78 | |||
| 33 | + | 02/139 ff2da75e170b01030791f703 | |||
| 34 | + | 02/159 ee1b1a0baa2e690f004bac7c | |||
| 35 | + | 02/179 70044ab48fec67d54592058a | |||
| 36 | + | 02/199 73e6310d3e4c2a02a0aac70f | |||
| 37 | + | 02/219 38ffab48e284151fed6243a1 | |||
| 38 | + | 02/239 dee6444cff6f5bdda9f190a1 | |||
| 39 | + | 02 replay-mismatches=0 | |||
| 40 | + | 03/019 23e37ad503fc5be744f00967 | |||
| 41 | + | 03/039 ec34adbb74097df59cc75e32 | |||
| 42 | + | 03/059 85c664d4a3ad1d484c5118b3 | |||
| 43 | + | 03/079 902369e3cf92b7cada2e9f20 | |||
| 44 | + | 03/099 008d777b9692c94433ca31e0 | |||
| 45 | + | 03/119 83d72044067f5276a4f75a4b | |||
| 46 | + | 03/139 0cdea02f941f932699118d8e | |||
| 47 | + | 03/159 dfd6d2abbad3a1be1e3d6ecc | |||
| 48 | + | 03/179 99e59e60677d6d5ec88631a0 | |||
| 49 | + | 03/199 8fe8fc36ce8901c3bf214f23 | |||
| 50 | + | 03/219 20b7f922c656a2e197d4589e | |||
| 51 | + | 03/239 045ae146ae096692a82f7211 | |||
| 52 | + | 03 replay-mismatches=1 | |||
| 53 | + | 04/019 4faa96c672855d38d5f009b9 | |||
| 54 | + | 04/039 45daf6da341e0be752176143 | |||
| 55 | + | 04/059 b210cfeb355ca92703eb5a7d | |||
| 56 | + | 04/079 567e6224445b47f639d67406 | |||
| 57 | + | 04/099 36dfe5e3817a756a77610280 | |||
| 58 | + | 04/119 28f266be513510780e5298a0 | |||
| 59 | + | 04/139 77159aaa349724f6a3468083 | |||
| 60 | + | 04/159 454932ee14333813f68147b2 | |||
| 61 | + | 04/179 5dc2da5983a46d4d8c045463 | |||
| 62 | + | 04/199 33b567344c5ba4cbb11073b7 | |||
| 63 | + | 04/219 98d41d64e511dc4f992f9ddd | |||
| 64 | + | 04/239 a873589f7cbf01ab97712424 | |||
| 65 | + | 04 replay-mismatches=0 | |||
| 66 | + | 05/019 68d508ff98ffe9c97d491512 | |||
| 67 | + | 05/039 22833aa6ac5590fd5c51a10c | |||
| 68 | + | 05/059 f37316a5530d28ea4d386599 | |||
| 69 | + | 05/079 72697ed3d723a5e4e2bf127e | |||
| 70 | + | 05/099 b8128a9baa2e24f9cc2b10d0 | |||
| 71 | + | 05/119 4f7d25dbe1b50ecf1ff67901 | |||
| 72 | + | 05/139 c269b4f1d67c5238d12fabc0 | |||
| 73 | + | 05/159 1ebdd92e38aeadf77e3d6c72 | |||
| 74 | + | 05/179 ffc342703ae6c3a8c4cd9f57 | |||
| 75 | + | 05/199 cb964d13d6ed459f435eca69 | |||
| 76 | + | 05/219 945a552afa3fc038c0d0d49d | |||
| 77 | + | 05/239 bb39be7fce9c2c1f3a7dd6a3 | |||
| 78 | + | 05 replay-mismatches=0 | |||
| 79 | + | 06/019 9f70eb84e027a0da12cf789e | |||
| 80 | + | 06/039 fd1b4d60360d797d01dcf980 | |||
| 81 | + | 06/059 e957cf36ed0da231cc781732 | |||
| 82 | + | 06/079 ef5a1e45228ec8d02b4339fb | |||
| 83 | + | 06/099 8aee61a050bf383a74a62a8b | |||
| 84 | + | 06/119 b63b5ac60fb59413621ca130 | |||
| 85 | + | 06/139 08d32c2a7d6a3d47faf8ad7e | |||
| 86 | + | 06/159 ae180c731417adc39a3ac92e | |||
| 87 | + | 06/179 50ecc3cc8de94af4fb6256e6 | |||
| 88 | + | 06/199 bcad1567aa6b13757337ca65 | |||
| 89 | + | 06/219 6d9e62599ea93901fbb98bda | |||
| 90 | + | 06/239 12ad1caedd5346ab013bd936 | |||
| 91 | + | 06 replay-mismatches=0 | |||
| 92 | + | 07/019 771e7f8f79ce9d77f0bb56ff | |||
| 93 | + | 07/039 031ed4ce8ac59a36ac7c1365 | |||
| 94 | + | 07/059 2516663f39582c63aa218218 | |||
| 95 | + | 07/079 3cd9f1f67cef906806422bdd | |||
| 96 | + | 07/099 c3709b3f1a08717fc304d159 | |||
| 97 | + | 07/119 9ca36d78bfa961ac711edb07 | |||
| 98 | + | 07/139 fea50cc9d78951ce47ab7b5c | |||
| 99 | + | 07/159 ac33e3c1365cc8c449a0d77b | |||
| 100 | + | 07/179 92874af432d8106e7d39dd73 | |||
| 101 | + | 07/199 6a9cdfd7f92ce4e2fc62bc5d | |||
| 102 | + | 07/219 b3f6403504ede15a9d1f283b | |||
| 103 | + | 07/239 a38515a32d34854dc6e2f4f7 | |||
| 104 | + | 07 replay-mismatches=1 | |||
| 105 | + | 08/019 29e2816bc5d015ce2d23c05c | |||
| 106 | + | 08/039 2a86fe7ff64d840cd8569737 | |||
| 107 | + | 08/059 51239ba60dc3b8f1b5078c45 | |||
| 108 | + | 08/079 abb48c671fe1388ddd3dbe91 | |||
| 109 | + | 08/099 bcfe3784a4f3cbe24d417126 | |||
| 110 | + | 08/119 039ca7a36109843550b4bbe2 | |||
| 111 | + | 08/139 0e6735e9400e34df32a24bc6 | |||
| 112 | + | 08/159 4cdce3dbee4325befd425faf | |||
| 113 | + | 08/179 005758f8650f3cec82710cfe | |||
| 114 | + | 08/199 52482d634ba2b06bf34693dd | |||
| 115 | + | 08/219 1b3e53ed97faccd2061b9e26 | |||
| 116 | + | 08/239 c4ea1ea89908641b93e41266 | |||
| 117 | + | 08 replay-mismatches=0 | |||
| 118 | + | 09/019 4c119ea37ab5f5aaf7ab50f3 | |||
| 119 | + | 09/039 a1efea7a141d9b5481317d21 | |||
| 120 | + | 09/059 a0cce6141ccb877fe5e71f53 | |||
| 121 | + | 09/079 614e9de8720ed8292a34242d | |||
| 122 | + | 09/099 fb69e78cf4ccaeba35c42a8a | |||
| 123 | + | 09/119 52f16f33c2b608d3d6b59394 | |||
| 124 | + | 09/139 b9f02f6852f5de3b13640508 | |||
| 125 | + | 09/159 72b12cd005097c0ba898111c | |||
| 126 | + | 09/179 06937b4778221356408d2ed2 | |||
| 127 | + | 09/199 369339d7370f38fcc22a12b9 | |||
| 128 | + | 09/219 b9eec1e382e0500d5f1edde7 | |||
| 129 | + | 09/239 c99a2183f2174d1e39502356 | |||
| 130 | + | 09 replay-mismatches=0 | |||
| 131 | + | 10/019 f1b345ef33635fe6028f81b4 | |||
| 132 | + | 10/039 bb0ee5ddb6ba0461e8747c48 | |||
| 133 | + | 10/059 7b0a1d3c4fcef2b20317695f | |||
| 134 | + | 10/079 9d002061162fa4fab0050bcd | |||
| 135 | + | 10/099 3878530870a8fb56b8328017 | |||
| 136 | + | 10/119 620d17d70aa0ae092bfe7fe0 | |||
| 137 | + | 10/139 5d8a36f80fa538d941383259 | |||
| 138 | + | 10/159 4f464ea644caecec04f35afc | |||
| 139 | + | 10/179 c60434e2aa9d7a64724ca147 | |||
| 140 | + | 10/199 b9f0bdae9c7a31f6ca8bcbe6 | |||
| 141 | + | 10/219 14e56199cd1442fc6793d65b | |||
| 142 | + | 10/239 8c43ab2d5bde3b2fda977ddd | |||
| 143 | + | 10 replay-mismatches=0 | |||
| 144 | + | 11/019 e3eb02d62746d50757d63d18 | |||
| 145 | + | 11/039 070fa14e017eb9ba3056672d | |||
| 146 | + | 11/059 8b016d3845f5d47e91b1826b | |||
| 147 | + | 11/079 75ce3f04d3082bc4788d6c96 | |||
| 148 | + | 11/099 2b7cf678237f6988de3396a3 | |||
| 149 | + | 11/119 230ebcd9a41d473aaed1896e | |||
| 150 | + | 11/139 a0473a1fc49d7c12f20b41be | |||
| 151 | + | 11/159 b9ef5849eaeefbe486770b76 | |||
| 152 | + | 11/179 b3c43da50034693c385b844b | |||
| 153 | + | 11/199 7145d045e0724186ce73059a | |||
| 154 | + | 11/219 ee6f5fc91e84ee8214060439 | |||
| 155 | + | 11/239 f4307d6e12b1cd2db37a8032 | |||
| 156 | + | 11 replay-mismatches=0 | |||
| 157 | + | 12/019 4fade3921f5735064355eec1 | |||
| 158 | + | 12/039 e2aab8e88f7241073e73eb05 | |||
| 159 | + | 12/059 2b1492eeb901a30dddcecfea | |||
| 160 | + | 12/079 809ab3f1339fe2c10da81dc6 | |||
| 161 | + | 12/099 09759c1960ee25243130cec7 | |||
| 162 | + | 12/119 d40b61390be6b1ade0fc9798 | |||
| 163 | + | 12/139 dd23c4487248a1236597baf8 | |||
| 164 | + | 12/159 6ccc55f91c960571ebc0f2dc | |||
| 165 | + | 12/179 2165c2c3b2a0fd7a597c1251 | |||
| 166 | + | 12/199 679db69d3aee6356dafac627 | |||
| 167 | + | 12/219 eff11bcec70127843617c651 | |||
| 168 | + | 12/239 af6d8b16a1eb290229dd6000 | |||
| 169 | + | 12 replay-mismatches=1 | |||
| 170 | + | 13/019 ef8a3432b61607340356a764 | |||
| 171 | + | 13/039 4e823b712c6aab07bcd5764e | |||
| 172 | + | 13/059 90b7e616d2c528faeee0f72b | |||
| 173 | + | 13/079 b1330a7079b795346ee9e317 | |||
| 174 | + | 13/099 9f0d10089131edf34cceb0a1 | |||
| 175 | + | 13/119 069d5dc52bcfba2eaa2211bd | |||
| 176 | + | 13/139 08b97ef26faddb637a424bef | |||
| 177 | + | 13/159 c29b028480ce1c097e8c3066 | |||
| 178 | + | 13/179 85d6cc0d0267ce57c4f438d7 | |||
| 179 | + | 13/199 84a14ed35e312f14e0c4d220 | |||
| 180 | + | 13/219 8382c4d6025f22cddd967535 | |||
| 181 | + | 13/239 4e3ac44cff6dfa7d7a843378 | |||
| 182 | + | 13 replay-mismatches=0 | |||
| 183 | + | 14/019 747d54cdf75a492242809318 | |||
| 184 | + | 14/039 e6530a9282bd6095e15a9677 | |||
| 185 | + | 14/059 715de56023e1afc30c1feea1 | |||
| 186 | + | 14/079 f5c6253e95d471ccf7892526 | |||
| 187 | + | 14/099 09063bb10e3e19d067c9c85c | |||
| 188 | + | 14/119 f8753c5d5010cd747e5dcc31 | |||
| 189 | + | 14/139 8a726baa028acc349f76fcb5 | |||
| 190 | + | 14/159 6707de5fd29ec469ba8913b9 | |||
| 191 | + | 14/179 3914c9045ccb2d16f6dfb386 | |||
| 192 | + | 14/199 f5992df3d18c885bcaab4496 | |||
| 193 | + | 14/219 38e1a9fe90723b701c438464 | |||
| 194 | + | 14/239 c90fb86719f7e4613e3720c2 | |||
| 195 | + | 14 replay-mismatches=0 | |||
| 196 | + | 15/019 95e07748c709fa34a80ceff7 | |||
| 197 | + | 15/039 4186ba13b89b2952cb8407b1 | |||
| 198 | + | 15/059 e113b092eeaeb7ba23f36f0b | |||
| 199 | + | 15/079 c9ad53bbc38faf68af448dbe | |||
| 200 | + | 15/099 6207b08ba91d6e0c93f27540 | |||
| 201 | + | 15/119 edffb952f657ba998d07de73 | |||
| 202 | + | 15/139 5c874878a9a518b08627f52d | |||
| 203 | + | 15/159 e7c63b49c6f3ebb122ab821b | |||
| 204 | + | 15/179 edb533ed1dcf2d238f7db6a9 | |||
| 205 | + | 15/199 80fa50afb7b21ddcc958e13e | |||
| 206 | + | 15/219 f602f7cb0510d54ab8c94ff9 | |||
| 207 | + | 15/239 3ac7cd4affba36e038a2254b | |||
| 208 | + | 15 replay-mismatches=2 | |||
| 209 | + | 16/019 4314babccb7675abd6328c32 | |||
| 210 | + | 16/039 3e6ee1b339dab4ff5e0c8b34 | |||
| 211 | + | 16/059 1afd8504d6a0be4d6cc9c3c3 | |||
| 212 | + | 16/079 50f71b7a9d7c444810cf1ba6 | |||
| 213 | + | 16/099 65a1c2e6e41adcddbc510efc | |||
| 214 | + | 16/119 3f7c33fe85ccde2210b37f8f | |||
| 215 | + | 16/139 1b47397842763e30313e66de | |||
| 216 | + | 16/159 3d9b4904b96244df64051cc7 | |||
| 217 | + | 16/179 60ae20b4cabf7c1a02192010 | |||
| 218 | + | 16/199 210f0c4ad73131d2b465b362 | |||
| 219 | + | 16/219 4b62676c69410e9b6f9d8f26 | |||
| 220 | + | 16/239 72f70f009e426f88b33ebdae | |||
| 221 | + | 16 replay-mismatches=0 | |||
| 222 | + | 17/019 1a06602479d8e795ee95c965 | |||
| 223 | + | 17/039 c7e7eb54ee63d9e7e23ceaca | |||
| 224 | + | 17/059 51c34ba1a80d55b1f9ae42fa | |||
| 225 | + | 17/079 d5f10d70e10e0e87e54ac6f3 | |||
| 226 | + | 17/099 0a3784787eb2a97cce6c8309 | |||
| 227 | + | 17/119 7ccd8bedfbf26111e264baa5 | |||
| 228 | + | 17/139 f028c427efd1ce9c0b9828d1 | |||
| 229 | + | 17/159 fb8b3eb57ac4186345d52918 | |||
| 230 | + | 17/179 1c7da7757137c15d7a05d3d4 | |||
| 231 | + | 17/199 55b35801df62b3250963613d | |||
| 232 | + | 17/219 1cb11bbb2c5dd8995094be53 | |||
| 233 | + | 17/239 6a146a19d7c534c4766e98f8 | |||
| 234 | + | 17 replay-mismatches=0 | |||
| 235 | + | 18/019 a1d2576637f54892892ea0b4 | |||
| 236 | + | 18/039 4415a2923a28b75a5aa0dbfb | |||
| 237 | + | 18/059 2bac49c23f1c09f170c68dc2 | |||
| 238 | + | 18/079 eeebf8f57f35ee0b110fc200 | |||
| 239 | + | 18/099 e3673a14664b853fa9613c64 | |||
| 240 | + | 18/119 57aa1250c9bb46869c8fc1a7 | |||
| 241 | + | 18/139 abaca7067643db7db4dfe105 | |||
| 242 | + | 18/159 6e878b08b5635eac7ab9816c | |||
| 243 | + | 18/179 031a000b34a4ba3bc6cedc53 | |||
| 244 | + | 18/199 c523b55eb720c065fc6134ac | |||
| 245 | + | 18/219 0f367277325b630ce5bc634f | |||
| 246 | + | 18/239 e04b873c6820a46bfd2f4873 | |||
| 247 | + | 18 replay-mismatches=0 | |||
| 248 | + | 19/019 2db955914f2e068e72cf8dfa | |||
| 249 | + | 19/039 ec344560f2bb7de5f25fdb78 | |||
| 250 | + | 19/059 d1a41ae321f1449d256cf19d | |||
| 251 | + | 19/079 c0d8a7ed784bd9d278c61bfa | |||
| 252 | + | 19/099 f91e7a807eea1aac3f05da1f | |||
| 253 | + | 19/119 36d47e23f93682f8f8bd720b | |||
| 254 | + | 19/139 b227a962815dfd349d06722f | |||
| 255 | + | 19/159 5552419605856137ee5e3384 | |||
| 256 | + | 19/179 0c7cf6585dcbaa42d05758c1 | |||
| 257 | + | 19/199 ebcee2469216bd045f79d9a7 | |||
| 258 | + | 19/219 d5bdec7e45121f8f757c12df | |||
| 259 | + | 19/239 ae4abbf3362cbcba26878acd | |||
| 260 | + | 19 replay-mismatches=1 | |||
| 261 | + | 20/019 3c776bd6d460be5f6bcd060e | |||
| 262 | + | 20/039 ef65b757bd137499a352d5ae | |||
| 263 | + | 20/059 1cfcab4ffbc08c619e97bcd5 | |||
| 264 | + | 20/079 8879e2856d1a555f07aa60d7 | |||
| 265 | + | 20/099 e16919d7e8c9f20f7dfa5956 | |||
| 266 | + | 20/119 cb7f9cf61ef176f01706ac77 | |||
| 267 | + | 20/139 f9edefe5603a815455b563ff | |||
| 268 | + | 20/159 cf8862d058a774e0fdb18628 | |||
| 269 | + | 20/179 82daa9b9d7f4edc82a424a4a | |||
| 270 | + | 20/199 fe78853df2a403308904f8e2 | |||
| 271 | + | 20/219 0895c29599a30db493020dd2 | |||
| 272 | + | 20/239 0e2310234476bea5dbb44cf0 | |||
| 273 | + | 20 replay-mismatches=1 | |||
| 274 | + | 21/019 357ece3eab75324d3823a6a4 | |||
| 275 | + | 21/039 fe4501bedc7017bc40ff0509 | |||
| 276 | + | 21/059 c97dc13dbcd996b58361777e | |||
| 277 | + | 21/079 478d3efcfcca35aac31016e5 | |||
| 278 | + | 21/099 3311dde300dcf85626edb8b7 | |||
| 279 | + | 21/119 67ff6fbebbb9976f9f5191d8 | |||
| 280 | + | 21/139 034216a3e7d38b221d5a8811 | |||
| 281 | + | 21/159 adc62b539970ad3123cb4175 | |||
| 282 | + | 21/179 0323505afc602891ba38d7ac | |||
| 283 | + | 21/199 577472eed259f3abc0aaa526 | |||
| 284 | + | 21/219 5d1dc3faa423707ac2ca4ba7 | |||
| 285 | + | 21/239 068abdef9483e1760da84d41 | |||
| 286 | + | 21 replay-mismatches=1 | |||
| 287 | + | 22/019 6fc626ac7796049f3457a9a7 | |||
| 288 | + | 22/039 e9214d74742e6ab1a1c8baf0 | |||
| 289 | + | 22/059 72e63b2a0100e4153851a23d | |||
| 290 | + | 22/079 9b1a8ebe1c432db2fedb68e3 | |||
| 291 | + | 22/099 924e3f255fb856ff7b7dc8ed | |||
| 292 | + | 22/119 53fcd1df652d602fe412c5a0 | |||
| 293 | + | 22/139 7414a5f8fdbd506ab15f698d | |||
| 294 | + | 22/159 bc2dd2281c12435224135073 | |||
| 295 | + | 22/179 dfeef83df0e865e875132be1 | |||
| 296 | + | 22/199 9fe1387f10258c696fd9e783 | |||
| 297 | + | 22/219 3009560abe632e04450f7f41 | |||
| 298 | + | 22/239 fe8d129db62a73ae9b9650bf | |||
| 299 | + | 22 replay-mismatches=0 | |||
| 300 | + | 23/019 20cb94d03695ad09763e03d2 | |||
| 301 | + | 23/039 6c18ee020345a7699fd75494 | |||
| 302 | + | 23/059 f85c5ecebab82f1126f3c964 | |||
| 303 | + | 23/079 2fbeda367e9d56072d5b0edc | |||
| 304 | + | 23/099 307896059fd55149f9d7c7e3 | |||
| 305 | + | 23/119 73bafc1697a5e247b2d3c75f | |||
| 306 | + | 23/139 214cbbca9ce4b1d0c98ea569 | |||
| 307 | + | 23/159 82259d749685c0f294be858f | |||
| 308 | + | 23/179 8d7b14f89c5f5d3113fbb3d2 | |||
| 309 | + | 23/199 ddee2d89e0897e90f9a6df78 | |||
| 310 | + | 23/219 65e4440830b9f98e48fe3a11 | |||
| 311 | + | 23/239 e8ca9cf0699b2c6333e0cb3e | |||
| 312 | + | 23 replay-mismatches=0 | |||
| 313 | + | 24/019 b7fdbacf57cb0af4d47225ba | |||
| 314 | + | 24/039 2a75c514d5b6525aaae38b3c | |||
| 315 | + | 24/059 25cfe1b7ea38dca8b287dfcb | |||
| 316 | + | 24/079 f26ad4606c3aa14fdd999af4 | |||
| 317 | + | 24/099 3e2f4c94e77ee245d7b1feb2 | |||
| 318 | + | 24/119 17ea49879ca006a1a9cc6f7a | |||
| 319 | + | 24/139 90243c3b758308f0dd7982d3 | |||
| 320 | + | 24/159 c734adf4c419ccaccb7b344b | |||
| 321 | + | 24/179 f4f0b4818a87d2d735361f5b | |||
| 322 | + | 24/199 16acbd765f02caece9941217 | |||
| 323 | + | 24/219 a4605530cb75474769714cf4 | |||
| 324 | + | 24/239 ea3ffdb6ec8b58fe625b928a | |||
| 325 | + | 24 replay-mismatches=1 | |||
| 326 | + | 25/019 eb73f8f3bb46422a6a12fc8f | |||
| 327 | + | 25/039 823c96945f71196e67f10f65 | |||
| 328 | + | 25/059 c04f006b9f600ecd075cec6a | |||
| 329 | + | 25/079 a21fe1e939c9fa2ddbc2d385 | |||
| 330 | + | 25/099 c20fca1d42014ae0701d6d0b | |||
| 331 | + | 25/119 3b6b3af160572b8ebc24277f | |||
| 332 | + | 25/139 e6151bf8cfffc442059ea129 | |||
| 333 | + | 25/159 5209600fc448f453bd687cb2 | |||
| 334 | + | 25/179 043175bc5eea52c09fb8f884 | |||
| 335 | + | 25/199 4b7c1cd9781ec41bf596b764 | |||
| 336 | + | 25/219 ad62688c776574ab5c442de7 | |||
| 337 | + | 25/239 22e025d483651540c55002c6 | |||
| 338 | + | 25 replay-mismatches=0 | |||
| 339 | + | 26/019 122c0cce3cc0ece13ce6f89a | |||
| 340 | + | 26/039 0999ef9b5da72bc75cef9d49 | |||
| 341 | + | 26/059 2efdbf6da4b726f10f2878d1 | |||
| 342 | + | 26/079 f899906f635ddb86d5c73984 | |||
| 343 | + | 26/099 46367bf24897cad56dd5235c | |||
| 344 | + | 26/119 a97b04bc18332773595951da | |||
| 345 | + | 26/139 c5ae15a2b57146ef155e27b9 | |||
| 346 | + | 26/159 51ab1eaae8be298680367404 | |||
| 347 | + | 26/179 e35bdd23c19b6295280181ed | |||
| 348 | + | 26/199 5f21c27026ba98b0a9d5d51c | |||
| 349 | + | 26/219 2fcb984adf0b347e0da88d33 | |||
| 350 | + | 26/239 200f40ed44f86ce04857c1ef | |||
| 351 | + | 26 replay-mismatches=0 | |||
| 352 | + | 27/019 ee2c010c41b9f0e5f70ce2b3 | |||
| 353 | + | 27/039 91ab08fb270466abbfb06c54 | |||
| 354 | + | 27/059 2e73fe488420a5de3866e7e3 | |||
| 355 | + | 27/079 a72a80db0e8ee9bffb891b38 | |||
| 356 | + | 27/099 917cde1321cfce583a63d9f2 | |||
| 357 | + | 27/119 b8056ec6e08df1e2f7033b6a | |||
| 358 | + | 27/139 22214c1a34de027d97f18ad9 | |||
| 359 | + | 27/159 18debaf4cd5dfd2bb665bf34 | |||
| 360 | + | 27/179 b8c41633f2ea27468edc411a | |||
| 361 | + | 27/199 85bc90436e42fca40bd64d3c | |||
| 362 | + | 27/219 f0dbe3da68c8dd144c3dd9b7 | |||
| 363 | + | 27/239 214002b3d936e2fe396aae01 | |||
| 364 | + | 27 replay-mismatches=0 | |||
| 365 | + | 28/019 3ee1c2f031af3ad67ae50ebc | |||
| 366 | + | 28/039 07508b3af21ec20fadeefe55 | |||
| 367 | + | 28/059 dfa63544d3d6a817a9212067 | |||
| 368 | + | 28/079 1855612e92d140c47b8abd1d | |||
| 369 | + | 28/099 bcb387773cfd286bf68b7b7d | |||
| 370 | + | 28/119 1f742786b7a0328e4e513984 | |||
| 371 | + | 28/139 491e0249e093af7d1fa703e3 | |||
| 372 | + | 28/159 7e72e1e6ffbd2f8191ff7116 | |||
| 373 | + | 28/179 daa22f39fc629726df0fd80c | |||
| 374 | + | 28/199 ad54c46914aa19908c0a9880 | |||
| 375 | + | 28/219 54e180efdd3796a13c1c7670 | |||
| 376 | + | 28/239 8f0e0e94c590745fc2dc7678 | |||
| 377 | + | 28 replay-mismatches=1 | |||
| 378 | + | 29/019 72e981fc506bfdd3bbf15c37 | |||
| 379 | + | 29/039 8c46bcf213fe0e9cf159a7d2 | |||
| 380 | + | 29/059 213c8dd3d5bcda3abb11ab05 | |||
| 381 | + | 29/079 5c42ca0bf2bdde3f4e8beb0e | |||
| 382 | + | 29/099 9da53eccca7406686de96ec0 | |||
| 383 | + | 29/119 c91f989577473d295e74d700 | |||
| 384 | + | 29/139 6d5ff6bc1b1b73cae023806b | |||
| 385 | + | 29/159 a1e1bfef4df06dc65e727a5e | |||
| 386 | + | 29/179 afb819e69c5c53d51d49d446 | |||
| 387 | + | 29/199 78df663b24d8507a469a7959 | |||
| 388 | + | 29/219 e40592fde61b5e14bf73242a | |||
| 389 | + | 29/239 9500a168ffd9142d6bdbde5a | |||
| 390 | + | 29 replay-mismatches=0 | |||
| 391 | + | 30/019 ea74787caee42ed3665575ed | |||
| 392 | + | 30/039 91b5e5afa5d3bb06b087f6ad | |||
| 393 | + | 30/059 ab536abe78f0cf27cdf94a75 | |||
| 394 | + | 30/079 cb0e90a99459de9ed8493a87 | |||
| 395 | + | 30/099 24cda2b6890297d9254603d1 | |||
| 396 | + | 30/119 dc93cdf4c2014d22ac4d1ef2 | |||
| 397 | + | 30/139 66e5417af5336b68e70cfa14 | |||
| 398 | + | 30/159 c10f3ab9c58aa3f43caa4f94 | |||
| 399 | + | 30/179 a7d6695e7add8ed6e7912161 | |||
| 400 | + | 30/199 f1fe2d3081c9ccfc7413e3a9 | |||
| 401 | + | 30/219 5c1dad4fffc15226a5eebe51 | |||
| 402 | + | 30/239 966ff4c469452f7951326c5e | |||
| 403 | + | 30 replay-mismatches=0 | |||
| 404 | + | 31/019 869af9273a068555a7c65e06 | |||
| 405 | + | 31/039 af9002bdf228a2e1f034df74 | |||
| 406 | + | 31/059 375fd592eea7fdb3ce4a7563 | |||
| 407 | + | 31/079 8d6f8e9a242c0da7a8707d06 | |||
| 408 | + | 31/099 f4e05fd0482cb95b1141b933 | |||
| 409 | + | 31/119 0499dd21ead9843ec3668589 | |||
| 410 | + | 31/139 8fab9f1300b2d8c6757a9587 | |||
| 411 | + | 31/159 e5ea9bef6d0ff097b0b0f00f | |||
| 412 | + | 31/179 2d21760a2b468c1f8e101b86 | |||
| 413 | + | 31/199 1c76b93d064c496485782148 | |||
| 414 | + | 31/219 8a23244f3e0b25b774aaabbb | |||
| 415 | + | 31/239 6b47e6b3998cf340d47f2dc9 | |||
| 416 | + | 31 replay-mismatches=0 | |||
| 417 | + | 32/019 ed11b0c6e02dfce865d49934 | |||
| 418 | + | 32/039 bd3bbe698e10adf1f5fbc490 | |||
| 419 | + | 32/059 fb530e83cb69ec498ccd32a7 | |||
| 420 | + | 32/079 f54210cbb277563ac36c611d | |||
| 421 | + | 32/099 9f2557fef0b33c2da3b1e22b | |||
| 422 | + | 32/119 4b02e8f56434d83e8f5234f6 | |||
| 423 | + | 32/139 39feab897a9120d585c20a8e | |||
| 424 | + | 32/159 d0953ad716ea690de47dfc3d | |||
| 425 | + | 32/179 f6a7b77c5991d37449e779b1 | |||
| 426 | + | 32/199 2c188342e677e30c8ffe7f9e | |||
| 427 | + | 32/219 7fc717e4fbfd57748ab342e3 | |||
| 428 | + | 32/239 506a3d22b3e2ff760873e0fb | |||
| 429 | + | 32 replay-mismatches=0 | |||
| 430 | + | 33/019 75e34d5589c8f836a06d090e | |||
| 431 | + | 33/039 372c230667e378656b2af4ff | |||
| 432 | + | 33/059 938b86084dbb06afa4454b27 | |||
| 433 | + | 33/079 7fa7265281d43e73b797d780 | |||
| 434 | + | 33/099 ac83290b711b8dec3003df47 | |||
| 435 | + | 33/119 021ac2bab909abdd6e5c77b5 | |||
| 436 | + | 33/139 ed69b8d8881a85100d32c4cd | |||
| 437 | + | 33/159 c1566ad91ec422c65923fe34 | |||
| 438 | + | 33/179 9275113b9df0b001112ad573 | |||
| 439 | + | 33/199 a269fac1fd59ac746cdcc3ed | |||
| 440 | + | 33/219 8efc32f7b432b011f6862afb | |||
| 441 | + | 33/239 360719a27cebdd0863b2e775 | |||
| 442 | + | 33 replay-mismatches=0 | |||
| 443 | + | 34/019 93dcae1fc2bfd6779aff1aff | |||
| 444 | + | 34/039 e4c849c2eb75db26ab3e4a69 | |||
| 445 | + | 34/059 f44f95efd2e73205143c656c | |||
| 446 | + | 34/079 500ac8d59cbff22ad8cedf95 | |||
| 447 | + | 34/099 8cde09d8fb843f0771e27c68 | |||
| 448 | + | 34/119 8888149eafa436929ba9002f | |||
| 449 | + | 34/139 9c9180251a5d41fb6bb84e0e | |||
| 450 | + | 34/159 a8c9c943771f1bac64729951 | |||
| 451 | + | 34/179 147dc1d5ce1aa53965747a76 | |||
| 452 | + | 34/199 d60cdab9e926b1a975ab7c68 | |||
| 453 | + | 34/219 70311db1c213b30d8622a677 | |||
| 454 | + | 34/239 16c8a61f803eef432762550b | |||
| 455 | + | 34 replay-mismatches=0 | |||
| 456 | + | 35/019 6ce6075f205b75f864a11ca6 | |||
| 457 | + | 35/039 8375248e74a353fc4ec62d73 | |||
| 458 | + | 35/059 794e546c0d3346dc3965043f | |||
| 459 | + | 35/079 f4a4a12dd0c59cfa3a7036b4 | |||
| 460 | + | 35/099 a424a229392a35da8d937062 | |||
| 461 | + | 35/119 28415d77a2772ddc9ff46361 | |||
| 462 | + | 35/139 b142770cfec4946c445f1925 | |||
| 463 | + | 35/159 a2c6d28bb5442fa733cf8ea3 | |||
| 464 | + | 35/179 0c4f1446420ee3280f18090c | |||
| 465 | + | 35/199 d8ab4d4487ea3bb519574059 | |||
| 466 | + | 35/219 f3f1d026b78975accc285f57 | |||
| 467 | + | 35/239 a734eb4db0f69306dc7a5630 | |||
| 468 | + | 35 replay-mismatches=1 | |||