From 73bf44212ef56fd2b4896bc44a3ce0157ae7e7c0 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 12:22:33 +0200 Subject: [PATCH 1/4] Media FEC: a generator for rs-gf256-v1 known answers from the reference crate Before reed-solomon-erasure is replaced by an owned GF(2^8) construction, it produces the vectors the owned code must meet. The ignored test encodes every standard video geometry (k = 1..=16), standard audio 4+2 at 1, 37 and 1300 bytes, a 4000-byte shard, zero and 0xff data, and the policy's edges (1+1, 255+1, 1+255, 2+254, 128+128, 200+56, 250+6), and reconstructs four erasure patterns per case, asserting the crate returns the original data. It is run once and deleted with the crate; git keeps its provenance. Co-Authored-By: Claude Opus 5.5 --- packages/cultnet-rs/tests/media_fec.rs | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/packages/cultnet-rs/tests/media_fec.rs b/packages/cultnet-rs/tests/media_fec.rs index 4af9f243..f28dda77 100644 --- a/packages/cultnet-rs/tests/media_fec.rs +++ b/packages/cultnet-rs/tests/media_fec.rs @@ -1141,3 +1141,98 @@ fn a_budget_at_the_media_record_ceiling_admits_large_shards_end_to_end() { .unwrap(); decode_media_wire_record(&wire).unwrap(); } + +// --------------------------------------------------------------------------- +// Known-answer generator: reed-solomon-erasure 6.0.0 is the reference that +// produced `fixtures/media_fec_rs_gf256_v1.kat`. It runs once, before the +// crate is replaced by the owned construction, and is deleted with the crate. +// --------------------------------------------------------------------------- + +/// The data of a fixture case, as the fixture's header defines it. +fn kat_data(k: usize, m: usize, shard_bytes: usize, fill: &str) -> Vec> { + (0..k) + .map(|i| match fill { + "lcg" => bytes((k * 65_536 + m * 256 + i) as u32, shard_bytes), + "zero" => vec![0; shard_bytes], + "ff" => vec![0xff; shard_bytes], + other => panic!("unknown fill {other}"), + }) + .collect() +} + +/// Erasure patterns of exactly `min(m, k)`-ish shards, each leaving at least +/// one parity shard present and at least one data shard missing. +fn kat_erasures(k: usize, m: usize) -> Vec> { + let mut patterns = Vec::new(); + let d = m.min(k); + patterns.push((0..d).collect()); // data from the front + patterns.push((k - d..k).collect()); // data from the back + let spread = m.div_ceil(2).min(k); + let mut mixed: Vec = (0..spread).map(|s| s * k / spread).collect(); + mixed.extend(k..k + (m - spread)); + patterns.push(mixed); // data spread across the block, parity from the front + let mut one: Vec = vec![k / 2]; + one.extend(k..k + (m - 1)); + patterns.push(one); // one data shard, every parity shard but the last + patterns.sort(); + patterns.dedup(); + patterns +} + +#[test] +#[ignore = "run once to write the rs-gf256-v1 fixture from the reference crate"] +fn generate_rs_gf256_v1_known_answers() { + use reed_solomon_erasure::galois_8::ReedSolomon; + let mut cases: Vec<(usize, usize, usize, &str)> = Vec::new(); + // Every standard video block geometry, k = 1..=16, m = max(2, ceil(k / 4)). + for k in 1..=16_u16 { + cases.push((usize::from(k), usize::from(STANDARD.video_parity_shards(k)), 37, "lcg")); + } + // Standard audio, 4 + 2, at 1 byte, an odd length and a full datagram. + cases.extend([(4, 2, 1, "lcg"), (4, 2, 37, "lcg"), (4, 2, 1300, "lcg")]); + // The widest standard video block at a full datagram, and a long shard. + cases.extend([(16, 4, 1300, "lcg"), (3, 2, 4000, "lcg")]); + // Degenerate data. + cases.extend([(4, 2, 16, "zero"), (16, 4, 16, "ff")]); + // The policy's edges: one shard each side, and 256 shards split every way. + cases.extend([ + (1, 1, 1, "lcg"), + (1, 1, 9, "lcg"), + (2, 1, 5, "lcg"), + (255, 1, 1, "lcg"), + (255, 1, 3, "lcg"), + (1, 255, 1, "lcg"), + (1, 255, 4, "lcg"), + (2, 254, 2, "lcg"), + (128, 128, 1, "lcg"), + (128, 128, 4, "lcg"), + (200, 56, 2, "lcg"), + (250, 6, 5, "lcg"), + ]); + + let mut out = String::new(); + for (k, m, shard_bytes, fill) in cases { + let data = kat_data(k, m, shard_bytes, fill); + let code = ReedSolomon::new(k, m).unwrap(); + let mut parity = vec![vec![0_u8; shard_bytes]; m]; + code.encode_sep(&data, &mut parity).unwrap(); + out.push_str(&format!("case {k} {m} {shard_bytes} {fill}\n")); + for (p, shard) in parity.iter().enumerate() { + out.push_str(&format!("parity {p} {}\n", hex(shard))); + } + for pattern in kat_erasures(k, m) { + let mut shards: Vec>> = + data.iter().chain(parity.iter()).cloned().map(Some).collect(); + for &slot in &pattern { + shards[slot] = None; + } + code.reconstruct_data(&mut shards).unwrap(); + for (i, shard) in shards.iter().take(k).enumerate() { + assert_eq!(shard.as_ref().unwrap(), &data[i], "k={k} m={m} {pattern:?}"); + } + let list: Vec = pattern.iter().map(usize::to_string).collect(); + out.push_str(&format!("erase {}\n", list.join(","))); + } + } + println!("BEGIN-KAT\n{out}END-KAT"); +} From d866cd5916d204c1031cf8700f490de1b56c41a4 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 12:25:30 +0200 Subject: [PATCH 2/4] Media FEC: pin rs-gf256-v1 to 35 known-answer cases from the reference crate tests/fixtures/media_fec_rs_gf256_v1.kat holds what reed-solomon-erasure 6.0.0 produced at 73bf4421: the parity bytes of every standard video geometry (k = 1..=16), standard audio 4+2 at 1, 37 and 1300 bytes, a 4000-byte shard, all-zero and all-0xff data, and the policy's edges (1+1, 2+1, 255+1, 1+255, 2+254, 128+128, 200+56, 250+6), plus 120 erasure patterns mixing data and parity. Its header defines the data, so another runtime can meet it. Two tests drive the codec's public audio path, which takes any (k, m): every case's parity matches byte for byte, and every pattern recovers the erased data from the fixture's own parity bytes. Both pass against the crate here, before it is replaced. Co-Authored-By: Claude Opus 5.5 --- .../tests/fixtures/media_fec_rs_gf256_v1.kat | 1322 +++++++++++++++++ packages/cultnet-rs/tests/media_fec.rs | 128 +- 2 files changed, 1447 insertions(+), 3 deletions(-) create mode 100644 packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat diff --git a/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat b/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat new file mode 100644 index 00000000..4df4617f --- /dev/null +++ b/packages/cultnet-rs/tests/fixtures/media_fec_rs_gf256_v1.kat @@ -0,0 +1,1322 @@ +# rs-gf256-v1 known answers for the CultNet media FEC codec. +# +# Produced by reed-solomon-erasure 6.0.0 (galois_8; encode_sep, then +# reconstruct_data, which returned the original data for every pattern below), +# through generate_rs_gf256_v1_known_answers in tests/media_fec.rs at CultLib +# commit 73bf4421, before cultnet-rs replaced the crate with its own +# implementation of the construction documented in src/media_fec.rs. +# If these bytes ever change, rs-gf256-v1 changed meaning: bump the scheme id. +# +# case a block of k data and m parity shards +# fill lcg: data shard i is bytes(k * 65536 + m * 256 + i, shard_bytes), where +# bytes(seed, n) starts state = seed * 2654435761 + 1 and n times sets +# state = state * 1664525 + 1013904223 and emits state >> 24, all +# arithmetic on unsigned 32-bit integers, wrapping. +# fill zero: every data byte is 0x00. fill ff: every data byte is 0xff. +# parity

