diff --git a/crates/pf-bitstream/tests/corpus_replay.rs b/crates/pf-bitstream/tests/corpus_replay.rs new file mode 100644 index 00000000..5a759767 --- /dev/null +++ b/crates/pf-bitstream/tests/corpus_replay.rs @@ -0,0 +1,232 @@ +//! Corpus replay: walk a captured real-host stream through the planners. +//! +//! The M0 capture hook (`PUNKTFUNK_DUMP_VIDEO=` on any desktop client) writes +//! the exact decoder input of a live session — `au-.` plus an `.idx` +//! sidecar carrying `offset len flags complete` per AU. This harness feeds those AUs +//! back through [`pf_bitstream::h264::H264Planner`] / [`pf_bitstream::h265::H265Planner`] +//! and asserts the planner survives a REAL host stream: every AU plans (bar the +//! deliberate skips), no panic, and the warnings are only the ones a clean capture may +//! legitimately produce. +//! +//! Why this exists separately from the vendored conformance vectors: those prove we +//! match the spec's own test streams, and the on-glass sessions prove the whole pipe — +//! but between the two sits "does the planner handle what OUR five host encoder +//! families actually emit", which is the question the corpus was captured to answer. +//! For HEVC this is the ONLY pre-wiring validation against real host output (the +//! client's HEVC rung is still being built), so it runs long before M3 finishes. +//! +//! Ignored by default: captures are hundreds of megabytes and live outside the repo. +//! Run one explicitly — +//! +//! ```text +//! PF_CORPUS=/path/to/au-1785970273.h265 \ +//! cargo test -p pf-bitstream --test corpus_replay -- --ignored --nocapture +//! ``` +//! +//! The `.idx` sidecar is found next to the data file (`.idx`); the codec comes +//! from the extension, matching the capture hook's own naming convention. + +use std::path::Path; +use std::path::PathBuf; + +/// One captured access unit: its byte range in the data file, plus the wire bits the +/// byte stream itself cannot carry. +struct CapturedAu { + offset: usize, + len: usize, + /// The wire `flags` byte (`USER_FLAG_*`) — kept for the RFI/intra-refresh legs, + /// which discriminate on it. + _flags: u32, + complete: bool, +} + +/// Parse the `.idx` sidecar: one `offset len flags complete` line per AU, `#` comments +/// and blank lines skipped (the hook writes none today, but a hand-trimmed corpus file +/// is a thing a human will produce). +/// +/// A malformed FINAL line is dropped with a note instead of failing: ending a capture +/// means killing the client, so the last buffered line is routinely half-written (the +/// hook's own docs call a truncated last AU acceptable). Anywhere else a malformed line +/// means the sidecar is corrupt and the run must not quietly replay a subset. +fn read_index(path: &Path) -> Vec { + let text = std::fs::read_to_string(path) + .unwrap_or_else(|e| panic!("cannot read the index sidecar {}: {e}", path.display())); + let lines: Vec<&str> = text + .lines() + .filter(|line| !line.trim().is_empty() && !line.trim_start().starts_with('#')) + .collect(); + let last = lines.len().saturating_sub(1); + let mut out = Vec::with_capacity(lines.len()); + for (n, line) in lines.iter().enumerate() { + match parse_index_line(line) { + Some(au) => out.push(au), + None if n == last => { + println!("note: dropping a truncated final index line ({line:?})"); + } + None => panic!("index line {n} is malformed: {line:?}"), + } + } + out +} + +/// One `offset len flags complete` line, or `None` when it is not four parsable fields. +fn parse_index_line(line: &str) -> Option { + let mut it = line.split_whitespace(); + let num = |raw: &str| -> Option { + match raw.strip_prefix("0x") { + Some(hex) => u64::from_str_radix(hex, 16).ok(), + None => raw.parse().ok(), + } + }; + let offset = num(it.next()?)?; + let len = num(it.next()?)?; + let flags = num(it.next()?)?; + let complete = num(it.next()?)?; + Some(CapturedAu { + offset: offset as usize, + len: len as usize, + _flags: flags as u32, + complete: complete != 0, + }) +} + +/// The capture named by `PF_CORPUS`, or `None` when the variable is unset. +fn corpus_from_env() -> Option<(PathBuf, Vec, Vec)> { + let path = PathBuf::from(std::env::var_os("PF_CORPUS")?); + let data = std::fs::read(&path) + .unwrap_or_else(|e| panic!("cannot read the capture {}: {e}", path.display())); + let mut idx = path.clone().into_os_string(); + idx.push(".idx"); + let mut index = read_index(Path::new(&idx)); + // Same truncation story on the data side: the final AU's bytes may not all have + // reached the file before the client died. Drop AUs the data cannot cover — but + // only from the tail, so a short file can never silently hide a middle gap. + let covered = index + .iter() + .take_while(|au| au.offset.saturating_add(au.len) <= data.len()) + .count(); + if covered < index.len() { + println!( + "note: dropping {} index entr{} past the end of the data file (truncated capture)", + index.len() - covered, + if index.len() - covered == 1 { + "y" + } else { + "ies" + }, + ); + index.truncate(covered); + } + assert!(!index.is_empty(), "the capture's index is empty"); + Some((path, data, index)) +} + +/// Per-AU outcome tally — what the run reports and asserts on. +#[derive(Default)] +struct Tally { + planned: usize, + skipped: usize, + errors: Vec, + warnings: Vec, + partial: usize, +} + +impl Tally { + /// A clean capture of a healthy session must plan every complete AU. Errors are + /// hard failures; warnings are printed and capped — `MissingReference` on a stream + /// that never lost a packet would mean the planner invented a gap. + fn assert_clean(&self, total: usize) { + println!( + "planned {} / skipped {} / partial-AUs-ignored {} / errors {} / warnings {} \ + (of {total} captured AUs)", + self.planned, + self.skipped, + self.partial, + self.errors.len(), + self.warnings.len(), + ); + for w in self.warnings.iter().take(20) { + println!(" warning: {w}"); + } + for e in self.errors.iter().take(20) { + println!(" ERROR: {e}"); + } + assert!( + self.errors.is_empty(), + "{} AUs failed to plan — first: {}", + self.errors.len(), + self.errors[0], + ); + assert!( + self.warnings.is_empty(), + "{} planner warnings on a clean capture — first: {}", + self.warnings.len(), + self.warnings[0], + ); + assert!(self.planned > 0, "no AU planned at all"); + } +} + +#[test] +#[ignore = "needs a capture: PF_CORPUS= (see the module docs)"] +fn a_captured_host_stream_replays_through_the_planner() { + let Some((path, data, index)) = corpus_from_env() else { + panic!("PF_CORPUS is unset — see the module docs for the invocation"); + }; + let ext = path + .extension() + .and_then(|e| e.to_str()) + .unwrap_or_default() + .to_owned(); + println!( + "replaying {} ({} bytes, {} AUs, codec {ext})", + path.display(), + data.len(), + index.len(), + ); + + let mut tally = Tally::default(); + // The planners take one COMPLETE AU. A partial AU (the wire's shard split) is the + // pump's business, not the planner's — count and skip rather than feed a fragment. + let complete: Vec<&CapturedAu> = index.iter().filter(|au| au.complete).collect(); + tally.partial = index.len() - complete.len(); + + match ext.as_str() { + "h265" => { + let mut planner = pf_bitstream::h265::H265Planner::new(); + for (i, au) in complete.iter().enumerate() { + let bytes = &data[au.offset..au.offset + au.len]; + match planner.plan_au(bytes) { + Ok(plan) => { + tally.planned += 1; + for w in &plan.warnings { + tally.warnings.push(format!("AU {i}: {w:?}")); + } + } + // The spec's own skip (8.1.3): decode nothing, show nothing, the + // stream is healthy — never an error (the WP-2 contract note). + Err(pf_bitstream::h265::PlanError::RaslSkipped { .. }) => tally.skipped += 1, + Err(e) => tally.errors.push(format!("AU {i}: {e}")), + } + } + } + "h264" => { + let mut planner = pf_bitstream::h264::H264Planner::new(); + for (i, au) in complete.iter().enumerate() { + let bytes = &data[au.offset..au.offset + au.len]; + match planner.plan_au(bytes) { + Ok(plan) => { + tally.planned += 1; + for w in &plan.warnings { + tally.warnings.push(format!("AU {i}: {w:?}")); + } + } + Err(e) => tally.errors.push(format!("AU {i}: {e}")), + } + } + } + other => panic!("no planner for a .{other} capture (h264/h265 only today)"), + } + + tally.assert_clean(index.len()); +} diff --git a/crates/pf-vkdecode/tests/data/test-25fps-h265.nv12.sha256 b/crates/pf-vkdecode/tests/data/test-25fps-h265.nv12.sha256 new file mode 100644 index 00000000..4863aaf4 --- /dev/null +++ b/crates/pf-vkdecode/tests/data/test-25fps-h265.nv12.sha256 @@ -0,0 +1,268 @@ +# SHA-256 per decoded frame of test-25fps.h265, DISPLAY order — 250 frames. +# Each frame is the 320x240 picture as tightly packed NV12: +# Y plane 320*240 bytes, then interleaved UV 320*120 bytes = 115200 bytes/frame. +# (This vector carries no conformance window — coded size IS display size.) +# +# Generated from libavcodec's software decoder (H.265 decoding is exactly +# specified — every conformant decoder is bit-identical), 2026-08-06, and +# CROSS-CHECKED between two independent FFmpeg builds that agreed on all 250 +# frames: 8.0.1-3ubuntu2 inside the pf-lxcheck2 image (docker run --rm +# --platform linux/amd64, ffmpeg via `apt-get install -y ffmpeg`) and 8.1.1 +# from Homebrew on macOS/arm64: +# +# ffmpeg -i crates/pf-bitstream/vendor/cros-codecs/src/codec/h265/test_data/test-25fps.h265 \ +# -f rawvideo -pix_fmt nv12 -fps_mode passthrough ref.yuv +# # then split ref.yuv into 115200-byte frames and sha256 each +# +# Sibling of data/test-25fps.nv12.sha256 (the H.264 goldens) and consumed the +# same way by the HEVC parity test. +ef4900effa72cbc00cff38938bc558cc5d70b31ca268aaf3c01c4268a9d8066c +fdbae4473d24c3adcef59a51c1449304c2fa271412622efceeba5ee7437b5529 +9aab5493a8fe3dfbd3f1eab89bf67a4964998bf8f1229c672ddf2d0d5fe26edb +52797a59351b0a02069a3d8b2022ad7e06be4e03a7ab9483e09c0b03e4dbfec9 +9c68407853977ddd95dda04b77676e983fd7ad4665bbbe65497ea5e91f5b19d9 +1d7e6adc6a225e1bcd2c5cc598c0342472edd2d7dcc162c38872fc665b57f3d5 +4633e66a0ec77f26ab955bea209b1e976947ae87d8405e4a70be2d8e6ab69625 +6cce1546ee5c1d414353a990aeb6cd3035199196cc6bd527d1f7985247c30cfc +6e5eb5c2286156d4c7406c9bba159348618799fdf2d46d470ad4f0fe2c6671dc +25b0af55a492eaa503349ab45f1d6449df832361ac60d2910eade5380c5b0789 +dcae9606d93f9405c95e5a03699beeae1442240303df2d457bb3b5fd5e2cbd18 +d7bcf9616f062b9cbdaa076db0aa8fe0e60ef83b51c1336276183fa4d6f2f0cb +003e43b5d1e731ae234146763c1f65b4c4e2a67fdab3d938b7ba442be360a520 +3bbf1a62250deeae73fdf77ff34c2c850886557bc79815a2c968a5d8968a67ba +d8fff092b581e7807b77df684e74f97013b6c69d4610ad17f08855585c3ef5d6 +52d3d01f8f147d8574295859ba1f7437ff11d57eafb5c5325b55c7533abf922e +e75eef989830cbd6c8dc3957fcd96e1061f861c5f12eaefadd21f600ce0998a0 +9ffd2496189a6384eddda2e752a719e27c6c69e5fc74330b75fa7e4c928cfd55 +ff304806867abf9d6d683c0d3bce95473cbad3252e815463e9eb5a1b5fabfd6d +9320a5e1fd33ec1c8b3d2964af8050bbcec219b3287b83c2cbe6f61e8d4f3cd7 +9816da3390b510218fc8605398f9cee8d0c8561e2933f2e2a1f771023a7116ea +198b078fda1f6f1402a53e8c73b99544abb8c4091efab68e7221af73732fc10c +fb13ab23a5e012e4ff0cdbba1af7261a636940831c1cbbe1065fdd4eeb4754f4 +73fca4a423291a3ff6c787a7660085732a687d7e061dff235a29d83ceb96e95b +195ce227acdcfa1a4cd4fe35eff3291ebdd5f3fe7545509e32e17c744eb98b4a +706aee7736f9c599b48dcd156cb27faaaae51f3fe85b14dc12402207033aed17 +e061a0ab22441f9b2be0fd111ca67248ea40bb3f5d3b280b3c52648eed8444b1 +93be6f5a81550ecca250ee0c145e5c4135d05ef0f4a78095f94e85070cae9db9 +465723432c68de41e96fc32e562812343a9fdecd53b9699fcc6770b5f9e4b002 +874b39b128db5b47160cf0d8bcc0040063e23dc5b7f0172a8149f78e7ab57195 +3cee067706084dd8740ba200e3eb1955c93e1df4aaecc298453560e61f658271 +7ea42e3f7c7a6488057d4cb7db1f7dd61f505cdcf8e71bcaa33eb00b734018bf +55b00704e979ac7bf2e4961dfe12f1ef5c559a421e24e3243f943a013e679fe0 +e91f2096b0bde6bef5762f2765d2ce30c77f9e1a9adc151bd07277fb715c8131 +08d1391f8876e67cd086d79a703401da70abdd37e3522445830e40cd820cf3ba +87531fd1641407d8eee5888068c9655920976deaaace01b0c6b6766dace53d62 +1993c8add817ed50882c865eff2c82f93f1c8f050e1ee3171d9518e3a6ac0563 +f682098c0cfa38af29ecb40572aae48c4663e11c5ba9ae256871a1fc62111dd7 +dca4d5cad38a35d15ecd4ef02e2a2effc6c103ac4f27ed61518416bfb8084f72 +33a6d608d4dcb2cb47d648ff4637b62e65b4a769093ba0bbf39ef151e174cf5f +814d05ff585e21676fe08a867ca4593c65870b81d5bb7ae51d8fb795371bf9e5 +91339266713bb79af8d547ad888a010b7ad56debc938e4ecd9f3542a8d148e40 +945258f865bb6b795d06609d57ac77ee5d863bf19fb35785ac7578d46e229cb8 +2b2619f2814b08675b263e38ae8a50d816fca71d6f014f6058cea2b4dae74aee +89b8983416633c225519b3621bf65601a0f6c0f46de5ca3250a9b0c34edff73f +0a007d2eacaf06f629ef854450fca7d83d6f6e1bab60a1145466dc157082f877 +e8550a8a431f623ccfc8457c4f171a6c9bfdc1c67807fe1fb45cf4bdbc14f7d1 +aafa9b366c4d560b9994366b1ec3a2f71c861191316b84e6b46ba7ddf79c19ee +4ec7b250bb49ec17b34a090f36f8cd07692829dcba1ee939a4526e89366ba1a5 +c00e70ec1b43cea1b3d2909d61cd53239f428085b6769913307c9006bfeae83c +b8744028170518a8542d4f37ff05990230ea1ab87d71896b0fb41866b3859e57 +cc9b833a5725bae1c6e9d198c148ef45fced9b34a36fcf043cfd2774baf9ac55 +81f3666280e765cc8641fcfb0a65fac70175e18c7ab701534a5499ffe3fa8543 +f92faf17adfb611afa432c2c4f3c192ca24246a9613601ca109bcec0d8a18c56 +6acda837d6691b80b11c9c83fb41108b0aaefd1a90b5bdae28429542ae998f5c +94db3458a1fac5e9cd5a9ec07de06c659a20051b80bf71b2435d174604e502bd +cdbb03c8d67aeef96c7073346a87b1a41e866c2fec75a5003ab43d10ba953457 +2f05ec023ce09ce00b3fdd277e0da4aabe1e1a073e9612632fb5f5c95103caa7 +5587ef3923591036daf4d471a8a131d04fff2a6612e2e7788ac4662a8a3e16eb +19c7c1534c40e8a4dd7a6461fcd5d8695951dca24d7d2923b5ca12cb4160afe1 +95bb6aefb8ac74e52e3ef64e224a64eecb08d82ff3449d1cc1b8b44888a2ca38 +1278e669f16f311582ea9456e7361c57d4b4748c9902c780c4d9d62c7e603f1c +0d77145396334c2f3008b72441d594f7c3e110da95aef11d6bede46f49c79be9 +099ea10831ceda528df779d1c7b679cdc1d251f10cfc39f4950f7fb3a3cebd36 +8cb5a50ee01d37f8501884be3cd5239f5e57892901afedc4ddc3a9fecf435b0a +5c4f777977b786ab5e80f6bd7ea9880aae7fd8d8cf9e4d79016020d4fc916c22 +d635fab89355529ee6661f8e867e5361bd28773422f8478939dd806162292346 +baebab9b93c6dbb63ea19468538d9663a2f9fef5bae21dba46161454ca05c770 +22f4c44eec9c70d32b05cf0eccfbdf5b477b0f93bc61a17f9b5b4c5a7dbc2ebf +fecadfb3ec25bd00929059e9849da387ca0cee8eec9f0792128f3256eddd5bd7 +48065431ce0b3f8f0ad5f77fc30fa29eb961b1ba5e6a97c0bf41c5d3c0ea8518 +b575ccc73436b7937f98ec1847fd54d7c7a3a086d08adcff9d226613efd5bac6 +be5d094c2a2ab5feb6c988c68e9f2d6eee73bc69e8c51ed24786325d185ad5f6 +6c16634620280926af758f013b66c2a75a79ea67a90184965ea1bf91b9f51696 +0b63b9eedc87d047474f9c46e71dee75ca1e3a5db5cb0c9d7ea0627fa8c82876 +02052e7622fa96d6a0696f5c27d632ee49caedc5ac908a5c9f6a8d6d475be6e3 +ceeff7e73cca36a48a775f2e6b773d7c968c20de18bef4460dca582e6ba4b894 +693663aaf34e534a4f6d2ae7846aa26aa1d73b4bd9f854dd70bdb6a41b0cbb65 +474a10f6eefba5c7d16efd99fee59e0b762380b0f3acbb91e76bdc18a6dc9c02 +4c005f6dbb86ba5ef0001a551af8b4e4aa996e5f27c23658ea3002d319e3c53a +9968c5c0966a246dad5e0b49668d3d1caccc12a7722aaff677a6ad63c32b961e +a1d57c958b92e6a8d557893c62cea08d5959dd34a1421bf1fba02adf7acf0244 +f885bb6f13840be75c88223d07105f0ccbd4aa678ac2e76ca347739b0e3158b0 +644526f73200746ed78f8d0a35bacf486b17c6bb890f58e12c433ccdad59a37f +2beef1de03e18970f81792b0d247fdc9112d603dca852507b48d3585c86a7fa6 +b76fe733ccb80eb233ccd263dbfe264b6c298a93a3847159ab19fd8d9566d63d +cc7cfa0dfd02fec28ddc9504cf0e4a2656c2b3e7e2f13207b9979f9930408ea6 +fa6546a8828e3e3c75b1a60c0681db322d521fd4dd3fc9b005ccfb42d58fd9ba +d1b2527753951c11ff69e4db8323c058e40f411bfa4e23915f5675e30cd43b77 +5eaf026663142af2ba32628a4589aaa143977ecba28181730172099c6f749abb +9d18abd96a6548cb643b39a2499b1f718713dbd92f66f35392fa0f9fcdf4a81b +38ce5c4261c740ab054dac484af6afe58bf5eacd95fd322113cf532f362e0ea2 +55852ee3a2f92db0bdfc64c2693ff5a43ed7348a6ea947aaf9646b964c1842e7 +ba7488d71bf856cebde72d96245a582efae3d3392dc75615fdce2146b123cb0f +3e4c55e47febd86861cf4a7b56cb2e6fdcb1d9542d521ab04f57643d844d2f81 +8df056cf2f0d1e13c1d1a419251b09a9e1edd164e5c9ace7525be2c08d7cb84f +2e7fc74a622da56c8ad17d66f7fa2bd5a466dfe4147db80b7a58dbc93158a0ea +ffac7a103ab051aad9ff90fa43f9d98c149aaa6057d59660a70256bc9ab3951a +3725bd25b394076327a8f6ff010fb9e4b7662d67b48c80693997d434eb541399 +fd807e07cd0f003ac3d834487bd7526b000d3ffd79295ba92fc10caf3e5bd6ad +071469b129dc9ef55e67f773ad5c2ef6498b728169e98910dc5923b4181e3ad2 +512ced1859982b5bbb58f48a5ae8006c56bd28e165f0a761c38bf214d2636983 +8a663c0abe78913974d29f4380b7addc46612d55fb431c11042bb7985f22d501 +92ea5506c94d885aedb9dfa7f884a43ed8289b49542b67861e576cfea4d62b11 +a29dde155a03511735f9370ce23b95bfd98868434ddf6e9a3669c2edd0ad1d8f +1688fd2fe7b5f7e4cfcc40d3afff903bd0a5cb8536c0d7da4d53b90544b03227 +96f593ab7339f0cb02bec07b6f5bcc11d2494ced35a90d4037c961a2e284d914 +9450d79524043cc3f2afe0dda19b6a5a06de7128ad9826802b798255f72aaf00 +4dee54c01448ed0595fe1c0108c97031c48de96ea425c8f8c2e5b3382e862a3e +24bb1d429aee55c9d2c719777d15a3e8116fd2074a569a5e44a8d5de6d11fe8f +fe0255625438d6403fd663f60eab05ff990780054eb530dbfaa3552a978cfdc5 +452961eb29a947dd2818409c8d5e283ebf47281eebeb9c368164b22dadb315a5 +e49ad550b30140b2b99b6edd0e1a7873f3ba0145829037c2490467ddfcfc439a +f02dd0ac342330223e9c0ae56ed6a9efc3a0c69d8bb4c036943dd16f654f10bf +60352cf761e2cd8ba2cc0025c45e715a8719fabe78d5de7a382da4b648704ea7 +3b79506fc18e01a1168f880712c539f0a520712dd62d2f824b6b754ce034f1f2 +3c16234ed4606128fca31196a81f769ac69a4261aa176bf8c8d86b8699177926 +6a98fcfcef5d1ed813ba3f2f66f5b1bd11729430187a7366dcbe9617f2914646 +5683c679c3c0fb24b740ef4582c787591f10dcffc9e46037422829f5b58a1501 +9b4c0ca82d8440b630a569a8d6525384beb26ca7d54b7a1ddb1b83a00e134701 +5693915c011d2ad738b84c8a04cfb8081006b8835ec655e76d829e5dbcbe0688 +1ddbeb8cbe7d0d8ce61a94e3ca35730a07e59bdbb692cc82dfa3ea0ff9ba5979 +ca10afdd882627e9a6cb5d389c71fa015cb0015124c7445295ac42660a01c491 +7aa406047f3fdc2a07d6d228b30535734689c99d93e8c5c7865044187ca0d3b8 +8e3f8d26612b58f985ed778f1ad5f6cca0be49acee5b7d4d09ab2b886a3327bd +9edb22ab753a8cb66392d301144df20c7e67e3afe6bc9d1c3143087ecf26120f +fc4d040db9172642d710b4edabca7e5444fdc9099f9867b5942e200da1d6373a +1cfd8b95c000cc30892061db6dac6323f2f7a654c30d0ed024368997ac65e673 +f9ebda67091c2b26e7205bdab431299d982dace99cedfec13c80cfd3195286c0 +5cea0e5febf079803e85b57bf45d3d563d0e8fa1e45844af18609077497fbd3c +2c4c74504660b5222f2135407805000fe1a41df48c47b28685e57020c6d21061 +d49cffec62c461a04292556bb21f78861556b058db006143696e53cee077d26b +9d7ad96746eef142fec236232c0d275943a5258a40fe3fa384aaa5614856cbc2 +958cde369e11df3a7bc534ac549fc11b4a6e325fc02942f49c68c47e99ed65bc +a05043d7198834dd8da3d544dd7c11d3f03a3389a422fd0a0be732824b587047 +700a4641d8ee0a7ab2b0a09421755ee6223ef1f7b6436a96eaa479140361b40b +16d487df87dc05fa10373f139558597f0c82b9a22828de0ec462fae72ac09124 +73a8de0a8f0652405aad48cb13765767c80286ef190dfc1a10234fae0ee0dc71 +19f30498e93062ddf30e284bb44b9c6b6ee5c2ef6e143d40cf7dd76b669bf670 +93de878f03e69fce3ec00963df3bb941713bf225b665b0baede46e4a9559b567 +fc55a32f7fc41925a5bdf7fcf34513b90b6acc326771fd77db7fc0f4572db395 +e84bffc862d0d017903ec43414b0659b9720d0a4d646be3d1576a168a4333ab3 +ef655d0f2ee29f23409d1a8689710e024aae636c1ecfe8bc1915774a94732f8d +95c63e9092ba3a5887e9ba2044aea96315b25e55af2fbadbad50345fb98629fa +15501a54f24943a19a663263c5459d31c45441147cd58eba4935ab67d1bed291 +bedcf37b2101e06e9f76f85df28e70574bff094ddee96fb3b4733da63551e22a +e06487f3691cb56df61d13e838d62a403e7cf4cbf399c89e98a7d04597e91653 +483596c621e2646ffe668743eb0e55269bc7dde6188ffb80c086b215d73a9717 +a84fcc692a5ae998f082fb21e429076d59c48fa6880309bbe692e6f0b5c0ab86 +4aace8b7ea4891aa80f8df369689e0dd587d88d55160d40ff1fc03dd4fb7fe44 +b0b13ef2e6684489fe0af1ddcc2dc2e04e833d372b7c4838d1cd173da993265d +6ca96a7be457ba2bf06aba287fdad92bf356d31723d392e654ff352d480b5da0 +69554f33448ca613f466adbb14748ce2001518c861397100579340b9d9c71dee +baebb6de906b34a88a890274e491ffe00ce28b003b0ef1331c484740c036a30d +841a7b7efd05b4f8aadf6bb0074067a64b118c20505f317babcdebcdac05b9aa +d2eeff202d639a4c0ff451caad9cc7a7f4bb8aa62e6659671fadc4ba85a7eb29 +4ebc9035f1db8feb6e56f47a180b4108ee374c7442ea5cb2ba4fa59427d88fc6 +b7e2988ff896ed2618a17766dff44839b23b934108a1d75948ed0826c8129d42 +9eb4b2490eabc51f7fcc75c5aad419208238ed6807ff109ec02cf0f6ac1e12dc +4ba2d5fc6b9b27c53b4217b04f70dc219073a9c1cb0863e040d9973c88899084 +e26701bcba0f2014ca93eee23893e020bf681e071c9e62d0fb75619de7de715c +ec829d9898ba7eccb51af41e5412255bfe93e61137f56a21d4999911fc154bfe +ad47b88c0fc7750dd1a5cc27cf134638a9270342e957aef81e517a5a519933fd +1d39f1ebd301526344a29c8d07049a389ce157e8a0184fb34e38ad3c2a851afd +20d4699ce3a43154a62ef5cbf8f14bc47cfc344eff92ec78e4e6108d2827ad33 +79ceadfa9d591934dc9d87125772ae7cf3a0f26fe91ee77f27eb11a23924981f +3d481e4dcd5f0e849323822d687f4cbc7570474b985251fc13cb019ac5f72a81 +80a680a0054e1b0968a3ade98a8ccb42789082584d3a72917d2a1857b348b5e6 +fd031ad921c6dad12473e6fa148c74e05660e98be91cb68d3c5b43da423d06d5 +7f5fa38b7ce6401ff353a9b680fccadc138af24cd66369a05a796c363bcdeb45 +8a6ab1effe3736620a7d21ff943e7c50bcd244d9ebd26dd0f613c86b1e866ad2 +8020cb196e1830ce33c5242cebae2c13ca64e36b0a1186cda71b3b19fcdcddef +8c8ad9f8f7fd75798017937e117699485711220f2b529f0b4e62c1765461538c +66cad5c567e55c73b672f710a95c05825e26786ba1c69551f0d1c6ef92887d70 +02379f8b49c069e1a14e4fc371b84aa46e0589d6b77050a51fa888b20de13a7f +f7bc1ca4d9c8a3ac7dd2c53f3829c459bf656fef033a1c2e07942e573f1488f8 +fafd7c0ef34504324578f48f22bdcca6b87d3a57a80ac6a9359b9559ba1ba599 +e0f5d057bc2ea677d16506f272387801e5717afad3c27d29c56e6f491496d399 +951dbfd636ec1d0a164350cad1f0a69f097ae28447520e6d3ce4dd18cb7ce33e +b1707faa90c8d2853c98f3deb845283b119a3f626ff8733d1ca23367b617e24e +6835f35622b8a7f0a3393cc95aae42dd81d51cc31de0fd70aa0a2c4d9f7dc540 +6038a51c740da0a594613a563e0290493e1cea0f45b98334711137ecb309ce1d +9426f1fb57ce97b8a50cdfc98da236cd6c12404e0d9a27dbecfe53bd4e70ecc8 +55d02d7484a0e86dd43b28722b91ee46e47af72cddb478692b0569cf0ea19f8d +48a809c673c51d8acd8c8de0877bf5addd82ebd7ae39260dee46627b99e5f35e +61d251ca5cd28071a241e6e31dd1cca9d6c92aeab9bfb705c90fc02fa59d0721 +2e129f22a54b5a80cd9993eb6b10c0a8f3eb7abe2b47f5d2cc64c9365a083864 +c7acbe0e490196575368ffe3cdb08a12cb99466c0c77423fd1592033591e04ba +193d41b5c3ef5c85851f4ab1b3b5fb8a8f90575e2377362e89e35915de9166c3 +eb4c463ca22eac43632f8f5048343457e9bb0bbce83a67c4f3c34a74341622a1 +daff5bbe42272e24b29056ea9ab29b5dd70b9aa985370989ff1502d20b715890 +22b400207ffd843e7aec2ec2cbd6d53159e7252f1c60314ad5164f903862972b +d4f3bf876293ee9de770ae8a940458d75ed0f392a7fc93682223cee31d257d32 +a0ed984f000cf909cba32fddc9c9ac78c626105f2c0bc8abbf3f3f50d312a342 +825ca691eeaf5c2373892742050620e26ce1c1e81fb88c8ef089a1fbabb66448 +08e2a544c9756812b03513048e7ae109e9989aa87facd534ad633c15e440db49 +538d67388d4f47b8ee58da756dfb913f2762610044fc375849a4aeddd9cf8adc +7b11c5921e69b8544ad1fa6ff1f7da57912f1473392a64312b0132e657bf788e +87b0b1cb7b95066f491834c1f3f01afb6bc9e6e95a8a3a7b4cd3177ff0d52f4f +a6746ac64f664e61e3d1d041fc71ca5379c831925b714461b3203187e44a2542 +a88bdd638069672b79a708aeea9a12ab17fd7baebfc898182d4df64404dce9ca +0db7d0fb2b4262b93d1ec21bf00a428fafc96fb410cf1d5d2c30a8cefd374e93 +8e2218f82bc6b653705ff81ff03dc98dc90dd7249183521e1dae62429fe6e4ad +2d48037c916139c029f0d9ff9bb47ecb5dd4c0e3af9ce746077cb4a16759c62f +a03d1c762fea0edb027741537fbdad74b2aee95828218c8c6358ebe019f14782 +711ea5dd1cd0884c949066bc35af77e89a6760fc7812feaf34ff34c14b6cd124 +1802816711243b32c875a8b0c5fe04212f9dac427dfaa04a4ae614111cc9d490 +11421e2188d4b46d484edc1f346f22fefeba6ed504952fa53b33878512b7f364 +a54098f99bf02a7a143ae21a0e05809ee66ba2f712fa9a270c307fde43587f14 +0c3fdc1a5c92bba34cc7bd69f84771e7a979601e71428b9e8ff91e7a104552ff +023b3bf5fac6a8518ee80de4b3a41e443cee431eaa6e701b839b2ab5b42420b5 +cc073fa309af6fd9023c642316785bbd56d49f4ba60f96248a8991d3287455f3 +a24b3cf2b9e1c2639e8dc4a33dcc4f652afe949e8dfe50d3dbe6fb075c63e5ef +454e182ce3b29aa1942470b0a223d6070dfdfad119fbd9cd86d52d34d0261bd1 +14287adcbbd45689d25c04cc29edb30f8f2bc8e40499436fa09b33558a8f3c5e +ae957a764471b46c353396802418c1a13f3701b7847ed3749bcb619a95ec5546 +500832b21119374cec8b831a06969cb0534892707b3c8b8d49354bc2c9e87262 +29a45c4d1a1240f45efb271b7710418501030ece1a526557364f65591d34daf8 +2ecc2c67e41643ee4d3a1b1c27d2d608396b68de4293d2df2e061faca97456e6 +051f0a7a60641592f2a83846c157bb42538bf0be3f31b27e24f6c69ca6575485 +c108938b16d30b2a13bbaa2fea82ddecefd85b3c0e52a0a200f63a7c5c6526c9 +5b5c1d60dcef4e90866a824a1dbf3e0cb67eb95661dee5f17aaa146d04c99c8e +eb8517229cccf5d0a5f3f43f21ef18ca68520684f4761fed62894555e05fb132 +227566441065679a17fcaae3a15b019606f3ea941b0b78e2fbcff48c0de253ea +61618f5db64098bfbfe1f4a6e05ff647c703a00b74854bc4a5a60dd1672a7e66 +44add97c02f2d128a3b15363ccf61c126438aa96c826b2c0577402fa56de3cb7 +00feb43366991297b770fe2929bde6d1844e6b984662db62323138662c9b51db +43dac961a766a410c13eb45ff6422d6f9fd69944e1d81801b19bb38a2f30fb54 +e8deb99a10dcf354e4065c587c51feaba35d45b6a3cd3333a5d8657ce0e7bc34 +f0729cfcd090b1ad620934a99d06adc5705a4bd2d2d57ac2e82a35f5e030eb1d +9cb217fd3190461954921f1b5be6e6d7337cd694ad30d7a3e10f672b2f74a717 +346cf7fd86aab5ab941b926a243c6aa7b65fdca52aaddfd6884cc1fd7e0f4a2e +d8ce9c4921d6fdd4c57dc64a250c2dc886e74e9bff336b8c7030ba4a539ff79a +eb67d21bf9fec6d9ee9af47b3dfe95a44c201836d17e7e08a3bfe561908b7008 +bdb306831f8bbcd7e675f7db95ef50d66ecff44dabdfe6e89972cf016b915b6b +e3d1aed962e48afacd57007efcfcafb0b4d245ae4c579676d98c1b21855fbf9d +c092a99f788b140c59d0288560c53c6522cffcaa9223c2ce88b8fb8a7ad5ee45 +6d0dc8953f306f6913d3be67c54a113eee14ca9600b7d6b5cd99f2ea18cf4984 +94bd0d5ee0cc10c95c836337733f1730be33151383d7946afbc24e2e2fabd346 +99ffdc3d1b432890b662126c09dc56a45c6491b580dd5bc4b2a7f464fd7b00ed +fb8d38ac9ebfb8f196a6549161931d723151d3aa180f77fe9130e059e12d2481 +fca6ae50a370e226998e785ec9c5b7dcfcb23263ee7bb621da05eaa6fcb806e7 +f3d0c20bd10b0b2fae3ea4bb4180ae2174310a283c03f5c7733ac937a07198fc +f4cdac0c218c483533fdd15627cb3c93296b714159bbb1594c3c3ea59ba09185 +db3b2227ac0212da4c2a01ba24c41aa1a075e42117d70bd220b8f03c27f6602f +9561dc8fad0e5e16c1a035da4edaa1c8255cd36340a0d3ee2731ffa65be2037d +53dc9c6bc9462c30449f4df70c19981d5d144895518bee4258934399f91fc181 +ab0bf02e7debd2d7fc43f9e0d3cf5d3c816a9c10398b3336eb17cf6c3ef20f3f +d1666cdfb8645f4f2e5f554a4036e789dc98eadc0d06bf99c7daa4203a0a3e77 +30044bd22f2ed02193191fae5ece9a623c7c9b22442fc2f2274da08a5708f0d6