parity shard p, lowercase hex +# erase a pattern of erased shards (0..k data, k..k+m parity); the +# shards left recover the erased data exactly +case 1 2 37 lcg +parity 0 1d76461ffe37cefddcf40fda2b204102be510c9f0d5207339f2a8a89c6c91c7d1c2e1e0c7f +parity 1 1d76461ffe37cefddcf40fda2b204102be510c9f0d5207339f2a8a89c6c91c7d1c2e1e0c7f +erase 0 +erase 0,1 +case 2 2 37 lcg +parity 0 dd18e10df0fc6c0fa9d8e5e86e4fd21a766207cfd40f5b1f846d30267007b52a7d947ab4f5 +parity 1 db201de79683235efa2a5e1150c2cb417ed32b0a58354c1dd1e80b93460ffa8200ec6c14c2 +erase 0,1 +erase 0,2 +erase 1,2 +case 3 2 37 lcg +parity 0 9f31dd8658475a0b0efce438c7f7591a0596afdc19fe90fce15355887c7de3ca42c39466a9 +parity 1 8db62234c2ba4073f6367b9738b0fb00ad4b5908c5c5482725cbbc1b83452c75f9dc380860 +erase 0,1 +erase 0,3 +erase 1,2 +erase 1,3 +case 4 2 37 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf67028 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 5 2 37 lcg +parity 0 4cbfa04fe94c62ca970a747f837544ca91e95d8364637dcb67f43eb3f829f26edd276081e3 +parity 1 d8be8a2b22f6180407e961be1d6804df6eb56cd85e0e78d4a56b54aa4832a316a9c71fe1d1 +erase 0,1 +erase 0,5 +erase 2,5 +erase 3,4 +case 6 2 37 lcg +parity 0 d61fc5398802d8e9d450d52e1c03f37233b7c586fad4ea84a5a81f6f535ee5fbef8f12a068 +parity 1 ca4789f2758e3a32dd0131195eb49d293b2279d6830a0c9a5c39685cbd592a937042dac379 +erase 0,1 +erase 0,6 +erase 3,6 +erase 4,5 +case 7 2 37 lcg +parity 0 63f9bbbf4551c9c0155b03f6ca672253a3884cad31a3609a5293e5570d96039213b9b7d898 +parity 1 d833010b405b5ed00222ce40c128900dd93f0ecc8d9f6ee6e45000653463d7c3ae0d2deeae +erase 0,1 +erase 0,7 +erase 3,7 +erase 5,6 +case 8 2 37 lcg +parity 0 1f2644cdeef6330582964313ac2a88779168a371ce25e9a331a2feb0e926e80a3fb6dc956e +parity 1 07c99275ea440ffe50a30e803a3cc7b399ab77b35c1e394fd5830164d43b8863cc1128849b +erase 0,1 +erase 0,8 +erase 4,8 +erase 6,7 +case 9 3 37 lcg +parity 0 1783c9967a6b04b7747a8311d8d20945a84c966d9de43a32e8ab7542ae536455acee379d42 +parity 1 7daef83cb52401f06d8e470a46bbcadbafc1424629376b5a9125ef2c0587ffe9d84bad60c4 +parity 2 1faeacfdc3f9e3784c134f037fd1e21646d9fd81d0028752a00a4aae093797896fe4c700e6 +erase 0,1,2 +erase 0,4,9 +erase 4,9,10 +erase 6,7,8 +case 10 3 37 lcg +parity 0 62af6d8e84589baa9d526110950638f093f96b94a45b2d9c30b6a9745234574b9ead9e066f +parity 1 dc28ca8f0e824db76227dbaf40733617dbe74da368e0538121c11a60e82bc4e3b47c8fb74e +parity 2 792cad82f9efcf8246d704887e1929eb262662d402ad1a1105d191b8e8fa6511f901000350 +erase 0,1,2 +erase 0,5,10 +erase 5,10,11 +erase 7,8,9 +case 11 3 37 lcg +parity 0 54ff5670647c20b41f8ceb38e74267f6aaa30804c845d4900e44db8ca11c0f3f0751ff5eed +parity 1 8e3025f78568c3c891d514c143e94db1c9886f45c82af0229a8a1222f14ebf6e761a497780 +parity 2 5a35595594b8eafac3a78f9cd182c985ab079a03fd844c7bc1bc473add2e24b9e6aabb6107 +erase 0,1,2 +erase 0,5,11 +erase 5,11,12 +erase 8,9,10 +case 12 3 37 lcg +parity 0 46e7b0691080090c58b9665a5f1c02c559e279cbb817e8cef8d1a2614e96686fbdfb91a03f +parity 1 70ce7f47fe7e88d78c4e0e8958e93c8b0e5543a62d7c61d55959031e518333fb131342470a +parity 2 925e128717e460de9d9c3e5f1d8a0a98e56c203e74a8dc831262bfd648b79ba2566c80ea79 +erase 0,1,2 +erase 0,6,12 +erase 6,12,13 +erase 9,10,11 +case 13 4 37 lcg +parity 0 5dba6319228d9ffa4d4b314912b2f6463a8c1ff416a1307299b9a0994ee8e85ab12a8cf659 +parity 1 a473d1a714554131a8b900d9359fa176c2c2562f3ce23341c592e3fefa6fd3db019d8e77e8 +parity 2 485ac4a9c57938083530f17f2133f8bacadeb7b8f51540daf9266fc6a8a9b95dc781d58cb0 +parity 3 f2de139b1450bb18d36906a9883d94f5fcd1d50574654f467df2ebd5e7057f7e0edb001fc2 +erase 0,1,2,3 +erase 0,6,13,14 +erase 6,13,14,15 +erase 9,10,11,12 +case 14 4 37 lcg +parity 0 d6fc31195735a1ddf12dc46e1769d12101aa30c0775e05ebe21fcf6a741e3e80f0a2222eb6 +parity 1 f41477b7e559afc4bc64ee9094ea1582996327f11dc7f9c07e95456981810d8966810f8ee7 +parity 2 f48caa91c33fd3601dbf9b8d4c650811811b4ccee2bb874125319d8fee4352229ef5ae988b +parity 3 19a11e9ce28d3af19a6a896c3d59941aed6c97cbbf851bca8e710b4bb9354c667aa7e6a8be +erase 0,1,2,3 +erase 0,7,14,15 +erase 7,14,15,16 +erase 10,11,12,13 +case 15 4 37 lcg +parity 0 436fd02724e4f85321b35365f4e8fbf0b8e59172b59a05c0b133fee9efc201c802249660ef +parity 1 5bcc7fa05ccdcfe4056842ff288d85716bc68b61fdc4d00e60c1c828a1737c1e902fdef318 +parity 2 e426dba0f19e3460737f46901181a5bb36cd6e5428a8cb8e54c8e8869838eaa0f136ea659d +parity 3 7c3f1eb480e565aad5df965694b15d758e010f5f85df523585762922c16bd74569c9921b4c +erase 0,1,2,3 +erase 0,7,15,16 +erase 7,15,16,17 +erase 11,12,13,14 +case 16 4 37 lcg +parity 0 85f104a041c87405e95cc483c09d776675d5820ec5b2f06cb467f6603a6f6c28f82bb7e123 +parity 1 25c276c043503575a53501e07972c6fa4f61faad0602b1c106c825f32094d08a469bf5cc58 +parity 2 c1def77fb8d48ef70aa21ffdc7a53c19535c94f1d9d2a4f9418ba26038abbfb4ea681f4b9b +parity 3 cecdb160ca6d4f767f38aa79601a9b78123c1f6c57a032264146ff7ec479dd12fa9c05cf69 +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 4 2 1 lcg +parity 0 a8 +parity 1 98 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 4 2 37 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf67028 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 4 2 1300 lcg +parity 0 a8ee004fd095063619e7584588e5a31cc94e9820e3a5d2d55c36b1e570ce9f68556bf670284a2589488913ff2c91729b64c6857dacddf1709c4c999d687804effc454640dc56ae2392086c72e5283b9fac105113741de8cca8453864789c22df919591a512eb999af9d6845cf933f15714ec42b35d62d2f57fd9de9edefaa91114ff1ff4abffb647030c6057d9c4c9106a75f6c4ab1063b7ba0d1501c85c1ca6abb77b7cb0cc55cfb5efb037017afeaa3baff4cb0ebfb3469dcd5c757c4ca73201f09d7c7dc660686f0d211188403ae0bdbfdf71da0013d3cd2e4c2f197ff34868eecad4e56c54f14752ab3088aadb26c696b8675ba561e4013abba81fccc430b510c45a13e0488891877cdd5fc9de193a534377b4eccd66b97f3368f8b5e1bf63e2518331940770718c1740e08c2a75f605973194057647d27263467d3f31ab4c7ebf3d7b9f02abff4a2c1d7a83896ff936b2c25cd7fd7b58b3967576e65cae819c71591f64df1d217dbbc866bf7dd7d10efc4f94eecf7b8cd0a8c6f27a9fcf32d97940f5d2accc653c883fa40d4d636e59531cd006766b489b893290a934ab7c1e2c8c61cfd0c218359d97d718b6ff90f5a2d86d6e1de3477f20f92f7893a80c7a5dd8e8f81e43e6f01734dce33676794d043257a63998fd92a4a6fa058f538e890a0e540f81b04bdc9449055427ed6b38da5f98b3a41f4996d796b2b9e0de32cfcfa9eee9ddacbc6bf714f446eb93e324b309c2375cb2e2c3290fd8e6d5ef1ed959fe35febb26c4d44edd13c568b1b827dd9a6e74fbd20061824b2647e612e3eb6eb2e73093b1f12c02fb3a0e03eea185d55b8f0f7657d73d209a99ca7da9fc52014a0278a01bf06fc7dbe3fa501fb1bddfdaeaec1284cb5f1d816160b677db1d69a3cd681680b459051f039417590660eaee8f551865928a50d6e18cc949bff156541a87834c396832a7d9df6dc92425c1b6184e41dffbfbb863826c8dcca2c45019a8f37b5e44e1b3920a177f224e32dc8f4038b37a05de36e8c2d1ceb2ea1efee16954b175b972d27adf19b4cf91a76595ebc7fa36fe4bd913e51c8787bdef2572273d21d4e3d65060a1bcf06a6cbf488c97d1e4e95ad4090be1ee99fdc03cb1055e562456a28025945f7b21ed1c058d2c754af83724da667313366877fe4e75f4be28716e7383db3e02854908fb5117bc94cd2dcaa6162212f5068aa314fe279932d9020be20120052467b75002abfc9552c71042ab5c276dd9fcc0fd5d6e69a3cbd1e23c2c802e7cd8c1ebfc085b4739eb0cb198f734f758d94f19c20c4bc59bf8995caf13a768cac10b9f8de6b0fa005b5c963735edcedc3da07fea83499bf65b6e375682aa7aa9dbf10360d76ff134560ace09f8460060fff0391de5348922653dc19891619720fab3dd46e4ef470b27e3aae767d516d100c5b315a308f3835cee0e6e8d96847ac29db8e3fa06d6bda07d37c6f9759c07cd4a04a72bdbe0ce25ab269778a8c8f2c08aec1401eb23d9df2474dbd53544e10667414f39339c154033a4c0ca44a6ab631c67ffd1ac1e52b42f03d6b2fecb4bd22a4c4e25c0722ed5111039ee32e55d3c5b84478851065b9b7c99034e90119a64b4d3c0992ab83a814bc661f6ea13b4694783857903eb31d3739858a729e652df22000ae17c6723af64607ef7323392d74e4bc647058d38b436aa3573149a296dbc9dab14ae415585822e6eaf63fe7f1e6a22b2280204d9a0bebd0bf68f80a057c42c81b48ca28494569b407d1b01c05b078a23bceba8aed30ccf6e192dfc53be1e6890a104261ceb3d13a5d4b1057dbaa8c2554f294ad1 +parity 1 98d97eef9511077e81e7a54752088dd5c122b9c9f2ab3dd78e98f1435ec6709f8f49edd0a3fa193eb71a66f7374036d453d4f639bcc7fb5f6c8b4b6932ee123eb6fbaec4bcdab7bd7b914beff31eb13dcf1c81d5553567b5fdbc838d38216762d8da05136081c250998a2f7abdf12adcf9b4268d58907113293eb1bf8d8f8f0c05833d7cf39cfd20129b951528c31849539c4607e88fb4c08b9c7b432e0ed6e3aa66742f96b9f9e0646f497380bae9ea946f87285a4c372b470dd3bbb8a52771461d42bbf9c5220e301988ac9f0bf944646977627486dd0fd66dbe1a2b63fe0a02e273c17874870e804d2fb2a2faef323058032d11ab7fc7a30f1f783da2c6f3e115e339c35e0997c268dd30aa8bb15705281537c7e85ce8022af05b31602eeadfa8135246ed91129a8901c21729f643f1f7814de50b7cc494ab1a9cb3bf73023d73bb85673fe5fc9a56a75e6fe9c31d53eaf7e175baa118eddf5446f7c0cf6ffd75f616b3d20f1f12b99ab0b78001b4b09fddc1ebda719858008b2991fd268bb1f18c24ea014025b47c1470a51b111d4e5aed12bc5de6e6e684d19d3d5d62d8f71bf61718654acad324f16b1128627fe6de15107960b9f6a1635da4b647c7e61fbe7adc7a91c1366e5a7cfb492d833cf7d6127d123154445ff19979908c3dbd4b0cabdf6e5406690c9479f4cde0cb0cd2d980442e2be28e176c29d841e261a5b3e45d9eba698b5a7f44b94bb5907b7007e00641f6ddfacabc5028c9a660c015ffd19e81b52c6732fb9cd89e1e0c2c577d890c3e048eb7f9524658298df43aa9e152f11fda1e2eda74ac34f0051ff12b6596e7ba4b5b1cf9057267fff889a918c71bcc50c1a6e09e6aa55a7478bf72c6c6954e0a746b0621fa1fae6f09535fe0a4991f3a2cec769a823130c0e950d4740c2577e6df7bcf330d305edf8e80af521afb5dd5c94cddb715f54fe38fb6ec0a570a9475ae9a6a3aa8aae36fb15711eb09ade6887f8d347532a5b158a8fedac6424c3133d1a9396d6e355964b398b8aea63e68bbed134f335b6280e9f6070be60099af4992b10eca6beaf400e876ec23729b6686b7da637b1fc80ff688f850a8b51d6a59881f7fb114318ab85f4537a5d63c1ac26480cfe1d6b41f10fd401f4a5def8dc4cb4312b740006998f0ed7766580eaec26f7b18cee4f69f7867aea8a9eacfa63200ffb9c7dfa3ddbd5fc612f2844d5592b004897e8c3d7c51670464b75c6feeddb8f8f18f9c1e2c5408807aff0351316f8a143b51b688bfc8f72fd693b192fc1aff4614fb2da557d217e7cda5dbe4725368c886c69a795c6d823ba2b9535c41f6ae4716a30d7948dbc8f65627946d577e3a282786485f5fa73ebfdc63f53321759e4681d53845dac77ebfced9c530dd15c520797fc9d5fc0fe1fb53eb04af1e86eaa0db2b080774e5c902628b3a3d6df445cfa17d06ec536e79fdebe144b61f3bbd269f690f0617f155f3f6792ded800f9ede44d0b77951b28676d9d9898ef250d67e9c0bd71880704b9b1d695b3dafa9d165de70a285e38ec927a93fe2ef4f0ac57c2a622073dca3c25740dd55f7437036505d207bdbbe156382d841741a94942447441be17d95c621bc51260f5c930f4727308cb7e1bf68611a275b5f8da545279323b8096678ba84a0ab805b3162996fb34de22e7fca19820a893effab1095862dedfd711ec413769cf84e93bf71397636efaaf16915f89d48f2ad4a5d7c473901cc350ab47554a303a04efc7ba02a7f593aa45da4bd66b70d45c0863ee9bb5048c894bedadc5528035efca372af3ccbbbcc20c1240a0b025c2e4ee0f5eba8 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 16 4 1300 lcg +parity 0 85f104a041c87405e95cc483c09d776675d5820ec5b2f06cb467f6603a6f6c28f82bb7e12333145d30cf956f6de66d56b8a76754f2b8d60748dae4bee952cfd9a52538e6c13e6fa87b831084edecf3b4e396c1cc7b8eb2abea93e54e63a65cd71d243e7f846cb23dc66df0ba6158bb1b8342a3fb4903083c0f7b29097c88a19a57a98aa959cce846d42ce6214ad3015e7dda73c6e15c1a9b302954ce997b31486be47de00707f9e164ef564d38a12c156205a8b838b11e403271be18a1c6749769e7752e492146dd0373d2d32c4fcc591d128836b6f7e88ac9ad7c5518f9ab56f76eee76fb4ac8e39d7cc7b9711f06e8543e36ecbc8b6b3a8febf0c0424a7a24644a958c989abc25630011444afecbce36b61cfc7098d56807aa31cdecf3ae7d481ea9f8780bb5f4205a936d4bd61409e13cea8db8e7e2c44fc8d9197a9c3131e4edfe9127f1057cce7e42003908410c4f8afbfea530eb4a995299a99b938457933c273b131ef8b7d09b48767e65f4602032e821356a8d779a0f890f7d773326fa3e2f9f79ef71db0690d0107fd6f73c546fc7a3e12f862f2285eb23c598cd5e0bf1086c1df709948f031fbb351ac9e9e77846eaa598ef6272366f0aa667e781accfeb6261225753d5cc8d20415922c8a8dcc5e89120db712a092b6d3722dd9024bb0bda72c83d08527e3db8c1211ebc729279f7026d9119b95f4acb3ca0d2cb4984b0a92265c756feb48c435ad438332b597620b7749b21f3c5aa2ae228fa07fb46f2c8ba9319078dffa9ee1dd4ba0f181d94e367c1596466355f76ac303acc3b58bed365361a8ddb706746fdf595bdfd4e53d0e6d24a37b416b92b09ec173c091f4587889527fb505003c5ad3cd6dba9f56fc163c2bbe859a4e99fa4484341461822fe998b2a8866ed93918a686dca1608b3b3126ed3dff8034d2aa9296949bee7048b76204a65e2d214a096f465b38cd69d59b5591815d6f70c54d904abace5bc1d0e6dc4adb0c5a75b0d3f417519b3921aa63216eb69caf8a41ad29c1f071d00b6ce968155b3053e4dc223670e8139bdad5edb9a2c6d2dd1e4bb8f12b1bb534bc5efb415df36b30a4f3c71152abfcdccd28fefab11bb857bfb777cefe8d7f6a91087f6a0d31b52f33b4e66a8177fa0349d5fdbcdc84cd1b0f0b9ff6b841180bc746ce4738661ffc08eaddf1b9ed2e1d1216a0b9c741c57521d972a429505ce01bd54ea6191a5ebd495202e3bb24609b3785f21bc64cf9ae3bb0df13ded758e501725a73d6df6c89e1eb3c15757a88646176f0ab32392327ac53416b67a17d17d64c9471ddd3f083e877e50f1e335bb1a887e3297d1863414520a3722bbe3b9952b0d1394454e71622705fb49411c457d5003c24d2325b8407cdabab97fcce97c056fa6f516cbbdb984397fb5d80b0d6680f3fb4b755f53a6118e721bb0731a5f793651b16da7aa9a6f97fd7e8dca561052b6f85b14a6736bec6d9549e35cc90dfd1a07c85981a10cd251ac69b96574836a21e3dd536031fd31108d5f572a58e333bed3b9e51a149e074680bd0364fb0e7f71f40ba3f33f4b156bf54ad4f5c0d74c2b41f94f03f11ef93d5e4ed92e8d24f9b13295178f65c0761ea507b35b65d626fc452378c3fea7e7d2c24d8955f1b5fc7c673bb25772c9910b1c36cc0ff770f32002c54bc52e3bded5580fed0a16eaed351992178ced525364c2c38ea405dbb318ddbaee25a157e11f5525c3b073d7bf0069f1bbb8e4df3005c5248b70edd166716eba20390bb295da0135916e406e3363dddad52a4e2ebf47e242fb838c213419461afc7dcffcd1cb2cf546b9a7977aaa +parity 1 25c276c043503575a53501e07972c6fa4f61faad0602b1c106c825f32094d08a469bf5cc5852bc50bc7e30ec60898c1b226edbdacd137973fa898908bcfab88e1ff015216383523f42f7c986b4f89b6ea6f8de9e401a7a865c36924723a85422c6986eeb551aeeae0696c01028f7226bd5e79d42ca2fdb07f1e668d45f345bcc8a30b07a24bf8bc04c507b90462f3e2cec768397cb12afce61252e240c1718e39158b0b2ecbf9391d43ce60b365928bbed1247957c53cbc68fcab81a2895c9021983b43e36607bada430e87eab160a23d326a403f99278894b5cd05d8d70b278150355e42cb6c9e2bd8299c92e9f1a6dc42e2ae016ce606985116fd98a0d95259ce983603edd896cb307e3549c7741b554f03eb90725c82af3ee82d57370f8922377a5a06de63dd828a6546844fdf1bf830eebf62d13f3904360b6993a7ab11bddc7cc497f7e426555ac679b76dde2acbd05c00e3045da6422739778dbb94eb9c79f3c4f1afed83a9f73dac2dfd69137c756368bedc18b51244161af375920608d1ab84189f44a71befab5d16982a3ba2d3377f2de9616ebde5eeb185b835d235a2d014629be593fa61022bc875321474f069eac9441897c8d262bb767b4e45a05c02a889f1b4209e69e53726d429ee59f404dd995698186217aa8d61f53981c599a665bca8785a025af491c47e869172e65f77ceaea3c7ed6399115ebfc71ec2f74c2122570dcc79a77a82b708ca810d1511ac2beb887752ccbb10b7d8e02b6d8f347a165c09cf604843a5ceac51a466c0c07c9ac2100cb658b82acb48dac98ff0cd4729943fcbe1dd1447e1fda5879ccf46c6acb35392a665853219b9f888d4c6592943cc8e5e7a1881a8bd8f319c1c58d54dd4850572a794f66dbfd86ceecc355c6a2e0557374a921691183cb4da4ae13261305d750f44dd5797867d6255ed31f1641f097af5e9e3c94e779629ea3f23ecbc3ba973323670e7fd506f07ded135a60a3d27f5d86cfcc91207c412f53df27745bb603c4e029b3dc6438de42d26f421ec718e34b7ae90a77784f34bb1e15e1def1fbfc661d3942b11fd215a568fc3fa2771b66c93e0fd60da311bf8a4189a2a554c6c68ae37ceab911255c0bb2ba647606e3df65f60a419bca9aa60e600d3f929501cadc29ee17b2ade204a07ce3227b27e2f993eddb8effa59c8d3a235d9eab90135a74adee00d99b8155b1306e7246962948bf2c9804928356456d0dc5ada1eea6ebd906f2a4c9e270dbea9cf6ede8e359aa3f86b90cdd6f2733860171adbc0cc3914b0bea6bd575c3224002ec90842d7c570ef3ce0db05f980e66be16295efeb4f605372a2bcdd20e955bddf8e02dfe8c50691b6fa923d1585c987a0473b47b1054da3e8594d9b8578be77fc9541b0b64de511818b1558dbf079af95d77e3fae697db8b9f186a3c7c8776b42b1c4205551a2002c3e624d4cbf9fc10368a76d2ecb25f4c0126358baa26f261d9d6aace6125ba024bec6d916bcafb92480ada6a15e8812969299fb70ba4df4240a7f2bbbdb7c5548794d25d11d802e0687185d1c0396b422cb7737c35544289aa20a9adb9c1176091109ca1ce8fdc198275e7507b3b2adfa3b23dd394e1c30809a18100e45187c2518e89fbfedf98cef84ae181a7756ac90eeef3e3d7cb6c5937af3d3c21f51b04a3a89610905f3e33c4f2372dacd24a41dad16914f9c5de5c6d3bcb1c6b7ba351d4f5c1874f62c81cd078e13f6460f254d77514ad36340a7d0ccb7b674e578b049d95aa9994da17cee8a60d1c56997ba6df9850c61ec88987c7143f86b0bacd690840c8e5eed3b6a1a982d4be +parity 2 c1def77fb8d48ef70aa21ffdc7a53c19535c94f1d9d2a4f9418ba26038abbfb4ea681f4b9b80f897cc62547e45bd90b3bb4c2d349dca16d288ac5dcaf8e54f50cf133bf425ec9e81159a79ba542d08e789771e444b682f52cf1ebc92883439f410e67e374f925d8e9cc80e752a02cffc546df776090028be00cd43201da2a7ab75ab40d750dc5dc2421ef270b66b8ed817908e4648a6d71a9a108915f53246e775d7fbe3330cd50a10686dcee7bda7d59f697bea302e3c537161e9b45ee096c42ebec3edb2c15b1de7429f090cd7f305b3883c6afc5a434c0abe24174147c5165dd3488e4f506a25775d6af9ffe608c338c3cdba0180b5661018a4e61e21be3644493201881e274a60790c62c907d884c03fbff6a11bcca79aed297c103c28793ee1dbeae7bd27eac73f847822cc6b8b3f0e382646c7086feb1a794c1dff6afb3f42d536a10300a27d659f5268991129da95818c7dfe1bb2fdae912c77417654f61c7804364f8471467f2954a1f2c70cc809a4ddcba86707e6dada3bfdb51eb897a5f5800434f511e47cca68594c2d33b248f9c80eeca626256bf52a79d215f973e0d53ee056326fd6a514ffff012279ed1453bcc47899d9a6de7f2c3f91800be81c18961100db07057eb5796a847ccceecbf6ee09a29a358fad7555232c0efd1b264f494be713767e05c507ddd7344633bf6ef9492292660632157275ec03317a015b7fe096118663bcec9c51710f2d6b7eb52984e15b70c21de6561c69900764f11bb35d50dbccf249f9dd3cba050ae3b04866e5e57135048d7620de0fe02f2e78177ed7016c39fa6ca13a106725fe002ae916357aa05a1a33dc832d728faec96c1a2772aee8255c6cb843f6a74c544eff20fcd76aa9b2812691dea9253c6d9ece8f9f9b30d798078fc77620dfa47a3319cc0c21012addfcea1b6bfbc3d21918118441812b705082dc1784a45ec935e7f780967315df01edfcbd282faffac95ce541b61015c91e2c7ab0d043ccfbd8a0b8df27411de63ecf5e1e5c464c7d22a2d55f86335ce2becac2a2da8f18c1f9ab4f9ffca28a15f8435a749a0cf4e305fc1a63ac4d6df5ea5e70eb7e13b96a427320f8e688fa8134ac82fb38acdaa9166dc7828301dae3ade26e0125ca1c57d813a41f837574f78dc4bef94a2cc249f9d6f7e8f69350219a64163b1c9dffbdd01968d43e8d4b5e398ed0134d44ace13de4e62482de0337501406e468d4db20e3d1c92459002034984f522246f90d8349be1fc56489fd365f18112b51e85718e104402b553c4f341fd72ef70b05e42b0c0e50fb847c6bebc68978c9a26488494b080cbd4268ef7c0c834cd46654389af554c1fcfcaaa9aa11c87856c57c7847f61e836738a67ef186c7fadbebcae06a8b3ed72f2ccd09bdcd92afb44302f7f297ed0c4a5bab9a7fe577d95bbdf41108ca06b4fa73219c0e29fe8b9671afd99b9e380f3601bcd722baf71cfc1e57645fa38ca155ca1de08a6f655901d1617ed7e286c1f13e676b4a6ffbaa3c21de9a6249f32cb3708390c88da2e460648b67a297d48df8963e9ce1aae79ab6088cbbb4ed242687b88143303a04b360e17365d1a7d99230b5dd93ae498c622412c8317db80c90d0d8e619e26255bcbdb7e318457d22825eabb9216a242d87cbc484722a2e34c9bfb6f6bde8533161a28e5afa0a9d61adb70110b21fdd53076cbe33a9787081a05210fbce1d2f69f8d89629577feec55a4ba7fde1013a67065a4e4eca628d37ad8e54b6ebde7e4b32467fdb5c77460cf2ec36fcc861d81e425d53eddec67b0c47dc61dc7d483bc51cf27d4cfa9b76566aac73 +parity 3 cecdb160ca6d4f767f38aa79601a9b78123c1f6c57a032264146ff7ec479dd12fa9c05cf69840fee3a05763be1004d561567481197dbba7be318b51724c17ddc8fc05e36302d2ba0c38baa9df984b5306f35e65701e3deeee7f6e8a8185d31bc7c6a56880c056bfa8c2c180e8ac67216cbe1dd60ec7eec794bb9502158e95068eb34c9517d18f00925e21bceb90be0f7e2a6274bb0ea8ab8ccd8cc8cfb5402ca5e91253acf0fd00272e3adebc452d5b67278c408921235dbe9b867880e13172ab31f4e660217d0ad83c372b21f745353da2e4e2d3c1818fee7416e8877c262ad1a9171fb5b424a2d57ed87bed4c7c00b5f2f0d21beb2df8cc6db6315d0e69daaf9acc2f32f9e97aeb9ca04727abc4035dcde6715a3827a8c85f2398115937eea3a707ae5ecfc81c50ff635a4b7440ce63cbc60c946ca5e1c2404fd2dbb5a3cfed81a46dfb40bd2313c49357d6f16599605796a4246320e17b2d7be8e3d748dc088fb42d3babe46e9b084b9f6857ee950d4595668ef46ec5c0a14f1132e6e5e634c74ab6881957a94b0049c11bf34b9fae8b29d54c65336ad7c5fb83e6ba487e2d605f69baad77ef99aed9a3c92d4396a08979ffc50dc04ddcaf8b0ca0cda334f81471a000f8077e17de673453d7c7a6400edf7c2b6b1bb7a3e21e9e542ef97a350074b5cc1b24c800fa74d53ad5b169c3a0a1bcf1e4a8d7c4e2d57db6099c85834735049dc61b4e6532baff8b0889f9dd60bcda8e71d1115c6bf43b3388b1f7fb698ef2b303c53ecca034c40d27761c671b1e1f18501f193842d91896603bac7d39ed1df95e9cdc20133cb356293dbb202942a3d31215a8136f7745acbad849fbb00f540e44e8eff52a31802723437329a9c8f0915290dbabcbf7234aa2c47afdc4e913186b6d02d3861bf1c03343fe4a89b05657815ebb659430cb074ba68581c1cfbf1c7917bd2ae4bd032f0a6d0b238f1aa77dc03eaf6f84ea28414a2ee3109d172a8d689bb6f3b11997962d839502fe7b615df3074467132ece03a2a889b10fad08420f0824b7f17d4d9bb3db05dea51d8048c52c0415dc6d21a776901121c691280db8892bd645a40d66a55ca686c4e1b258f7805b4f44c93cf929a2061581502ee519ee5645c267ea129f7f047279df3261c63a5c055882b5f1f82d83030916bfba8e543b59fd6d13b9b0e78b8dc2d1d9dcd20f6423cd17128dabd686ea93681a819cdf9ed89c6bca0f82dde6fc79b6978a4a3e52194e23dfeb3ab581fb7a01d7cd72922b274a5fe77f03a21c1c833f73661b0e3a6abeac47d27932037ce8eb4810e542645a534c3e020027c902c1592acbd7ff806cee106077491552315effa60edb2f90069ecf1468d0d1a49f04c91fb3fed5b6c63ebe3cbd181c99a1e9e33efc70b15f71e9075793032bd793b1bff50e759b319c61c29d24209c91726b11a05cb6410e05bb3974cdfc2647e622ddf062e8559e870f5bf09a32a77d46245fd8f02cbb32ea0ae1a111d1060bc764b19b8d558bb319f9cd4f278a17d24e5c1848bcdb2a2f0c65ac3414013e72c60aaed59693328c6cf8f0aead9d87028ccaf1ebaecd0905630e6b7b846790936b9b6983ff04c48a65c37c88b16da6ecedb464a1aea2fc7318791f1cb6f0bf06585e2b27c21524cb4a54bd7ee95c455a2675898d4d88c81d6ac465a6d2f98186fb780b2e1e0836ffeed84d8348f1de726d9bdd71415a8d9fa1336851a79ba842188b4831911d4bfa02a6527b6c5a5413a6858d046d25bbd14cd5cdf871e54bb8f9f467e433f4ef7585b6dda0795be4c6eeb0eca7e2ff61fd259a2e10485f5bcbd31d6adf1 +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 3 2 4000 lcg +parity 0 9f31dd8658475a0b0efce438c7f7591a0596afdc19fe90fce15355887c7de3ca42c39466a9d9eb4cfbdd1e52c3344b99bb47f88f373fd73d7e1425d6d5473112158810cacd6c8db651345d800bdcb9742d15aa909051ec9ef03039d76ffa448e35c78d81584d1cbf16598f09c1bb55c7fa5d73f73d3b22c6eecee2e31b5c7873730da9392b081d3565e9c35f4eef729d38770ca8e9b43bc7d24ee78ad9062c1bebe9fd7921d54c8aca47beff725d0c814843a0f0d606ceb16858c2a2578638d50b27929c2ab4f294ed0a5d5d762b6cf2143cec2067684c415246fb2adf7d32a2f0668f0f222a090cbba0a2ff9c0f0e1a364dbc37f5ab633b0b81a80f9bfc483c0e80d61cdce6310eaafaeaae1fecd59c6de2a730947376f11a349169c7b8e425ef4edfd46f2fdc123ffb7fdf5eed960af439b7bc6145c642c68eaca69b243fb3f152ec5b02234d61ecb8ac91a95bea564a6c30039a26868a3c7437a8299c0eab2d4b0bc9a32167f50f330be99228e4c2f67260897bcf719eac2f323a435f85138870fa16e517e5edbe51b94ecfcaf754fa62a452accaa723fb7fb6f2058ddf0864b76b5cdd0363b3ec95a551b67a63f6f17d4b67e3c796789114dcdcdc25cbcaac28d34dbc9d05acd17e2ddc594d32c63227c876c6360605754db1b76b5f7dd3ff86282207efc18b2eec7e345c49ff4068f24b1a83a7122f48780175d492cd0bc16c8634d286b39765a19ced55e80f2e1e0d7c6023372118bdf120642a97922826b1a2475fdb3b0c2f99df82721db40d7194af59da1459ae5e23f5c629a215a50ca9b225d53060a90b7392dadcff98d479c996ecc27c43b023c04f9fadd7abe573f7dbc6d6a8a8025952d96ae4a42dea867e84a05c393668ddddf46216807b8afa36babcafe4b7140123b9cca9f4e3250e0891124959b948b5550aae343edc7e92b0cf2e40a8def6d86ccac15f0b383230cd74cc207888fe2d502834c96153c2455590a543c7ab43ea278e7a67064c7089e7d8b2b99e1e96cd0a6f68f0f2b0312a00c6fc16a53b3c7951f2f293fc042dffa3ea2d76ae074d2fdea96674c4f588de94079c1a005622ac6c268100c70a47f4c2bc954be1401b15652fb8d6722a91de3ffe0e0aa34d6baa8e9d2b616b2a9cc53ca0f7548dbe3bc11dcc226e13d81275ef0bb84336fedeb2f430a1530424de1c1aca9bf36cb296645571cc5efb77637610f0a12cd1d1d999461925cfa50060096972cab9baad94e140f570cf97867cf7a3e27ed80e6ff453b498fe30aa6098fe1045721b2d24943ce9e3c6255dc59c175d7bc14a3cdfd6e8a264ff97dfc18b955cd806486cd41633f5f19b917d4bfe7b38a8e2913f428ac4868ddfce7304fe23fe741a89c3d2dcf21b707b857bb4d614b4a22be79004df8d03b4a837b1324877fb541bd2d262a703cb2622e82b7275c266900b73362e753f80f0e9d00b31237de97763bcfd5e64721e2d4cfc2387402a0935f795d87ca7f22875aaa70b72765dea646b024f64c82c0187c87e61b4197c2a1d6f135e3e48fbc6fd5189033ea9c0c2f31fa69ad9b403709a97e0b95934c5f2f816d2e91181f7246bf37429df93bfca66232232ac9fc9d8aa1e503e942baa2bde26aed45b67e0910c9720ee7a13590733b92614a418060798b06456fce2334da66ba01f412803a432b51d6e7db63004999ec183d1909063a851bd99ad1c57dd70958c3a01a873f6dfb8706f1ec01940e34dd11664b06fbe27542a1b4ba7543d1d4614e6a113d90ba2d4fd1ed531eb5d3728f2dd9150f448e6466b49e46370204065ae3215cabcd88ece25c9b106f78dbcf3787bcab1444f656e097733a4a90206b41ba44e5760d7b6708d0137702ec5d491b29f9c12b7a92d2aedc09d326a45c3729b977fef91fa723ecad3b68574ba1c8662394ce175a37d853dc47aa5301f1d9698b3f923a4c0f39a49d748f4ffddd1dfb343d1924462d6dfe0393d3e44a3447ab8983126a043006e11d89e123f459441ff63ed40b880d9bc05547a5c2a6f38509ff8576c3be4952133fabb193e0c9f05bfb8b3e7b4e6c1265cb9fcabc6e029d44be54d18c2095920420f1cedf1b72999c1c6abe20833df08334edbf4f3692e83d288c6f30df4bd63bcf138b61bd4e1a33e9b6d688e7ff396ec965d22089c1c5399797648a491d312a6245bf31c1183459a7d1d0cf6615a26d31a2ea8abb0bf5d1d8edb0695ad6be4ff99cd909ae1b4d09b69b40893f9e757fc02fe313ec3cbb3c7229d830e97f3b4d274a560f5f8d2631f269b6843fc11ed870618963b3b62f2fd6ffab5debee09cccb9caf32968db79d47b2fac02dcf899f80422b45c13bc74e34dfe10d2e8f13848d792d997a82f22396bd247423c2f65b07cc52889c97bf0b86ac68a504f0ba7797d30fcbe9c69adf9055079727461e7fa1d1a771387b79e9842b094a64d6d1fee370f423997472cb014563a21602b915aafbd0db7998bc33394e0597fa8b30f8b63399a2d8c2d8d0a725b59a53437317eff5c7ba55db4a32826536a76b1642ddd2c4578d98c625530c14043ac797edbe42d4441197e26699b2f628bc273484ea01b2bd1bc42d3c6ea6e81c5425634d8035c6002e7f4abea71fd1c2c3833137ed2c515db259c2802e20385ee2c02f6d84da3a298c9a970ec8217c6500b166c36597e7b9c7a95caa722d1481cff27ee16a322de219a5343ffd936a58558067a55a35ab0af43b65e44d8ece1c664f3e45a8a262fee1bd37d1c9b2ec44c8f7935bec4f425f2f50207765ef3ea3e67c4bcb0a4c2d2a3ee23120ac5df410e004507433ab6f082589b5a006be8d4cc6c8e2522c5cf62c37b543540263eb133ffd81d4d4be53b32a6fea13024e6bb5809461f9d052ef84428b90f9cd746b3ae3d6cce9deeb1ff736dc3cb024d1389a74224b4aff36a863dc8f54e3e24a725045ebca846d3ed7fe052bc147aeef80ff5aa8272b92a8348d259bf55f2f24df3373448a1f54b9d5bd5c0e63495c972cdccafad00c830dfd9e27e263ae24b4e59f3c9d88270d834d009295107afb97df2f185f0e12c3be20270e7c83c0dbdf287fd236cd91e46654c91e32875864f45503cfe1f387ad17c0194ca5954f43b43dd6105e82dc8799ae68c9d19f73e46d71e51b49e33c05ef8b18be7f350b79a2d7a9631cfeb9c890f4d610628ba937a166e19f83f53700ea256f59c9aca8110bb596a86ecf15a59d69551d51903ecec8fd40a7ebed202efe63f0dca5bd94e474433d9ffe08c71c11b60d52b43f7fb7d46464483584f0d74aa8b808fab854e042bcbca5eb4f6a378ee94b3b60710017d19ad8708309bc2271aa3ad3dfb0b17100e8c8227c0ff615f5d46de82422c4c5395d5ea59e752ef658c995c4a2d6fa14b7ad4b0adecf3d3a1f3da401fcc62c4132eb687f98c2fe65f1dcd55e63c9f08ed64cd13ae367f572492f8aa562daf6ca92c1d9b934538c6f85b86be1f981f71fdae14b80b6e5248512a0a55e02696108541746b206f253284d4e994bfc04929f04568707fb99315bbe5b90e0dfa3ecbfb431b3d190fae608ce6df3e6c76d35e4bdadf90653f03d2c148eb05c6edba0ac28e3f12dc73eb5e023a3a634b0b13cedbe7ee3152c6390db621a832f0b997775c80879298c1ed24e026984e27411385817bce391429659c78b610a1ed29cbe2c4d02f30df0d0898a7ad9166cb5d2a18d48b8590eef75cd564af25aa0d0e3f99144feb2680b2e3fc11162ca1d401e875e26a41f235898df5254b19f52001a979ccbe7fe2d10562469c5120417bcc5488261aa00efd6ad751d2672ee1f073e4ed10cb18b0d2c05f3b4c9c8365dc3fcee37e00f66aa6f1ab6b32f0e9dc51e92e309df594b7d2cdb108c33a09dbd9f91d8556be7e17b7149627687e027d6a98a26d01e1e45a98eade2916fcb970db6a671f0badaac63160594a1446ff6e0fcdb2a95a3a91e3a3ad86c257f114598f49258940f4fada6c9de3c92b25ddb473f84d18357a9e00d94d43ef84a0cc6d7a60ed816468b6ae8fad19cd4f1ee81bde1cdbc8e3c7df83e52bbc94d650c70a1e1d79330cc22bc5ca3f181db525c9915d956f9ededaf3484c5650d99d90b98dcf41767e515c276a9109df549f0e640c46cd5ad59a034f382e6460a5185dc6aba39b9318e19579496a3af1f88d1efc93fd53d1b0914415da71afc06feb727b51e58e4515d7cf5b40fbe1f44caafcd4bdd2065ff99dee099a7bb07842a9a91dee897794837971b78d48f6009221c9fb986a2b609f18ac11665dcb30fa36cebc3bf438007ddcb4ee59e8617773904da76e5be9ed441b99412893f308c03379230bb5ecff01c28ed31f5048546fd026d41c05e3006857e410c4dd66da52eb1628b32b8db84605ce61fb4506ee42988aba1ffe3a0b21b6a0349384da4b54571e9bf01093784976a271e29bcea4959d4cf434b528489858a338277aa73abbfc1f17fa79e024a3b4c0ad13becb63d26e520a08e8e31d795947a67262860a2a14cee09e2f9e04ea251b864d403360aa360edb68caedb5f19437f2dd60d173ebd40fc64767e00ca8e3a92b783d0ccb3f8598b26bfe94878da1207e3bac65046063df1a43fbfaf33648a0f6f4939456e80b766a2f4e277ee243cf63fbc2c147e743c96d4d6abeecc7c8105eae84c7b29f94430abd9749ff752aec3d6504288fe7262645543c5ebad8bb17194689a64530a5952cfda40aa7288c04848d4e91867b8035b36896170d40af461353e6fa634351c784cc05b05d17455d1a41495a700a51b4f7057477eb7fd6590c91def292abfab326733925f9bff8570ddac6d7309ccd99c978cd7ef7d6b6462ba9b462c3c61ab69446d3c1d7722ed7c6270de18ee24c3685d6a5f7e40fb378f02a97ea2dd13b3e6530adc1f29a2c98cc0b4409847cfc8382df7d062a19860b9f966584fa45011a27b49868b4aa3e7c0cc17ab838976efbea25edec1d4bba9245580b7a53711e042c111d3e8ffd3b5cc6246fa6d679b68c0aba113ea1b6a5080f9ec7fb1a1e48efe9ec52b9c5df0a5a752ece9a33fb9047c89e2166629c7268570af5a61fbec1e1d0eaecda14920954bc7616ae1b84d3bd2b5aadaf967fbbdd767c61549f61892a434eaa9cdb9fe5c1c1590c023942fd53cc05cd7ab1e75b577897dbbe5a1b7b2c983d9f9cf3ee699f65011bcd1c02ac4dea4ef91f3b326c6de2c889658ddcde04eb11f8aef94464060ae13a1fbc5010556bc588cfd7f51a1f19f6db6cff455de980bfb18960d3524bf85f7d6d30f4e8d2d329b40eb8367feb5b9d3f7ba70ef84fb9d928822a49aebda04b45f0705702602306ce437eb20da3be0ccd00c2372a9cc6e7e82d0faf29646efec02782481c7f5b3dafe069727a6e59a9809622dc02bca681061e2430b4fdae71b039f7595e38045a8adf6679dd2b632e4a2121298429ce4b6377b79254f6fd3cd25895c1627f716971cf38d6b79af342e8e6c1f0732d59926491ade2e8125db0341e459dea8290dcb91c4ca8279d3fc16b80fcc40495df02e3bb9a48958a4e6254229530bdc40c0cce50bdeb1a7c6ff6f85e289b24028cec87f7f6665720fa88fd628598ee7a6a5b6499115c659cc01e8caa3e2abc3457e2a23fd23e57dcc52957608a1683469fd7 +parity 1 8db62234c2ba4073f6367b9738b0fb00ad4b5908c5c5482725cbbc1b83452c75f9dc380860d5bfb4d609c18a9bc253eadec25917fb990133679b06eef185e7869772f129bf49ce66d26e949db10f7558938e89be24116c9610b6cada1bf07ccf6fc516f1da131d2f3783d5ae5a7c1289bad68b8c41426548e065b303d438db30a26669f25ae43447ed055bc8d760cda1bef3b883e25e487e245e6a8ec994c143fe0dd40163ebd1bd0b9e72818a7417a875b466ebd063b68edd717a65f1b88f0f0801658ef20d619558ff195fb93951c86339d71f4cfada3e7f20dc9bcfaa5e621030f05998d2222bfca592e0b77a15effdd41e546f2fc2709c9e994843006c70b2b2ec29ad51566c82d12103f9f3c314f1cd46ed41ed297cdde81924326b08d318ae4116c6a0b36756a1086cb99ff98b89f89deae44c1c55b6b00f270f60cf041f73b8b6dc978bd5d156b506c3d6f71688e5b5aa74ed5a492be0c20bfe42c8ed3f56bdbd980ce36d85f2b73a90c53247cfed3496dd304f5f58ef3043e5ba375c1c5eade78e456db3e88b2ce64bbb76d30897b8e5f86cb93c02f44e9b0aeebb619efdc0b14de115579b67676159a504792a4447df623724c2133aa654e3f7c1bb7148c473617fc3a80ef1f34e087bd352cac1fba27b232b273b77b3fa950da2a46763f7448f8e706269edff5a231783c5d355a71b9b9cf00490f0515c6706ddf75b82b48e636f75f00a73358bac2bb17f2970d3008212e033b030f5e77ab9845c0c4e7c4e862ca72c19f43e4ce29e02a0b783e61522793cd5e6dfbe8fc79456594ae58303e5df390fb29226d43ba767e1e859ca6a450648c406182f199c74af7332d48bd7811d833460718ea16c39565decea6499f2bcdb7ca4ce6183f167677aac718610d87e8990085e2cce7cb6038da5641be5252ccfb6e09062cb9c49f00fbbbb14ce4a315ca0bb81ff122601d91aae177bed28adaa1bad50e0749b6bb22e21345fd16456ca84e28546f0c2b422f38523e986061613dde2730f13b514635d57e0754f56a6a41db5fc680671769e675345394a0a4d5a96dcd578c20d30d2c51176a7ad2a41e429252d54ef5a8964445a70254a68fe7f87e7c984a82319a0b394697827b4edf578114b786b228fd5f235f3a7979628470caf97690398211a383099224595a1fd6dea45a07374a1cbbda2ab81ee6b09079bebf48def800c8b7ac14612fcfdd4df765127dff0ee2d51d586009e087b388eb7f875a368737e07efa822b708ee083d5813736dc815c300e3a11cc39ed3fd1f6a9f8783a430f5410c501cf852f31ce4c3c3925d8a37198fe79a5e254d185c3ee392d4b806f06c0eb8a1147901a60c43d39382d7c762ff3bfa1c68a428775e3e12d22fa11b4a5cd06e8900ca488904afca4bb07574c8cf2230e5dca426010ca1e5b6d0cd2c206289ce06944625daa27213eef6bb5ef1c4b7441af6719191c1d511e07d3ab5edbd1ef7f6e135cb4291220849ed3b77b3359e1491a7df52883e86713bbe5b14c9f6dde932fd2612ed64fc6eab751bf1243b79749de04a2f6f8c1daae9697cef2144120acb40413ad598854ee06df159c631561ef06c011abc7d651eb8c94db7fb847c88a6582368da91a28599363c638b09201790d4c9ef3a9d8b3fba72490dbebca2f844f26783890960b32b2da80358a11a806cad6813d0ed20edc4d95aa39e31ce6775112f21cbf71bed04f43f61c9a188a8c080c25813b9edb215ba41e35bf223aac9253f3384e7f6c88042a72d43871464089b8a9b6d128a00f582baf30a14cf55793b51f10a2958f9732c4da748d767b8a64eb4a54d1d77d9ea7aeb423fbcac2ddb23f6f68152b6eb371749611fc2e3247511f8e9c8fed5154f43cb8771d6c6ac47b3a6151662a4c328be30d34861d5063e19b3bf2ced7a4d68983fde57f27c2014d7f0f056e880e7db978ff0b2e57909201f0718c62607d141b8cc3f4c68a5defa27dae07507a2832be5e044d24b87ba6921a2df8e8f6c99807ee99c9252ea866f402dfb963cead5bce5dda940c13fa4f2cac7e4cd48b8c967226618bad6cadace7853191e2d09bf68c1c5d19e93139822bbd94e9df52215ed1d06396104173cd5dee2e7f19f8c30a9e599224601d9b2a27b0333b25eaeb35b212f9afc9a0312f581a9aa9b30a71054d8f0a24dc2878b9e0023e15b0dbf884ff2312d64c5d97077659798defea330180e64e23b620c03cc1d85b2b1504e68aca31bd83139d4d3961efa0efc22c8550d73d43d44d917ce9e860309960e866c31a6faee795b002f8c6a0d36c2789555ced1e81f40f6dd1954ac782a4eada84e65c9aaba5f9eeadad0c35684cdbde8f41cad56eb99de2148200ac3e3c2839baca892e8072c93d1d55f43b2ce57d396b964a1ddb1ef305c2f7f0c915eda2605c57ad2d34e99e2ea97c8fc5660d9905a0d35d1c4e17be2645fd355acfac027787ae517718eb5e256f937047bd43acf0215273595aa3da9ceb3e85a76296837c53172c0d609b828868a465cc2dddc7b54011d3467c1f45d534c7f71735dfcec09ac98a2989eb2e8287c37b8a4f53e0fddd642776fda6b601d2ee3e4f88a4ad04cd4a82785d1f21924346138ac773c6688aebdd2aabcb4a3ada64141595ad0c7fc918aa667f1f74b7102b956d5834e689e43ac44f826f1fe6d3734ce61954669b5ca4440c9144b3a0374141fdf1faa85bdd69a15f99228a2eb72609a1b3528e2a432c1fc29d008ad535c6e9c1339414d0105f00095e8b5551326dddca3e5892fc32c8eb22572858f0784194184ccb666f9093a6278ec08cfb20074701f212a25e0717ac19f22deae00126b886daf24a4389b064857f0d6269199d05d366a5b6138bcb0c5998638a75dd17b793171ba196f76de4b1b234375a0a4ca211759a8cecf586364740a963572e76589fadeb31af2c233828a33f5a05887269cbf690b6fd69b875af257c314f7ccb173e64c6032fc7e92fc364017d55f30ce22f379aa8a776474721c1b33ddf3e38e0817528429a3aafa0bc97e221e9f2ec36646ff3b3806bfdc59e3f212fd1fb36f0ac1aadbb10894acc178ddfd8bb64614ba6d9d14d39c27a4c4e6bf14627b0b3559a20e05439119c000eb0add9b2cca503104d477875a6ef1047dacbf47c163234d55c6f064db91c2de73cdd16ac35971d0c5d24575882377d09b0edf776c8acf5d621ba51c4b24e6c25269d4e016a489ebf2ebc8d0b80cc4e39365f46bdd6b2c99a532025a74e8e4a72b1f22dd38f57970893183405114bad8833a93a8a55309f0a24e0edf6a79b1693e9eb4394a289966433c600a9cb0b3ad9ac730a4d8917dbbf595daf415e5da6f869420aab0d1f0ef25bfe0ac102cd04eef5ab4b5b89644ffd56d77b0f6472d8cd4d843601811b0adc71eb3f8f8203c812dbca7a154c061a9a3a9608dd3b8bc3faeea85d9bac2c0912351c34537129ddc667fa3430310a8918c77f72b03a91110f9f8dde0e9ba962f85e78ca9146d94bcf25fc9d26d28787daf4daf3d5f0010b14b7be84a584de43d53b3092a0f2fb8a32894d120e05a9edfe89228e1796e7d405d1730f00597be0c6da789d400754eb0d348316842fcb111deaf516dfc1334cba0a126d26350992d5a515ed95388de27046f66fce32343a012a04b6c5c1ea0086a5b8a5ef2fdbcdc7d3a2206007c37a69e99914902f3555d2e18d1a6cf5c96f8a199796cf8cc92080a0f9b9ae12d0eebc34fd03fb9281855ca2ed662954b4058e550dab8dbe20377f9fb24c1fee31d81c743db2b373da115e4424ceeb19271c3a6a2e7b1dd3f6ff2ac1f400ca6fe5476718a8b2ed0f45b78ca8a5cf859bb0752697e1dddc724a7ef07bbe3611b7bbf11c67c64850899b28d5e62a9584b503ba28cf33cfa8874dd35788d86a7b5659eddf67cd0ccd3bde729a3efc4b573ec17592e96bfd10c6a195b69e91d2bde5c197461c276c3cf21bad5da62a5d8aaa273a195e80d988ee1c508d305e7786994d09a0d6f10a690370a47fecbec2bc2308b937ec666dffbab76f4b4a21638dff8906cd361530eb3e32bf095ed4b499669d9263d4cb552bef4538ae26a790f4f0e58c3b834172df96f4aa25fe00fb0370165c6c8c372c6a722b67a42fe0292ddbe47463144a9e9be3b2670c6f11c13fff6882282a750ee4fb3b6c20c05a9e1695f56753167e3cbca97b479c427ad04a976fac8e21c748d94e3240ffef0eabcdbcba898bbd08884c5eb5d7f9d33e4111fe05f9dbf25c8b6bf8b6dee3f33e660943e6511eb519a34d86910fa76d274c3b6f49f4d7eebbd963ed1639503390fd37a087b394603235aac6f4a8b2e80320522a1e4c5c6815102b8125ed235de64486c25394b7953d009d0992762d9e7db78e8052f1e41c4788c938ea9aec84045ca9279909f02f268bf15250abc89429dc59652ef33ef13b60eb140c10f8e666d9dfdcd43d6ca64c9a48a272d79f6fc4e71be5125eaadded1818b28ea8431cf83a4cf7433533a6de195c59e587ca3f56877b62a0050dcbdea069bc462591a5d9e19c2e5946af0c8e5e87cd44011b999bce703cf11d3fd2e0e41d83d77f23eb17f2e248f144b2aa24089d61dafc8888d48866cb92af89014bc3cd95661516c389161b5f1a7dda17dbb1db0fcd05d6512cf2fd2bb1a2636234ada25bd1374a9fcef7d6c67dda194aac9d4a1b906d4a05b985e289d78481cbb43dfacfe4be66c01058e900f08001b9b5697bd283ed25f090d2fbc162c39e53b935494b3038a370145d6a69afd86231c6371d2eecd7504b5468204df7df593a14d3e3fa4eb8dbd6be9e18ba98163dcb2776e9e0bbb1e55a038c4229f586aed938215ad264c8f6fb35fd18188adfe99ebd2bf433bf019dd85573368a2470aa1fb84816b8113a024d1a4864b059c652a4658d65c529bbd529e6f9bdb44aa4fcefdfa28184ea90874734277d4dada764866ce6225d4fc63f22073cc39319890521cb015846c085f48e9354d9ba6cfb30758c1be393be1110cd67cbc82501861e3410f537f323d54f0a0afd26f96a39b029ba1d2ee5f3f90439fa42b8f025e9cb83c9c0fe27d38f5ad83b716ceea3053be5726aa67cfa06d9475a2cd08fa0cd07808352e999e36cd663d6c66ad406c9040b878e36bf5d1303b3efc42931139d379e7212034ce2cc327d3a6ea923a73eb3e2b11d369e0f9da8f65173cc36b9644add703b8d2642a6d47e962c794d4cc8274a840cc147f2191b41e9c85ca11810c6bb3c4dfb1e03fdeb101996d8cd79e182e47d3ea225199a2eee62eb3b90c75db71faeb283ca3eaedeac7fba5b08b858e05dfbecb20ce5bc02be0709baf18d8815bd10a8909e4c7a41ef3246b81921deb2dbcde2e589d493b89edacfd471c06beb86a8153c5a6606adb48cd145f1e37fffa3217fe729f1ae77445df445367a01979d8197b6e2b6adb6713d6a1cb077e20a825f89da00cf5efa364b7230d8fc7415d5eab427aecca33316d4072dafbe6b62f261dd0a1f6c7161b51e7f8d784092d06dabdd6be343706281e51d59a8951f114391978e2db3ab5797a47f89fa39a0e05f6863b81f82ce80cf8bd6011e321c875a432fa17dcfe884bf39705434add274cd8969a1c8c7ceaa6e0d2460299a26ee01287b55335 +erase 0,1 +erase 0,3 +erase 1,2 +erase 1,3 +case 4 2 16 zero +parity 0 00000000000000000000000000000000 +parity 1 00000000000000000000000000000000 +erase 0,1 +erase 0,4 +erase 2,3 +erase 2,4 +case 16 4 16 ff +parity 0 ffffffffffffffffffffffffffffffff +parity 1 ffffffffffffffffffffffffffffffff +parity 2 ffffffffffffffffffffffffffffffff +parity 3 ffffffffffffffffffffffffffffffff +erase 0,1,2,3 +erase 0,8,16,17 +erase 8,16,17,18 +erase 12,13,14,15 +case 1 1 1 lcg +parity 0 87 +erase 0 +case 1 1 9 lcg +parity 0 8763c23ca4b05c1d0a +erase 0 +case 2 1 5 lcg +parity 0 cd8575a93e +erase 0 +erase 1 +case 255 1 1 lcg +parity 0 51 +erase 0 +erase 127 +erase 254 +case 255 1 3 lcg +parity 0 512073 +erase 0 +erase 127 +erase 254 +case 1 255 1 lcg +parity 0 0d +parity 1 0d +parity 2 0d +parity 3 0d +parity 4 0d +parity 5 0d +parity 6 0d +parity 7 0d +parity 8 0d +parity 9 0d +parity 10 0d +parity 11 0d +parity 12 0d +parity 13 0d +parity 14 0d +parity 15 0d +parity 16 0d +parity 17 0d +parity 18 0d +parity 19 0d +parity 20 0d +parity 21 0d +parity 22 0d +parity 23 0d +parity 24 0d +parity 25 0d +parity 26 0d +parity 27 0d +parity 28 0d +parity 29 0d +parity 30 0d +parity 31 0d +parity 32 0d +parity 33 0d +parity 34 0d +parity 35 0d +parity 36 0d +parity 37 0d +parity 38 0d +parity 39 0d +parity 40 0d +parity 41 0d +parity 42 0d +parity 43 0d +parity 44 0d +parity 45 0d +parity 46 0d +parity 47 0d +parity 48 0d +parity 49 0d +parity 50 0d +parity 51 0d +parity 52 0d +parity 53 0d +parity 54 0d +parity 55 0d +parity 56 0d +parity 57 0d +parity 58 0d +parity 59 0d +parity 60 0d +parity 61 0d +parity 62 0d +parity 63 0d +parity 64 0d +parity 65 0d +parity 66 0d +parity 67 0d +parity 68 0d +parity 69 0d +parity 70 0d +parity 71 0d +parity 72 0d +parity 73 0d +parity 74 0d +parity 75 0d +parity 76 0d +parity 77 0d +parity 78 0d +parity 79 0d +parity 80 0d +parity 81 0d +parity 82 0d +parity 83 0d +parity 84 0d +parity 85 0d +parity 86 0d +parity 87 0d +parity 88 0d +parity 89 0d +parity 90 0d +parity 91 0d +parity 92 0d +parity 93 0d +parity 94 0d +parity 95 0d +parity 96 0d +parity 97 0d +parity 98 0d +parity 99 0d +parity 100 0d +parity 101 0d +parity 102 0d +parity 103 0d +parity 104 0d +parity 105 0d +parity 106 0d +parity 107 0d +parity 108 0d +parity 109 0d +parity 110 0d +parity 111 0d +parity 112 0d +parity 113 0d +parity 114 0d +parity 115 0d +parity 116 0d +parity 117 0d +parity 118 0d +parity 119 0d +parity 120 0d +parity 121 0d +parity 122 0d +parity 123 0d +parity 124 0d +parity 125 0d +parity 126 0d +parity 127 0d +parity 128 0d +parity 129 0d +parity 130 0d +parity 131 0d +parity 132 0d +parity 133 0d +parity 134 0d +parity 135 0d +parity 136 0d +parity 137 0d +parity 138 0d +parity 139 0d +parity 140 0d +parity 141 0d +parity 142 0d +parity 143 0d +parity 144 0d +parity 145 0d +parity 146 0d +parity 147 0d +parity 148 0d +parity 149 0d +parity 150 0d +parity 151 0d +parity 152 0d +parity 153 0d +parity 154 0d +parity 155 0d +parity 156 0d +parity 157 0d +parity 158 0d +parity 159 0d +parity 160 0d +parity 161 0d +parity 162 0d +parity 163 0d +parity 164 0d +parity 165 0d +parity 166 0d +parity 167 0d +parity 168 0d +parity 169 0d +parity 170 0d +parity 171 0d +parity 172 0d +parity 173 0d +parity 174 0d +parity 175 0d +parity 176 0d +parity 177 0d +parity 178 0d +parity 179 0d +parity 180 0d +parity 181 0d +parity 182 0d +parity 183 0d +parity 184 0d +parity 185 0d +parity 186 0d +parity 187 0d +parity 188 0d +parity 189 0d +parity 190 0d +parity 191 0d +parity 192 0d +parity 193 0d +parity 194 0d +parity 195 0d +parity 196 0d +parity 197 0d +parity 198 0d +parity 199 0d +parity 200 0d +parity 201 0d +parity 202 0d +parity 203 0d +parity 204 0d +parity 205 0d +parity 206 0d +parity 207 0d +parity 208 0d +parity 209 0d +parity 210 0d +parity 211 0d +parity 212 0d +parity 213 0d +parity 214 0d +parity 215 0d +parity 216 0d +parity 217 0d +parity 218 0d +parity 219 0d +parity 220 0d +parity 221 0d +parity 222 0d +parity 223 0d +parity 224 0d +parity 225 0d +parity 226 0d +parity 227 0d +parity 228 0d +parity 229 0d +parity 230 0d +parity 231 0d +parity 232 0d +parity 233 0d +parity 234 0d +parity 235 0d +parity 236 0d +parity 237 0d +parity 238 0d +parity 239 0d +parity 240 0d +parity 241 0d +parity 242 0d +parity 243 0d +parity 244 0d +parity 245 0d +parity 246 0d +parity 247 0d +parity 248 0d +parity 249 0d +parity 250 0d +parity 251 0d +parity 252 0d +parity 253 0d +parity 254 0d +erase 0 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 1 255 4 lcg +parity 0 0d2f7919 +parity 1 0d2f7919 +parity 2 0d2f7919 +parity 3 0d2f7919 +parity 4 0d2f7919 +parity 5 0d2f7919 +parity 6 0d2f7919 +parity 7 0d2f7919 +parity 8 0d2f7919 +parity 9 0d2f7919 +parity 10 0d2f7919 +parity 11 0d2f7919 +parity 12 0d2f7919 +parity 13 0d2f7919 +parity 14 0d2f7919 +parity 15 0d2f7919 +parity 16 0d2f7919 +parity 17 0d2f7919 +parity 18 0d2f7919 +parity 19 0d2f7919 +parity 20 0d2f7919 +parity 21 0d2f7919 +parity 22 0d2f7919 +parity 23 0d2f7919 +parity 24 0d2f7919 +parity 25 0d2f7919 +parity 26 0d2f7919 +parity 27 0d2f7919 +parity 28 0d2f7919 +parity 29 0d2f7919 +parity 30 0d2f7919 +parity 31 0d2f7919 +parity 32 0d2f7919 +parity 33 0d2f7919 +parity 34 0d2f7919 +parity 35 0d2f7919 +parity 36 0d2f7919 +parity 37 0d2f7919 +parity 38 0d2f7919 +parity 39 0d2f7919 +parity 40 0d2f7919 +parity 41 0d2f7919 +parity 42 0d2f7919 +parity 43 0d2f7919 +parity 44 0d2f7919 +parity 45 0d2f7919 +parity 46 0d2f7919 +parity 47 0d2f7919 +parity 48 0d2f7919 +parity 49 0d2f7919 +parity 50 0d2f7919 +parity 51 0d2f7919 +parity 52 0d2f7919 +parity 53 0d2f7919 +parity 54 0d2f7919 +parity 55 0d2f7919 +parity 56 0d2f7919 +parity 57 0d2f7919 +parity 58 0d2f7919 +parity 59 0d2f7919 +parity 60 0d2f7919 +parity 61 0d2f7919 +parity 62 0d2f7919 +parity 63 0d2f7919 +parity 64 0d2f7919 +parity 65 0d2f7919 +parity 66 0d2f7919 +parity 67 0d2f7919 +parity 68 0d2f7919 +parity 69 0d2f7919 +parity 70 0d2f7919 +parity 71 0d2f7919 +parity 72 0d2f7919 +parity 73 0d2f7919 +parity 74 0d2f7919 +parity 75 0d2f7919 +parity 76 0d2f7919 +parity 77 0d2f7919 +parity 78 0d2f7919 +parity 79 0d2f7919 +parity 80 0d2f7919 +parity 81 0d2f7919 +parity 82 0d2f7919 +parity 83 0d2f7919 +parity 84 0d2f7919 +parity 85 0d2f7919 +parity 86 0d2f7919 +parity 87 0d2f7919 +parity 88 0d2f7919 +parity 89 0d2f7919 +parity 90 0d2f7919 +parity 91 0d2f7919 +parity 92 0d2f7919 +parity 93 0d2f7919 +parity 94 0d2f7919 +parity 95 0d2f7919 +parity 96 0d2f7919 +parity 97 0d2f7919 +parity 98 0d2f7919 +parity 99 0d2f7919 +parity 100 0d2f7919 +parity 101 0d2f7919 +parity 102 0d2f7919 +parity 103 0d2f7919 +parity 104 0d2f7919 +parity 105 0d2f7919 +parity 106 0d2f7919 +parity 107 0d2f7919 +parity 108 0d2f7919 +parity 109 0d2f7919 +parity 110 0d2f7919 +parity 111 0d2f7919 +parity 112 0d2f7919 +parity 113 0d2f7919 +parity 114 0d2f7919 +parity 115 0d2f7919 +parity 116 0d2f7919 +parity 117 0d2f7919 +parity 118 0d2f7919 +parity 119 0d2f7919 +parity 120 0d2f7919 +parity 121 0d2f7919 +parity 122 0d2f7919 +parity 123 0d2f7919 +parity 124 0d2f7919 +parity 125 0d2f7919 +parity 126 0d2f7919 +parity 127 0d2f7919 +parity 128 0d2f7919 +parity 129 0d2f7919 +parity 130 0d2f7919 +parity 131 0d2f7919 +parity 132 0d2f7919 +parity 133 0d2f7919 +parity 134 0d2f7919 +parity 135 0d2f7919 +parity 136 0d2f7919 +parity 137 0d2f7919 +parity 138 0d2f7919 +parity 139 0d2f7919 +parity 140 0d2f7919 +parity 141 0d2f7919 +parity 142 0d2f7919 +parity 143 0d2f7919 +parity 144 0d2f7919 +parity 145 0d2f7919 +parity 146 0d2f7919 +parity 147 0d2f7919 +parity 148 0d2f7919 +parity 149 0d2f7919 +parity 150 0d2f7919 +parity 151 0d2f7919 +parity 152 0d2f7919 +parity 153 0d2f7919 +parity 154 0d2f7919 +parity 155 0d2f7919 +parity 156 0d2f7919 +parity 157 0d2f7919 +parity 158 0d2f7919 +parity 159 0d2f7919 +parity 160 0d2f7919 +parity 161 0d2f7919 +parity 162 0d2f7919 +parity 163 0d2f7919 +parity 164 0d2f7919 +parity 165 0d2f7919 +parity 166 0d2f7919 +parity 167 0d2f7919 +parity 168 0d2f7919 +parity 169 0d2f7919 +parity 170 0d2f7919 +parity 171 0d2f7919 +parity 172 0d2f7919 +parity 173 0d2f7919 +parity 174 0d2f7919 +parity 175 0d2f7919 +parity 176 0d2f7919 +parity 177 0d2f7919 +parity 178 0d2f7919 +parity 179 0d2f7919 +parity 180 0d2f7919 +parity 181 0d2f7919 +parity 182 0d2f7919 +parity 183 0d2f7919 +parity 184 0d2f7919 +parity 185 0d2f7919 +parity 186 0d2f7919 +parity 187 0d2f7919 +parity 188 0d2f7919 +parity 189 0d2f7919 +parity 190 0d2f7919 +parity 191 0d2f7919 +parity 192 0d2f7919 +parity 193 0d2f7919 +parity 194 0d2f7919 +parity 195 0d2f7919 +parity 196 0d2f7919 +parity 197 0d2f7919 +parity 198 0d2f7919 +parity 199 0d2f7919 +parity 200 0d2f7919 +parity 201 0d2f7919 +parity 202 0d2f7919 +parity 203 0d2f7919 +parity 204 0d2f7919 +parity 205 0d2f7919 +parity 206 0d2f7919 +parity 207 0d2f7919 +parity 208 0d2f7919 +parity 209 0d2f7919 +parity 210 0d2f7919 +parity 211 0d2f7919 +parity 212 0d2f7919 +parity 213 0d2f7919 +parity 214 0d2f7919 +parity 215 0d2f7919 +parity 216 0d2f7919 +parity 217 0d2f7919 +parity 218 0d2f7919 +parity 219 0d2f7919 +parity 220 0d2f7919 +parity 221 0d2f7919 +parity 222 0d2f7919 +parity 223 0d2f7919 +parity 224 0d2f7919 +parity 225 0d2f7919 +parity 226 0d2f7919 +parity 227 0d2f7919 +parity 228 0d2f7919 +parity 229 0d2f7919 +parity 230 0d2f7919 +parity 231 0d2f7919 +parity 232 0d2f7919 +parity 233 0d2f7919 +parity 234 0d2f7919 +parity 235 0d2f7919 +parity 236 0d2f7919 +parity 237 0d2f7919 +parity 238 0d2f7919 +parity 239 0d2f7919 +parity 240 0d2f7919 +parity 241 0d2f7919 +parity 242 0d2f7919 +parity 243 0d2f7919 +parity 244 0d2f7919 +parity 245 0d2f7919 +parity 246 0d2f7919 +parity 247 0d2f7919 +parity 248 0d2f7919 +parity 249 0d2f7919 +parity 250 0d2f7919 +parity 251 0d2f7919 +parity 252 0d2f7919 +parity 253 0d2f7919 +parity 254 0d2f7919 +erase 0 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 2 254 2 lcg +parity 0 1de3 +parity 1 061b +parity 2 47c9 +parity 3 5c31 +parity 4 7124 +parity 5 6adc +parity 6 f39d +parity 7 e865 +parity 8 c570 +parity 9 de88 +parity 10 9f5a +parity 11 84a2 +parity 12 a9b7 +parity 13 b24f +parity 14 8635 +parity 15 9dcd +parity 16 b0d8 +parity 17 ab20 +parity 18 eaf2 +parity 19 f10a +parity 20 dc1f +parity 21 c7e7 +parity 22 5ea6 +parity 23 455e +parity 24 684b +parity 25 73b3 +parity 26 3261 +parity 27 2999 +parity 28 048c +parity 29 1f74 +parity 30 6c78 +parity 31 7780 +parity 32 5a95 +parity 33 416d +parity 34 00bf +parity 35 1b47 +parity 36 3652 +parity 37 2daa +parity 38 b4eb +parity 39 af13 +parity 40 8206 +parity 41 99fe +parity 42 d82c +parity 43 c3d4 +parity 44 eec1 +parity 45 f539 +parity 46 c143 +parity 47 dabb +parity 48 f7ae +parity 49 ec56 +parity 50 ad84 +parity 51 b67c +parity 52 9b69 +parity 53 8091 +parity 54 19d0 +parity 55 0228 +parity 56 2f3d +parity 57 34c5 +parity 58 7517 +parity 59 6eef +parity 60 43fa +parity 61 5802 +parity 62 a5e2 +parity 63 be1a +parity 64 930f +parity 65 88f7 +parity 66 c925 +parity 67 d2dd +parity 68 ffc8 +parity 69 e430 +parity 70 7d71 +parity 71 6689 +parity 72 4b9c +parity 73 5064 +parity 74 11b6 +parity 75 0a4e +parity 76 275b +parity 77 3ca3 +parity 78 08d9 +parity 79 1321 +parity 80 3e34 +parity 81 25cc +parity 82 641e +parity 83 7fe6 +parity 84 52f3 +parity 85 490b +parity 86 d04a +parity 87 cbb2 +parity 88 e6a7 +parity 89 fd5f +parity 90 bc8d +parity 91 a775 +parity 92 8a60 +parity 93 9198 +parity 94 e294 +parity 95 f96c +parity 96 d479 +parity 97 cf81 +parity 98 8e53 +parity 99 95ab +parity 100 b8be +parity 101 a346 +parity 102 3a07 +parity 103 21ff +parity 104 0cea +parity 105 1712 +parity 106 56c0 +parity 107 4d38 +parity 108 602d +parity 109 7bd5 +parity 110 4faf +parity 111 5457 +parity 112 7942 +parity 113 62ba +parity 114 2368 +parity 115 3890 +parity 116 1585 +parity 117 0e7d +parity 118 973c +parity 119 8cc4 +parity 120 a1d1 +parity 121 ba29 +parity 122 fbfb +parity 123 e003 +parity 124 cd16 +parity 125 d6ee +parity 126 2acb +parity 127 3133 +parity 128 1c26 +parity 129 07de +parity 130 460c +parity 131 5df4 +parity 132 70e1 +parity 133 6b19 +parity 134 f258 +parity 135 e9a0 +parity 136 c4b5 +parity 137 df4d +parity 138 9e9f +parity 139 8567 +parity 140 a872 +parity 141 b38a +parity 142 87f0 +parity 143 9c08 +parity 144 b11d +parity 145 aae5 +parity 146 eb37 +parity 147 f0cf +parity 148 ddda +parity 149 c622 +parity 150 5f63 +parity 151 449b +parity 152 698e +parity 153 7276 +parity 154 33a4 +parity 155 285c +parity 156 0549 +parity 157 1eb1 +parity 158 6dbd +parity 159 7645 +parity 160 5b50 +parity 161 40a8 +parity 162 017a +parity 163 1a82 +parity 164 3797 +parity 165 2c6f +parity 166 b52e +parity 167 aed6 +parity 168 83c3 +parity 169 983b +parity 170 d9e9 +parity 171 c211 +parity 172 ef04 +parity 173 f4fc +parity 174 c086 +parity 175 db7e +parity 176 f66b +parity 177 ed93 +parity 178 ac41 +parity 179 b7b9 +parity 180 9aac +parity 181 8154 +parity 182 1815 +parity 183 03ed +parity 184 2ef8 +parity 185 3500 +parity 186 74d2 +parity 187 6f2a +parity 188 423f +parity 189 59c7 +parity 190 a427 +parity 191 bfdf +parity 192 92ca +parity 193 8932 +parity 194 c8e0 +parity 195 d318 +parity 196 fe0d +parity 197 e5f5 +parity 198 7cb4 +parity 199 674c +parity 200 4a59 +parity 201 51a1 +parity 202 1073 +parity 203 0b8b +parity 204 269e +parity 205 3d66 +parity 206 091c +parity 207 12e4 +parity 208 3ff1 +parity 209 2409 +parity 210 65db +parity 211 7e23 +parity 212 5336 +parity 213 48ce +parity 214 d18f +parity 215 ca77 +parity 216 e762 +parity 217 fc9a +parity 218 bd48 +parity 219 a6b0 +parity 220 8ba5 +parity 221 905d +parity 222 e351 +parity 223 f8a9 +parity 224 d5bc +parity 225 ce44 +parity 226 8f96 +parity 227 946e +parity 228 b97b +parity 229 a283 +parity 230 3bc2 +parity 231 203a +parity 232 0d2f +parity 233 16d7 +parity 234 5705 +parity 235 4cfd +parity 236 61e8 +parity 237 7a10 +parity 238 4e6a +parity 239 5592 +parity 240 7887 +parity 241 637f +parity 242 22ad +parity 243 3955 +parity 244 1440 +parity 245 0fb8 +parity 246 96f9 +parity 247 8d01 +parity 248 a014 +parity 249 bbec +parity 250 fa3e +parity 251 e1c6 +parity 252 ccd3 +parity 253 d72b +erase 0,1 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253 +erase 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 128 128 1 lcg +parity 0 3f +parity 1 67 +parity 2 58 +parity 3 ac +parity 4 d6 +parity 5 45 +parity 6 7a +parity 7 81 +parity 8 b0 +parity 9 5d +parity 10 5a +parity 11 e7 +parity 12 35 +parity 13 da +parity 14 5d +parity 15 31 +parity 16 03 +parity 17 35 +parity 18 5c +parity 19 a1 +parity 20 00 +parity 21 4a +parity 22 70 +parity 23 5b +parity 24 13 +parity 25 b4 +parity 26 9c +parity 27 dc +parity 28 f6 +parity 29 9b +parity 30 f1 +parity 31 38 +parity 32 20 +parity 33 59 +parity 34 f9 +parity 35 84 +parity 36 dc +parity 37 4d +parity 38 a6 +parity 39 68 +parity 40 e8 +parity 41 2c +parity 42 8b +parity 43 93 +parity 44 e2 +parity 45 75 +parity 46 55 +parity 47 d1 +parity 48 43 +parity 49 0c +parity 50 9f +parity 51 c7 +parity 52 c0 +parity 53 cc +parity 54 89 +parity 55 77 +parity 56 68 +parity 57 d4 +parity 58 56 +parity 59 a5 +parity 60 b3 +parity 61 c1 +parity 62 12 +parity 63 e4 +parity 64 0d +parity 65 a8 +parity 66 88 +parity 67 9c +parity 68 f9 +parity 69 b9 +parity 70 27 +parity 71 b9 +parity 72 c3 +parity 73 3a +parity 74 84 +parity 75 f9 +parity 76 d9 +parity 77 0a +parity 78 8c +parity 79 79 +parity 80 c7 +parity 81 f4 +parity 82 46 +parity 83 ff +parity 84 ce +parity 85 e7 +parity 86 1c +parity 87 72 +parity 88 07 +parity 89 c2 +parity 90 d1 +parity 91 89 +parity 92 7c +parity 93 ab +parity 94 8d +parity 95 80 +parity 96 3c +parity 97 aa +parity 98 b7 +parity 99 ee +parity 100 5c +parity 101 14 +parity 102 98 +parity 103 05 +parity 104 73 +parity 105 07 +parity 106 9a +parity 107 01 +parity 108 0d +parity 109 f3 +parity 110 9f +parity 111 da +parity 112 3e +parity 113 d9 +parity 114 67 +parity 115 d9 +parity 116 69 +parity 117 7f +parity 118 e2 +parity 119 76 +parity 120 71 +parity 121 a5 +parity 122 7a +parity 123 8d +parity 124 cc +parity 125 5b +parity 126 2f +parity 127 ba +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 +erase 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191 +erase 64,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 128 128 4 lcg +parity 0 3fd0cd4e +parity 1 67acce3d +parity 2 58c5aec7 +parity 3 ac1f402d +parity 4 d680e2ae +parity 5 454d646d +parity 6 7a15f048 +parity 7 81db42c0 +parity 8 b041c75a +parity 9 5dba99ff +parity 10 5a4d1ce6 +parity 11 e7ca523e +parity 12 35ee7d2a +parity 13 da241b58 +parity 14 5de4a50e +parity 15 31caf2ad +parity 16 0369da6c +parity 17 35997fc0 +parity 18 5c0294e7 +parity 19 a1971585 +parity 20 002a2321 +parity 21 4a974d65 +parity 22 70d4ab7a +parity 23 5b1a8ec6 +parity 24 1395171f +parity 25 b4a002a7 +parity 26 9cf90bbe +parity 27 dcdf3f10 +parity 28 f6cf46fa +parity 29 9b9a0b6a +parity 30 f11027db +parity 31 3833f7af +parity 32 20828a5b +parity 33 596d355f +parity 34 f9f209d1 +parity 35 84b632c0 +parity 36 dce8d468 +parity 37 4d726181 +parity 38 a6835e72 +parity 39 68910de5 +parity 40 e80d1978 +parity 41 2c63092a +parity 42 8bc522ee +parity 43 930f5c98 +parity 44 e221177a +parity 45 75147f7c +parity 46 558b8934 +parity 47 d1a202c7 +parity 48 4311487e +parity 49 0c846b66 +parity 50 9f5d19d2 +parity 51 c761f90c +parity 52 c07a9e70 +parity 53 ccfcf9ab +parity 54 89c8a8fc +parity 55 7779d864 +parity 56 685c954e +parity 57 d448cdf2 +parity 58 56421c35 +parity 59 a59f2518 +parity 60 b3e1bb66 +parity 61 c157d8f2 +parity 62 122452aa +parity 63 e4037089 +parity 64 0dd99b70 +parity 65 a8a9472c +parity 66 8834af03 +parity 67 9c61990b +parity 68 f9545ded +parity 69 b90d3aa6 +parity 70 27bfc30d +parity 71 b92302fc +parity 72 c3cba2dd +parity 73 3acc4530 +parity 74 84d610c8 +parity 75 f9fac232 +parity 76 d97bc887 +parity 77 0a27b288 +parity 78 8c8d80a1 +parity 79 79cd4482 +parity 80 c72e0490 +parity 81 f42d6c27 +parity 82 46173b48 +parity 83 ff95a4c6 +parity 84 ceb1fbfa +parity 85 e7e8039e +parity 86 1cc326f0 +parity 87 725f2675 +parity 88 07c0d0bf +parity 89 c2b987f5 +parity 90 d1c73bfb +parity 91 89e3d746 +parity 92 7c66fdde +parity 93 ab32e989 +parity 94 8dbc7edf +parity 95 8093750b +parity 96 3c645f96 +parity 97 aa542e61 +parity 98 b77c83f1 +parity 99 eeb2e7f6 +parity 100 5c0c86c6 +parity 101 14d3453b +parity 102 98ce7f22 +parity 103 05d8e6a8 +parity 104 73717e05 +parity 105 070cb0d5 +parity 106 9a78d5aa +parity 107 01c97566 +parity 108 0d924c1d +parity 109 f34be814 +parity 110 9f02b477 +parity 111 da85441b +parity 112 3e111d43 +parity 113 d9b57355 +parity 114 673936e5 +parity 115 d90c4645 +parity 116 691c9e27 +parity 117 7fd06f61 +parity 118 e21706a9 +parity 119 76758e4c +parity 120 719d6a94 +parity 121 a5950a27 +parity 122 7a0b1ba9 +parity 123 8d76695b +parity 124 cca2e422 +parity 125 5b805cd2 +parity 126 2fe56bae +parity 127 baf031ad +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127 +erase 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191 +erase 64,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +case 200 56 2 lcg +parity 0 946f +parity 1 3f89 +parity 2 2f17 +parity 3 2700 +parity 4 5d92 +parity 5 ba5f +parity 6 95aa +parity 7 3395 +parity 8 2109 +parity 9 a097 +parity 10 2313 +parity 11 55e1 +parity 12 9e65 +parity 13 53ce +parity 14 ce44 +parity 15 c58b +parity 16 f2bc +parity 17 64e3 +parity 18 5677 +parity 19 ba5f +parity 20 d1c9 +parity 21 e3e9 +parity 22 7b7b +parity 23 9714 +parity 24 215d +parity 25 f4c8 +parity 26 4eba +parity 27 4e9d +parity 28 2af2 +parity 29 59bb +parity 30 9674 +parity 31 8e33 +parity 32 334b +parity 33 a29b +parity 34 21eb +parity 35 6ba9 +parity 36 7b98 +parity 37 1ada +parity 38 67f0 +parity 39 70b9 +parity 40 19a7 +parity 41 ac6b +parity 42 04b4 +parity 43 1bdb +parity 44 fd80 +parity 45 91c6 +parity 46 b69e +parity 47 da95 +parity 48 1524 +parity 49 4232 +parity 50 8464 +parity 51 f86e +parity 52 2a39 +parity 53 6429 +parity 54 4bcf +parity 55 ea24 +erase 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55 +erase 0,7,14,21,28,35,42,50,57,64,71,78,85,92,100,107,114,121,128,135,142,150,157,164,171,178,185,192,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227 +erase 100,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254 +erase 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199 +case 250 6 5 lcg +parity 0 cd4bb59e20 +parity 1 15f9bc891f +parity 2 8fb793679f +parity 3 fbf644d8e5 +parity 4 009c7d60c8 +parity 5 4cd67a51d4 +erase 0,1,2,3,4,5 +erase 0,83,166,250,251,252 +erase 125,250,251,252,253,254 +erase 244,245,246,247,248,249 diff --git a/packages/cultnet-rs/tests/media_fec.rs b/packages/cultnet-rs/tests/media_fec.rs index f28dda77..1dafae6a 100644 --- a/packages/cultnet-rs/tests/media_fec.rs +++ b/packages/cultnet-rs/tests/media_fec.rs @@ -1143,9 +1143,8 @@ fn a_budget_at_the_media_record_ceiling_admits_large_shards_end_to_end() { } // --------------------------------------------------------------------------- -// Known-answer generator: reed-solomon-erasure 6.0.0 is the reference that -// produced `fixtures/media_fec_rs_gf256_v1.kat`. It runs once, before the -// crate is replaced by the owned construction, and is deleted with the crate. +// Known answers from the fixture: every standard geometry and the policy's +// edges, byte for byte. The fixture's header states how its data is made. // --------------------------------------------------------------------------- /// The data of a fixture case, as the fixture's header defines it. @@ -1160,6 +1159,129 @@ fn kat_data(k: usize, m: usize, shard_bytes: usize, fill: &str) -> Vec> .collect() } +struct KatCase { + k: usize, + m: usize, + shard_bytes: usize, + fill: String, + parity: Vec>, + erasures: Vec>, +} + +impl KatCase { + fn name(&self) -> String { + format!("k={} m={} {} bytes {}", self.k, self.m, self.shard_bytes, self.fill) + } + + /// The case's data as an audio block: the codec path that takes any `(k, m)`. + fn packets(&self) -> Vec { + let mut packets = audio_run(self.k as u64, self.shard_bytes); + for (packet, data) in packets.iter_mut().zip(kat_data(self.k, self.m, self.shard_bytes, &self.fill)) { + packet.payload = data; + } + packets + } + + fn policy(&self) -> MediaFecPolicy { + MediaFecPolicy { + audio_data_shards: self.k as u16, + audio_parity_shards: self.m as u16, + max_wire_bytes: GAMECULT_MEDIA_MAX_WIRE_BYTES, + ..STANDARD + } + } +} + +fn unhex(text: &str) -> Vec { + (0..text.len()) + .step_by(2) + .map(|at| u8::from_str_radix(&text[at..at + 2], 16).expect("fixture hex")) + .collect() +} + +fn kat_cases() -> Vec { + let mut cases: Vec = Vec::new(); + for line in include_str!("fixtures/media_fec_rs_gf256_v1.kat").lines() { + let fields: Vec<&str> = line.split_whitespace().collect(); + match fields.as_slice() { + [] => {} + [comment, ..] if comment.starts_with('#') => {} + ["case", k, m, shard_bytes, fill] => cases.push(KatCase { + k: k.parse().unwrap(), + m: m.parse().unwrap(), + shard_bytes: shard_bytes.parse().unwrap(), + fill: fill.to_string(), + parity: Vec::new(), + erasures: Vec::new(), + }), + ["parity", index, bytes] => { + let case = cases.last_mut().expect("parity follows a case"); + assert_eq!(index.parse::().unwrap(), case.parity.len(), "{}", case.name()); + case.parity.push(unhex(bytes)); + } + ["erase", slots] => cases + .last_mut() + .expect("erase follows a case") + .erasures + .push(slots.split(',').map(|slot| slot.parse().unwrap()).collect()), + other => panic!("unreadable fixture line {other:?}"), + } + } + cases +} + +/// Parity bytes of every fixture case, byte for byte. If these ever change, +/// `rs-gf256-v1` changed meaning: bump the scheme id, do not edit the fixture. +#[test] +fn every_rs_gf256_v1_known_answer_parity_matches_byte_for_byte() { + let cases = kat_cases(); + assert_eq!(cases.len(), 35, "the fixture's case count"); + for case in &cases { + assert_eq!(case.parity.len(), case.m, "{}", case.name()); + let parity = protect_audio_block(&case.packets(), &case.policy(), provenance()) + .unwrap_or_else(|error| panic!("{}: {error}", case.name())); + let produced: Vec = parity.iter().map(|shard| hex(&shard.payload)).collect(); + let expected: Vec = case.parity.iter().map(|shard| hex(shard)).collect(); + assert_eq!(produced, expected, "{}", case.name()); + } +} + +/// Every fixture erasure pattern, data and parity mixed, recovers exactly +/// the erased data from the fixture's own parity bytes. +#[test] +fn every_rs_gf256_v1_known_erasure_pattern_recovers_the_data() { + for case in kat_cases() { + let packets = case.packets(); + let mut parity = protect_audio_block(&packets, &case.policy(), provenance()).unwrap(); + for (shard, bytes) in parity.iter_mut().zip(&case.parity) { + shard.payload = bytes.clone(); // decode from the fixture, not from the encoder + } + assert!(!case.erasures.is_empty(), "{}", case.name()); + for erased in &case.erasures { + let data: Vec<_> = + (0..case.k).filter(|slot| !erased.contains(slot)).map(|slot| packets[slot].clone()).collect(); + let present: Vec<_> = (0..case.m) + .filter(|slot| !erased.contains(&(case.k + slot))) + .map(|slot| parity[slot].clone()) + .collect(); + let recovered = recover_audio_block(&present, &data) + .unwrap_or_else(|error| panic!("{} erased {erased:?}: {error}", case.name())); + let lost: Vec = erased.iter().copied().filter(|&slot| slot < case.k).collect(); + assert_eq!(recovered.len(), lost.len(), "{} erased {erased:?}", case.name()); + for (packet, slot) in recovered.iter().zip(lost) { + assert_eq!(packet.packet_id, packets[slot].packet_id, "{} erased {erased:?}", case.name()); + assert_eq!(packet.payload, packets[slot].payload, "{} erased {erased:?}", case.name()); + } + } + } +} + +// --------------------------------------------------------------------------- +// Known-answer generator: reed-solomon-erasure 6.0.0 is the reference that +// produced `fixtures/media_fec_rs_gf256_v1.kat`. It runs once, before the +// crate is replaced by the owned construction, and is deleted with the crate. +// --------------------------------------------------------------------------- + /// Erasure patterns of exactly `min(m, k)`-ish shards, each leaving at least /// one parity shard present and at least one data shard missing. fn kat_erasures(k: usize, m: usize) -> Vec> { From d391770abb35fac799541603d233f1b45afffea6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 12:26:40 +0200 Subject: [PATCH 3/4] Media FEC: own the rs-gf256-v1 math, drop reed-solomon-erasure Operator ruling, 2026-09-30: "FEC: Please own the math." reed-solomon-erasure 6.0.0 pulled lru 0.7.8 (RUSTSEC-2026-0253, unsound) and instant (RUSTSEC-2024-0384, unmaintained) into every cultnet-rs consumer. media_fec.rs now implements the construction its docs already state, read against the crate's source rather than its names: GF(2^8) reduced by 0x11D with 2 as generator, the n x k Vandermonde matrix over the evaluation points 0..n-1 with 0^0 = 1, E = V * inverse(top k rows), parity = rows k..k+m of E, and recovery through the inverse of E's rows for the first k present shards, which is the row choice the crate made. About 130 lines: const log/exp tables, a Gauss-Jordan inverse, the encoding rows, and one combine loop. It allocates only the block's shards and three k x k matrices, so the decoder's bound is still set by the wire validators (<= 256 shards of <= 64 KiB). The shard functions no longer return errors that only the crate's own checks could raise; every caller already bounds k, m and the shard length. The generator test is deleted with the crate; the fixture keeps its output and names the commit that ran it. New: a seeded property test over 300 random (k <= 48, m <= 24) geometries, any e <= m erasures recover exactly and m + 1 are refused. Co-Authored-By: Claude Opus 5.5 --- packages/cultnet-rs/Cargo.toml | 1 - packages/cultnet-rs/src/media_fec.rs | 181 ++++++++++++++++++++----- packages/cultnet-rs/tests/media_fec.rs | 128 ++++++++--------- 3 files changed, 199 insertions(+), 111 deletions(-) diff --git a/packages/cultnet-rs/Cargo.toml b/packages/cultnet-rs/Cargo.toml index b1cd8d11..f87af6cb 100644 --- a/packages/cultnet-rs/Cargo.toml +++ b/packages/cultnet-rs/Cargo.toml @@ -19,7 +19,6 @@ cultcache-rs = { path = "../cultcache-rs" } ed25519-dalek = "2.2" hmac = "0.12" rand = "0.9" -reed-solomon-erasure = "6" rmp-serde = "1.3" rmpv = { version = "1.3", features = ["with-serde"] } serde = { version = "1", features = ["derive"] } diff --git a/packages/cultnet-rs/src/media_fec.rs b/packages/cultnet-rs/src/media_fec.rs index 490eaaed..ec1b1230 100644 --- a/packages/cultnet-rs/src/media_fec.rs +++ b/packages/cultnet-rs/src/media_fec.rs @@ -14,11 +14,11 @@ //! the `k + m` shards recover the block (the code is MDS), so any `m` erasures //! are repaired and any more are reported as [`MediaFecError::BeyondRepair`], //! never as wrong bytes. The construction is named on every parity record by -//! [`MEDIA_FEC_SCHEME_RS_GF256_V1`] and backed by the `reed-solomon-erasure` -//! crate. The library is not the contract: the construction below is, and -//! `tests/media_fec.rs` pins the parity bytes of known inputs, so a library that -//! changes the matrix fails there instead of on the wire, and a decoder in -//! another runtime has vectors to meet. +//! [`MEDIA_FEC_SCHEME_RS_GF256_V1`] and implemented in this module, from the +//! text below. The construction is the contract: `tests/media_fec.rs` and +//! `tests/fixtures/media_fec_rs_gf256_v1.kat` pin the parity bytes of known +//! inputs, so a change to the math fails there instead of on the wire, and a +//! decoder in another runtime has vectors to meet. //! //! # The `rs-gf256-v1` construction //! @@ -42,8 +42,10 @@ //! * Any `k` surviving shards recover the block: invert the `k x k` submatrix //! of `E` for the surviving rows and multiply. //! -//! The known-answer vectors in `tests/media_fec.rs` were checked against an -//! independent implementation of exactly this text. +//! The known-answer vectors were produced by `reed-solomon-erasure` 6.0.0's +//! `galois_8` field, which builds exactly this matrix, before this module +//! replaced it; the smaller ones in `tests/media_fec.rs` were also checked +//! against a second, independent implementation of this text. //! //! # What a block is //! @@ -88,8 +90,6 @@ use std::collections::BTreeMap; use std::fmt; -use reed_solomon_erasure::galois_8::ReedSolomon; - use crate::media_stream_contracts::{ GameCultMediaAudioPacketRecord, GameCultMediaAudioParityShardRecord, GameCultMediaVideoAccessUnitRecord, GameCultMediaVideoParityShardRecord, @@ -102,8 +102,8 @@ use crate::media_stream_wire::{ use crate::rudp::RUDP_FIXED_HEADER_BYTES; /// Names the construction on every parity record: systematic Reed-Solomon over -/// GF(2^8), as built by `reed-solomon-erasure`'s `galois_8` field. A different -/// matrix is a different scheme id. +/// GF(2^8), as the module docs state it. A different matrix is a different +/// scheme id. pub const MEDIA_FEC_SCHEME_RS_GF256_V1: &str = "rs-gf256-v1"; /// The largest UDP payload an IPv4 datagram carries on a 1500-byte MTU. @@ -237,40 +237,151 @@ impl fmt::Display for MediaFecError { impl std::error::Error for MediaFecError {} // --------------------------------------------------------------------------- -// The code: equal-length shards in, equal-length shards out. +// The code: rs-gf256-v1 exactly as the module docs state it. Equal-length +// shards in, equal-length shards out; every caller has already bounded a block +// to 1..=256 shards of one validated length, so nothing here allocates more +// than the block's shards and three `k x k` matrices. // --------------------------------------------------------------------------- -fn parity_shards(data: &[Vec], parity_count: usize) -> Result>, MediaFecError> { - let code = ReedSolomon::new(data.len(), parity_count) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - let mut parity = vec![vec![0_u8; data[0].len()]; parity_count]; - code.encode_sep(data, &mut parity) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - Ok(parity) +/// `x^8 + x^4 + x^3 + x^2 + 1`, the polynomial the field is reduced by. +const FIELD_POLYNOMIAL: u16 = 0x11D; + +/// `EXP[i]` is `2^i`, written out over two periods so the sum of two logarithms +/// indexes it without reduction; `LOG` is its inverse on the non-zero +/// elements. `2` generates the multiplicative group of GF(2^8) under +/// [`FIELD_POLYNOMIAL`], so the tables cover the field. +const FIELD_TABLES: ([u8; 510], [u8; 256]) = { + let mut exp = [0_u8; 510]; + let mut log = [0_u8; 256]; + let mut power: u16 = 1; + let mut i = 0; + while i < 255 { + exp[i] = power as u8; + exp[i + 255] = power as u8; + log[power as usize] = i as u8; + power <<= 1; + if power & 0x100 != 0 { + power ^= FIELD_POLYNOMIAL; + } + i += 1; + } + (exp, log) +}; + +fn gf_mul(a: u8, b: u8) -> u8 { + let (exp, log) = &FIELD_TABLES; + if a == 0 || b == 0 { + return 0; + } + exp[usize::from(log[usize::from(a)]) + usize::from(log[usize::from(b)])] +} + +/// `1 / a` for non-zero `a`. +fn gf_reciprocal(a: u8) -> u8 { + let (exp, log) = &FIELD_TABLES; + exp[255 - usize::from(log[usize::from(a)])] +} + +/// Row `r` of the Vandermonde matrix `V`: `r^0, r^1, .., r^(k-1)`, the row +/// number read as a field element, with `0^0 = 1`. +fn vandermonde_row(r: usize, k: usize) -> Vec { + let point = r as u8; + let mut row = Vec::with_capacity(k); + let mut power = 1_u8; + for _ in 0..k { + row.push(power); + power = gf_mul(power, point); + } + row +} + +/// The inverse of a square matrix, by Gauss-Jordan elimination. +fn invert(mut matrix: Vec>) -> Vec> { + let n = matrix.len(); + let mut inverse: Vec> = + (0..n).map(|r| (0..n).map(|c| u8::from(r == c)).collect()).collect(); + for col in 0..n { + let pivot = (col..n) + .find(|&r| matrix[r][col] != 0) + .expect("rs-gf256-v1 only inverts matrices whose evaluation points are distinct"); + matrix.swap(col, pivot); + inverse.swap(col, pivot); + let scale = gf_reciprocal(matrix[col][col]); + for c in 0..n { + matrix[col][c] = gf_mul(scale, matrix[col][c]); + inverse[col][c] = gf_mul(scale, inverse[col][c]); + } + let (pivot_row, pivot_inverse) = (matrix[col].clone(), inverse[col].clone()); + for r in (0..n).filter(|&r| r != col) { + let factor = matrix[r][col]; + for c in 0..n { + matrix[r][c] ^= gf_mul(factor, pivot_row[c]); + inverse[r][c] ^= gf_mul(factor, pivot_inverse[c]); + } + } + } + inverse +} + +/// The given rows of the encoding matrix `E = V * inverse(top)` for a block of +/// `k` data shards, where `top` is the first `k` rows of `V`. +fn encoding_rows(k: usize, rows: impl Iterator) -> Vec> { + let top_inverse = invert((0..k).map(|r| vandermonde_row(r, k)).collect()); + rows.map(|r| { + let v = vandermonde_row(r, k); + (0..k) + .map(|c| (0..k).fold(0, |sum, i| sum ^ gf_mul(v[i], top_inverse[i][c]))) + .collect() + }) + .collect() +} + +/// Byte `j` of the result is the sum over `i` of `coefficients[i] * shards[i][j]`. +fn combine(coefficients: &[u8], shards: &[&[u8]], shard_bytes: usize) -> Vec { + let mut out = vec![0_u8; shard_bytes]; + for (&coefficient, shard) in coefficients.iter().zip(shards) { + for (byte, &input) in out.iter_mut().zip(shard.iter()) { + *byte ^= gf_mul(coefficient, input); + } + } + out +} + +/// The `parity_count` parity shards of `data`: rows `k..k + m` of `E`. +fn parity_shards(data: &[Vec], parity_count: usize) -> Vec> { + let k = data.len(); + let inputs: Vec<&[u8]> = data.iter().map(Vec::as_slice).collect(); + encoding_rows(k, k..k + parity_count) + .iter() + .map(|row| combine(row, &inputs, data[0].len())) + .collect() } /// `shards` holds the block's `k` data slots then `m` parity slots, `None` for -/// each missing. Returns the `k` data shards. +/// each missing. Returns the `k` data shards, recovered from the first `k` +/// present shards through the inverse of their rows of `E`. fn recover_data_shards( data_shards: usize, - mut shards: Vec>>, + shards: Vec>>, ) -> Result>, MediaFecError> { - let parity_shards = shards.len() - data_shards; - let present = shards.iter().flatten().count(); - if present < data_shards { + let present: Vec = (0..shards.len()) + .filter(|&slot| shards[slot].is_some()) + .take(data_shards) + .collect(); + if present.len() < data_shards { return Err(MediaFecError::BeyondRepair { - shards_present: present, + shards_present: present.len(), shards_needed: data_shards, }); } - let code = ReedSolomon::new(data_shards, parity_shards) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - code.reconstruct_data(&mut shards) - .map_err(|error| MediaFecError::invalid(format!("{error:?}")))?; - Ok(shards - .into_iter() - .take(data_shards) - .map(|shard| shard.expect("reconstruct_data fills every data shard")) + let inputs: Vec<&[u8]> = present.iter().map(|&slot| shards[slot].as_deref().unwrap()).collect(); + let shard_bytes = inputs[0].len(); + let decode = invert(encoding_rows(data_shards, present.iter().copied())); + Ok((0..data_shards) + .map(|slot| match &shards[slot] { + Some(shard) => shard.clone(), + None => combine(&decode[slot], &inputs, shard_bytes), + }) .collect()) } @@ -393,7 +504,7 @@ pub fn protect_video_frame( .map(|chunk| padded(&chunk.payload, shard_bytes)) .collect(); let parity_count = policy.video_parity_shards(size); - let parity = parity_shards(&data, usize::from(parity_count))?; + let parity = parity_shards(&data, usize::from(parity_count)); let parity_records: Vec = parity .into_iter() @@ -634,7 +745,7 @@ pub fn protect_audio_block( let data: Vec> = packets.iter().map(|packet| packet.payload.clone()).collect(); let deadline_ticks = packets.iter().map(|packet| packet.deadline_ticks).max().unwrap_or(0); let parity: Vec = - parity_shards(&data, usize::from(policy.audio_parity_shards))? + parity_shards(&data, usize::from(policy.audio_parity_shards)) .into_iter() .enumerate() .map(|(parity_index, payload)| GameCultMediaAudioParityShardRecord { diff --git a/packages/cultnet-rs/tests/media_fec.rs b/packages/cultnet-rs/tests/media_fec.rs index 1dafae6a..c76709d4 100644 --- a/packages/cultnet-rs/tests/media_fec.rs +++ b/packages/cultnet-rs/tests/media_fec.rs @@ -1276,85 +1276,63 @@ fn every_rs_gf256_v1_known_erasure_pattern_recovers_the_data() { } } -// --------------------------------------------------------------------------- -// Known-answer generator: reed-solomon-erasure 6.0.0 is the reference that -// produced `fixtures/media_fec_rs_gf256_v1.kat`. It runs once, before the -// crate is replaced by the owned construction, and is deleted with the crate. -// --------------------------------------------------------------------------- - -/// Erasure patterns of exactly `min(m, k)`-ish shards, each leaving at least -/// one parity shard present and at least one data shard missing. -fn kat_erasures(k: usize, m: usize) -> Vec> { - let mut patterns = Vec::new(); - let d = m.min(k); - patterns.push((0..d).collect()); // data from the front - patterns.push((k - d..k).collect()); // data from the back - let spread = m.div_ceil(2).min(k); - let mut mixed: Vec = (0..spread).map(|s| s * k / spread).collect(); - mixed.extend(k..k + (m - spread)); - patterns.push(mixed); // data spread across the block, parity from the front - let mut one: Vec = vec![k / 2]; - one.extend(k..k + (m - 1)); - patterns.push(one); // one data shard, every parity shard but the last - patterns.sort(); - patterns.dedup(); - patterns -} - +/// Seeded random geometries past the fixture's: any `e <= m` erasures, data +/// and parity mixed, recover the data exactly, and `m + 1` are refused. #[test] -#[ignore = "run once to write the rs-gf256-v1 fixture from the reference crate"] -fn generate_rs_gf256_v1_known_answers() { - use reed_solomon_erasure::galois_8::ReedSolomon; - let mut cases: Vec<(usize, usize, usize, &str)> = Vec::new(); - // Every standard video block geometry, k = 1..=16, m = max(2, ceil(k / 4)). - for k in 1..=16_u16 { - cases.push((usize::from(k), usize::from(STANDARD.video_parity_shards(k)), 37, "lcg")); - } - // Standard audio, 4 + 2, at 1 byte, an odd length and a full datagram. - cases.extend([(4, 2, 1, "lcg"), (4, 2, 37, "lcg"), (4, 2, 1300, "lcg")]); - // The widest standard video block at a full datagram, and a long shard. - cases.extend([(16, 4, 1300, "lcg"), (3, 2, 4000, "lcg")]); - // Degenerate data. - cases.extend([(4, 2, 16, "zero"), (16, 4, 16, "ff")]); - // The policy's edges: one shard each side, and 256 shards split every way. - cases.extend([ - (1, 1, 1, "lcg"), - (1, 1, 9, "lcg"), - (2, 1, 5, "lcg"), - (255, 1, 1, "lcg"), - (255, 1, 3, "lcg"), - (1, 255, 1, "lcg"), - (1, 255, 4, "lcg"), - (2, 254, 2, "lcg"), - (128, 128, 1, "lcg"), - (128, 128, 4, "lcg"), - (200, 56, 2, "lcg"), - (250, 6, 5, "lcg"), - ]); - - let mut out = String::new(); - for (k, m, shard_bytes, fill) in cases { - let data = kat_data(k, m, shard_bytes, fill); - let code = ReedSolomon::new(k, m).unwrap(); - let mut parity = vec![vec![0_u8; shard_bytes]; m]; - code.encode_sep(&data, &mut parity).unwrap(); - out.push_str(&format!("case {k} {m} {shard_bytes} {fill}\n")); - for (p, shard) in parity.iter().enumerate() { - out.push_str(&format!("parity {p} {}\n", hex(shard))); +fn random_erasures_up_to_m_recover_exactly_and_m_plus_one_is_refused() { + let mut state = 0x9e37_79b9_u32; + let mut next = |bound: usize| { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state as usize % bound + }; + for trial in 0..300 { + let k = 1 + next(48); + let m = 1 + next(24); + let shard_bytes = 1 + next(40); + let mut packets = audio_run(k as u64, shard_bytes); + for (index, packet) in packets.iter_mut().enumerate() { + packet.payload = bytes((trial * 1_000 + index) as u32, shard_bytes); } - for pattern in kat_erasures(k, m) { - let mut shards: Vec>> = - data.iter().chain(parity.iter()).cloned().map(Some).collect(); - for &slot in &pattern { - shards[slot] = None; + let policy = MediaFecPolicy { + audio_data_shards: k as u16, + audio_parity_shards: m as u16, + ..STANDARD + }; + let parity = protect_audio_block(&packets, &policy, provenance()).unwrap(); + for erasures in [1 + next(m), m + 1] { + if erasures + 1 > k + m { + continue; // at least one parity shard must arrive to recover with } - code.reconstruct_data(&mut shards).unwrap(); - for (i, shard) in shards.iter().take(k).enumerate() { - assert_eq!(shard.as_ref().unwrap(), &data[i], "k={k} m={m} {pattern:?}"); + // A random erasure set that leaves at least one parity shard. + let mut slots: Vec = (0..k + m).collect(); + let erased = loop { + for at in 0..slots.len() { + let swap = at + next(slots.len() - at); + slots.swap(at, swap); + } + let erased = slots[..erasures].to_vec(); + if (k..k + m).any(|slot| !erased.contains(&slot)) { + break erased; + } + }; + let data: Vec<_> = + (0..k).filter(|slot| !erased.contains(slot)).map(|slot| packets[slot].clone()).collect(); + let present: Vec<_> = + (0..m).filter(|slot| !erased.contains(&(k + slot))).map(|slot| parity[slot].clone()).collect(); + let outcome = recover_audio_block(&present, &data); + let context = format!("trial {trial}: k={k} m={m} {shard_bytes} bytes erased {erased:?}"); + if erasures <= m { + let mut all = data; + all.extend(outcome.unwrap_or_else(|error| panic!("{context}: {error}"))); + all.sort_by_key(|packet| packet.packet_id); + let payloads: Vec<_> = all.iter().map(|packet| &packet.payload).collect(); + let expected: Vec<_> = packets.iter().map(|packet| &packet.payload).collect(); + assert_eq!(payloads, expected, "{context}"); + } else { + assert!(matches!(outcome, Err(MediaFecError::BeyondRepair { .. })), "{context}: {outcome:?}"); } - let list: Vec = pattern.iter().map(usize::to_string).collect(); - out.push_str(&format!("erase {}\n", list.join(","))); } } - println!("BEGIN-KAT\n{out}END-KAT"); } From 7c1a792a28ff031f9086bafe785d2c785bb1a021 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 14:53:53 +0200 Subject: [PATCH 4/4] Media FEC: one table lookup per shard byte in combine Soul measured the owned math at 1.7-2.6x the crate's time at realistic sizes (16+4 x 1200 B encode 64k -> 122k ns), because combine called gf_mul per byte: two zero checks and three lookups. combine now tabulates each coefficient's 256 products once and spends one lookup and one XOR per byte; coefficient 0 is skipped and 1 is a plain XOR. The construction and every wire byte are unchanged: the known-answer fixture passes untouched. tests/media_fec_bench.rs is an ignored, dependency-free timing test over the public surface (4+2 at 160 and 1300 B, 16+4 at 1200 B, 255+1 at 1300 B): cargo test --release --test media_fec_bench -- --ignored --nocapture Co-Authored-By: Claude Opus 5.5 --- packages/cultnet-rs/src/media_fec.rs | 18 +++- packages/cultnet-rs/tests/media_fec_bench.rs | 88 ++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/cultnet-rs/tests/media_fec_bench.rs diff --git a/packages/cultnet-rs/src/media_fec.rs b/packages/cultnet-rs/src/media_fec.rs index ec1b1230..38525db5 100644 --- a/packages/cultnet-rs/src/media_fec.rs +++ b/packages/cultnet-rs/src/media_fec.rs @@ -337,11 +337,25 @@ fn encoding_rows(k: usize, rows: impl Iterator) -> Vec> { } /// Byte `j` of the result is the sum over `i` of `coefficients[i] * shards[i][j]`. +/// +/// The hot loop of both encode and recovery: each coefficient's 256 products +/// are tabulated once, so a shard byte costs one lookup and one XOR. fn combine(coefficients: &[u8], shards: &[&[u8]], shard_bytes: usize) -> Vec { let mut out = vec![0_u8; shard_bytes]; for (&coefficient, shard) in coefficients.iter().zip(shards) { - for (byte, &input) in out.iter_mut().zip(shard.iter()) { - *byte ^= gf_mul(coefficient, input); + match coefficient { + 0 => {} + 1 => { + for (byte, &input) in out.iter_mut().zip(shard.iter()) { + *byte ^= input; + } + } + _ => { + let products: [u8; 256] = std::array::from_fn(|x| gf_mul(coefficient, x as u8)); + for (byte, &input) in out.iter_mut().zip(shard.iter()) { + *byte ^= products[usize::from(input)]; + } + } } } out diff --git a/packages/cultnet-rs/tests/media_fec_bench.rs b/packages/cultnet-rs/tests/media_fec_bench.rs new file mode 100644 index 00000000..40c9de78 --- /dev/null +++ b/packages/cultnet-rs/tests/media_fec_bench.rs @@ -0,0 +1,88 @@ +//! Timings for the media FEC codec, without a bench framework. +//! +//! Ignored by default; run in release, where the numbers mean something: +//! +//! ```text +//! cargo test --release --test media_fec_bench -- --ignored --nocapture +//! ``` +//! +//! Each line is nanoseconds per call through the public surface, so record +//! validation and cloning are included. The geometries are the ones the codec +//! meets: standard audio, the widest standard video block at a datagram, and +//! the worst recovery the policy admits (255 + 1). + +use std::hint::black_box; +use std::time::Instant; + +use cultnet_rs::{ + GAMECULT_MEDIA_MAX_WIRE_BYTES, GameCultMediaAudioPacketRecord, MediaFecPolicy, + MediaWireProvenance, protect_audio_block, recover_audio_block, +}; + +fn provenance() -> MediaWireProvenance<'static> { + MediaWireProvenance { + stored_at: "unix:1700000000000", + runtime_id: "bench", + role: "bench.media", + producer: "bench", + } +} + +fn packets(k: usize, len: usize) -> Vec { + let mut state = 0x2545_f491_u32; + (0..k as u64) + .map(|index| GameCultMediaAudioPacketRecord { + stream_id: "bench".to_string(), + session_id: "bench".to_string(), + packet_id: index, + codec: "opus".to_string(), + pts_ticks: 960 * index as i64, + duration_ticks: 960, + timebase_num: 1, + timebase_den: 48_000, + deadline_ticks: 960 * index as i64 + 5_000, + payload: (0..len) + .map(|_| { + state ^= state << 13; + state ^= state >> 17; + state ^= state << 5; + state as u8 + }) + .collect(), + }) + .collect() +} + +/// Mean nanoseconds per call over at least `min_ms`, after three warm-up calls. +fn time(mut f: impl FnMut(), min_ms: u128) -> f64 { + for _ in 0..3 { + f(); + } + let start = Instant::now(); + let mut calls = 0_u64; + while start.elapsed().as_millis() < min_ms { + f(); + calls += 1; + } + start.elapsed().as_nanos() as f64 / calls as f64 +} + +#[test] +#[ignore = "timing, not a test: run in release with --ignored --nocapture"] +fn media_fec_timings() { + for (k, m, len) in [(4, 2, 160), (4, 2, 1300), (16, 4, 1200), (255, 1, 1300)] { + let policy = MediaFecPolicy { + audio_data_shards: k as u16, + audio_parity_shards: m as u16, + max_wire_bytes: GAMECULT_MEDIA_MAX_WIRE_BYTES, + ..MediaFecPolicy::STANDARD + }; + let block = packets(k, len); + let encode = time(|| drop(black_box(protect_audio_block(black_box(&block), &policy, provenance()))), 300); + let parity = protect_audio_block(&block, &policy, provenance()).unwrap(); + let lost = m.min(k); + let survivors = &block[lost..]; + let decode = time(|| drop(black_box(recover_audio_block(black_box(&parity), survivors))), 300); + println!("k={k:3} m={m:3} len={len:5}: protect {encode:12.0} ns | recover {lost} lost {decode:12.0} ns"); + } +}