diff --git a/packages/cultmath/README.md b/packages/cultmath/README.md index 1a26571b..8648263b 100644 --- a/packages/cultmath/README.md +++ b/packages/cultmath/README.md @@ -82,7 +82,11 @@ shader semantics. the analytic Hessian of `snoise(float3)` on the same lattice, hash and tie order as `snoise_grad` (a symmetric `float3x3`; GLSL `mat3`), and `integral_clamp_linear(a, b, lo, hi, t0, t1)`, the closed-form integral over `[t0, t1]` of `clamp(a + b t, - lo, hi)`. Three measured bounds describe `snoise`: `SNOISE_LIPSCHITZ` (gradient + lo, hi)`; and the octave stages `span_fbm(path, stage, + octaves, lacunarity, gain)` and `span_ridged` (shader twins `cultmath_span_fbm` and `cultmath_span_ridged`, + whose callers build the `SpanStage` themselves), over a `SpanPath` and a `SpanStage` (`SpanStage.Identity` + is the plain fbm in C#), which return a `SpanNoise` (`field` as `float4(gradient, value)`, + `curvature`, `fold`) for a stretched, offset, shifted octave sum over a span. Three measured bounds describe `snoise`: `SNOISE_LIPSCHITZ` (gradient length, `NoiseBoundTests.MeasureLipschitz`), `SNOISE_HESSIAN` (Hessian spectral norm, `MeasureHessian`) and `SNOISE_THIRD` (third-derivative operator norm, `MeasureThird`); each is 1.10 times a seeded maximum refined by ascent, and a @@ -164,6 +168,7 @@ CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`: ```powershell .\packages\cultmath\tools\get-dxc.ps1 +.\packages\cultmath\tools\compile-hlsl.ps1 .\packages\cultmath\tools\get-spirv-cross.ps1 .\packages\cultmath\tools\compile-hlsl-spirv.ps1 ` -ShaderPath E:\Projects\Odin\crates\muninn-move-tracker\shaders\MoveSphereCandidate.comp.hlsl ` @@ -174,6 +179,8 @@ CultMath keeps portable DXC outside git under `packages/cultmath/.tools/`: -OutputPath E:\Projects\Odin\crates\muninn-move-tracker\artifacts\shader\MoveSphereCandidate.comp.metal ``` +`compile-hlsl.ps1` compiles both `CultMath.hlsl` copies as a `lib_6_3` library with the x64 DXC; a warning fails it too (`-WX`). + DXC emits Vulkan SPIR-V with `-spirv`. `-TargetEnv vulkan1.2` is exposed by the script, but the current Windows DXC release compiled this shader successfully with DXC's default Vulkan target while rejecting the dotted target-env value. diff --git a/packages/cultmath/docs/design.md b/packages/cultmath/docs/design.md index a4d4c9ba..3511d755 100644 --- a/packages/cultmath/docs/design.md +++ b/packages/cultmath/docs/design.md @@ -112,8 +112,10 @@ local) and writes through a swizzle component (`v.xy.x = s`, which C# rejects as modifying a temporary). The HLSL side is checked too: `tools/get-dxc.ps1` fetches Microsoft's -DirectXShaderCompiler, and -`dxc -T lib_6_3 -HV 2021 shaders/CultMath.hlsl` must compile cleanly. +DirectXShaderCompiler (x64), and `tools/compile-hlsl.ps1` compiles both copies of +`CultMath.hlsl` with it (`dxc -T lib_6_3 -HV 2021 -WX`, so a warning fails like an error and the +text must compile cleanly); the script throws when a shader file is missing, when dxc cannot +launch or when it exits non-zero, so a stale exit code never reads as a pass. The mirror test compares bit patterns (any NaN equals any NaN; -0 differs from 0). It proves the text of `CultMath.hlsl` computes the same float32 results as @@ -123,13 +125,20 @@ compiled under `using static CultMath.math;`, so its intrinsics are C# `math` itself: the test proves the composition in the text, not the intrinsic rules, which only `HlslSemanticsTests` pins. +Every mirror function is compared, internals included: its counterpart is the `math` +function of the same name, public or private (`snoise_mod289` and `snoise_permute` are +private), and a function with no counterpart fails by name. A struct parameter maps to the +CultMath type of the same name, and only if both have the same instance fields by name, type +and order (`SpanPath`, `SpanStage`); the argument is built from the case's scalars field by +field and copied to its C# twin by field name. + The comparison also allows exactly one struct return shape: a mirror function whose HLSL return type is a plain struct of `float`/`floatN`/`int`/`intN` fields is compared to its C# counterpart field by field, recursing into each field's own components, down to bit-for-bit scalars, matching each leaf's declaration path (field names, not just position) and declared type as well -as its value. `CultCellular` (`math.cs`, `cellular`) and `CultPhasor` -(`math.Phacelle.cs`, `phacelle`) are its consumers. A struct return with a mismatched, reordered-by-name, or +as its value. `CultCellular` (`math.cs`, `cellular`), `CultPhasor` +(`math.Phacelle.cs`, `phacelle`) and `SpanNoise` (`math.Span.cs`, `span_fbm`) are its consumers. A struct return with a mismatched, reordered-by-name, or retyped field does not get a second comparison path or a shape of its own; it fails the same walk that already handles a bare vector return. @@ -777,11 +786,18 @@ differentiating each corner's gradient `-8 m^3 s x + m^4 p` once more. Each off-diagonal entry is computed once and placed twice, so the matrix equals its transpose bit for bit, and a clamped corner (`m = 0`) contributes nothing. Built from nine scalars, the matrix reads as columns in GLSL (`mat3`) and rows in HLSL, -which is the same matrix because it is symmetric. `SNOISE_THIRD = 536.9152` +which is the same matrix because it is symmetric. `SNOISE_THIRD = 535.8009` (`math.Span.cs`) is the next order: 1.10 times the maximum, over 1e6 seeded points, -of the operator norm of the third-derivative tensor of `snoise` (central -differences of `snoise_hessian` at h = 1e-3, then an ascent over unit vectors), -refined from the 1e4 largest; measured 488.1047 on the tied kernel. +of the operator norm of the third-derivative tensor of `snoise`, refined from the +1e4 largest; measured 487.0917 on the tied kernel. The tensor is closed form, in +double: `snoise_hessian`'s per-corner entry differentiated once more, on the +kernel's lattice, tie order and hash, and the test +`ThirdTensorMatchesDifferencesOfTheHessian` holds it against Richardson-extrapolated +differences of the double Hessian. The operator norm is a local ascent over unit +vectors (a dense sweep of 20,000 directions at the witness finds 99.9987% of it) and +the outer maximum a sampled search refined by ascent, so the constant is empirical +with a 10% margin, not a proof; an earlier pin from central differences of the +float Hessian read 488.1047, its roundoff. `MeasureThird` takes the measurement and `ThirdConstantPinsSampled` pins it. `integral_clamp_linear(a, b, lo, hi, t0, t1)` is the closed-form integral over `[t0, t1]` of `clamp(a + b t, lo, hi)`. All three have HLSL and GLSL lowerings and @@ -799,6 +815,76 @@ constant of 40 misses there). The margin over the function itself: a scan of the independently over every hash configuration gives 52.22, so 55.64 sits 7% above the configuration bound. A change to the kernels must re-run the measurements. +### Span stages + +`span_fbm` and `span_ridged` (`math.Span.cs`; `cultmath_span_fbm` and `cultmath_span_ridged` in HLSL over +the same struct types, `cultmath_span_stage` behind both, and GLSL derived) are the octave stages +of a warp chain, `span_fbm(path, stage, octaves, lacunarity, gain)`, each returning +one `SpanNoise { float4 field; float curvature; float fold; }`. The inputs are two +structs with named public fields and no positional constructor, so the three floats +of the span and the six fields of the transform cannot be swapped silently. +`SpanPath { midpoint, velocity, speed, acceleration, halfLength }` is the path +`q(t)`, `|t| <= halfLength`, through `midpoint` with `velocity` at `t = 0`, +`|q'| <= speed` and `|q''| <= acceleration` along it. `SpanStage { frequency, +stretch, axis, shift, offset, octaveStep }` makes octave `o` sample `snoise_grad` +at `x_o(q) = f_o (M q + shift) + offset + o octaveStep`, with `f_o = +lacunarity^o`, amplitude `gain^o`, and `M = frequency (I + (1/stretch - 1) a a^T)`, +`a = normalize(axis)`; a zero axis or `stretch` 1 means no stretch, and `stretch` +must be positive and finite (unguarded, like `lacunarity`). `default(SpanStage)` +has frequency 0; `SpanStage.Identity` is the stage whose field equals `fbm_grad` +and `ridged_grad` bit for bit. `octaves`, `lacunarity` and `gain` stay positional: +they are `fbm_grad`'s own trailing triple, and `octaves` is the one int among the +scalars. `field` is the octave sum and its gradient in `q` (`f_o M grad n_o`). +`span_ridged` sums `gain^o (1 - |n_o|)`, `ridged_grad`'s convention. The Span +Lab's stage is `1 - 2|n|` per octave: with `G` the sum of `gain^o` over the +octaves (signed, so the alternating sum for a negative gain), its value is `2 value +- G`, and its gradient, curvature and fold are twice `span_ridged`'s. + +The shaders have no `SpanStage.Identity`: a shader caller builds every stage field itself (in GLSL, +`SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0))` is the identity). A constant would +need a second transformation rule (C# has no const structs, GLSL wants a constructor) and a mirror +exemption, for one value; the mirror test proves instead that the shader text at a C#-built identity +returns `cultmath_fbm_grad` and `cultmath_ridged_grad`'s field bit for bit. + +Bounds come from the three pins only. With `|M| = |frequency| max(1, 1/stretch)` and +`nu = f_o |M| speed` the noise-space speed, the Hessian of the noise on the span has +operator norm at most `Hl = min(SNOISE_HESSIAN, ||snoise_hessian(x_mid)||_F + e_h + +SNOISE_THIRD nu halfLength)`, so `curvature = sum gain^o f_o^2 |M|^2 Hl` bounds the +operator norm of the field's Hessian in `q` at every point of the span (the pin takes +over once the third-derivative term exceeds it, and at `halfLength` 0 the local tier +alone remains). For `span_ridged`, an octave leaves the branch it is on at the +midpoint (`1 - s n`, `s` the midpoint's sign of `n`) only where `n` changes sign, and +there `|n| <= V - |n_mid|`, with `V = (|grad n_mid . w| + e_g |w|) halfLength + (Hl nu |w| ++ Gl f_o |M| acceleration) halfLength^2 / 2 + 2 e_n`, `w = f_o M velocity` and `Gl = +min(SNOISE_LIPSCHITZ, |grad n_mid| + e_g + Hl nu halfLength)`; `fold = sum 2 gain^o +max(V - |n_mid|, 0)`, exactly 0 for `span_fbm` and for an octave that cannot reach zero. + +The float32 `snoise`, `snoise_grad` and `snoise_hessian` are not the exact functions, so +the quantities read at the midpoint carry allowances, by the derivation of `af_snoise`'s +comment. `S_o = |f_o| (|M q|_1 + |shift|_1) + |offset|_1 + |o octaveStep|_1 + sqrt(3) nu +halfLength` is the sum of the magnitudes the octave coordinate's roundings are relative +to, and `e_n = 2^-14 + L 2^-20 S_o` (value), `e_g = 2^-12 + H 2^-20 S_o` (gradient +length) and `e_h = 2^-10 + T 2^-20 S_o` (Frobenius norm of the Hessian), with `L`, `H`, +`T` the three pins; they add no pin. Measured against a double kernel with the float +hash (6000 points per band, `|x|` from 2^-2 to 2^23), two samples observed the error's +maxima as fractions of `e_n`, `e_g` and `e_h`: 0.057, 0.046 and 0.075 in the cut's own +sample (seed not recorded; the 0.075 is against `T 2^-20 |x|_1` alone, at the smallest +band, where the absolute Hessian error is 2.3e-5 and the absolute term carries it), and +0.064, 0.0485 and 0.057 in an audit's (seed 0x501). They are samples, not bounds: a new +seed moves them. They say the allowances enclose the error with a margin of about 15. +Errors grow linearly in `|x|`: the value's is 2e-4 at 2^8, 1.5e-2 at 2^14 and 0.94 at 2^20. +Rounding in the Frobenius-to-operator-norm step needs no allowance: the Frobenius norm +exceeds the operator norm. + +Domain. No coordinate range is checked, clamped or refused. The allowances grow with +`S_o`, so `curvature` saturates at `SNOISE_HESSIAN` (true everywhere) by `S_o` of about +1.1e5, and `fold` turns positive for every octave whose `V + 2 e_n` reaches `|n_mid|`: +the bounds stay sound at every magnitude and stop being informative where float32 noise +is (see "Precision domain" above). `SpanStageTests` encloses the exact (double) Hessian +norm and both the float and the exact branch departure at 64 points of seeded spans in +bands of `S_o` from under 1e2 to over 1e6, and in a fixed span where the local tier +with its third-derivative term halved fails. + The slice. `af_frustum_ball(centreSlope, z0, z1, footprintPerDepth, warpVariation)` returns the centre `(m_c z_m, z_m)` and a radius `z1 footprintPerDepth + warpVariation`, with `af_frustum_axis` the axis `(m_c, 1) h`: diff --git a/packages/cultmath/shaders/CultMath.Affine.hlsl b/packages/cultmath/shaders/CultMath.Affine.hlsl index 09616940..21360d07 100644 --- a/packages/cultmath/shaders/CultMath.Affine.hlsl +++ b/packages/cultmath/shaders/CultMath.Affine.hlsl @@ -14,7 +14,7 @@ static const float CULTMATH_SNOISE_HESSIAN = 55.642143; // An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(float3), the // next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD). -static const float CULTMATH_SNOISE_THIRD = 536.9152; +static const float CULTMATH_SNOISE_THIRD = 535.8009; float3 cultmath_af_point(float value) { return float3(value, 0.0, 0.0); } float3 cultmath_af_symbol(float x0, float a) { return float3(x0, a, 0.0); } diff --git a/packages/cultmath/shaders/CultMath.glsl b/packages/cultmath/shaders/CultMath.glsl index 323f1e64..a9ed32ac 100644 --- a/packages/cultmath/shaders/CultMath.glsl +++ b/packages/cultmath/shaders/CultMath.glsl @@ -302,10 +302,10 @@ mat3 cultmath_snoise_hessian(vec3 value) // The integral over [t0, t1] of clamp(a + b t, lo, hi); see the C# math.integral_clamp_linear comment. float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1) { - if (b == 0.0) - return clamp(a, lo, hi) * (t1 - t0); - lo = min(lo, hi); + if (b == 0.0) + return t1 != t0 ? clamp(a, lo, hi) * (t1 - t0) : 0.0; + float direction = t1 < t0 ? -1.0 : 1.0; float start = min(t0, t1); float stop = max(t0, t1); @@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float float leave = clamp(max(atLo, atHi), start, stop); float before = b > 0.0 ? lo : hi; float after = b > 0.0 ? hi : lo; - float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave); + float held = enter > start ? before * (enter - start) : 0.0; + float kept = stop > leave ? after * (stop - leave) : 0.0; + float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept; return direction * total; } @@ -748,7 +750,7 @@ const float CULTMATH_SNOISE_HESSIAN = 55.642143; // An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(vec3), the // next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD). -const float CULTMATH_SNOISE_THIRD = 536.9152; +const float CULTMATH_SNOISE_THIRD = 535.8009; vec3 cultmath_af_point(float value) { return vec3(value, 0.0, 0.0); } vec3 cultmath_af_symbol(float x0, float a) { return vec3(x0, a, 0.0); } @@ -870,6 +872,119 @@ vec4 cultmath_af_frustum_ball(vec2 centreSlope, float z0, float z1, float footpr +// Span stages (invariant 8): the SpanPath, SpanStage and SpanNoise of math.Span.cs, field for field in +// C#'s order, and cultmath_span_stage, its octave stage, statement for statement so the text is a bit-for-bit +// mirror. They follow the includes because the bounds read CULTMATH_SNOISE_LIPSCHITZ, _HESSIAN and _THIRD. +// A shader caller builds a SpanStage itself (SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)) +// is the identity of math.SpanStage.Identity, which has no shader form); see math.span_stage's comments for +// the span, the transform, the bounds and the allowances. +struct SpanPath +{ + vec3 midpoint; + vec3 velocity; + float speed; + float acceleration; + float halfLength; +}; + +struct SpanStage +{ + float frequency; + float stretch; + vec3 axis; + vec3 shift; + vec3 offset; + vec3 octaveStep; +}; + +struct SpanNoise +{ + vec4 field; + float curvature; + float fold; +}; + +// The octave stage behind cultmath_span_fbm and cultmath_span_ridged, as math.span_stage is behind span_fbm +// and span_ridged. The Frobenius norm of the Hessian is written out in C#'s order (GLSL has no _mRC member +// access); the C# local named step is octaveShift here (it would shadow GLSL's step()). +SpanNoise cultmath_span_stage(bool ridged, SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + vec3 q = path.midpoint; + float halfLength = path.halfLength; + octaves = clamp(octaves, 0, 16); + bool stretched = dot(stage.axis, stage.axis) > 0.0 && stage.stretch != 1.0; + vec3 direction = stretched ? normalize(stage.axis) : vec3(0.0, 0.0, 0.0); + float squeeze = stretched ? 1.0 / stage.stretch - 1.0 : 0.0; + float norm = abs(stage.frequency) * (stretched ? max(1.0, 1.0 / stage.stretch) : 1.0); + vec3 mq = stretched ? stage.frequency * (q + squeeze * dot(direction, q) * direction) : stage.frequency * q; + vec3 mv = stretched ? stage.frequency * (path.velocity + squeeze * dot(direction, path.velocity) * direction) : stage.frequency * path.velocity; + float amplitude = 1.0; + float scale = 1.0; + float value = 0.0; + vec3 gradient = vec3(0.0, 0.0, 0.0); + float curvature = 0.0; + float fold = 0.0; + for (int i = 0; i < octaves; i++) + { + vec3 octaveShift = float(i) * stage.octaveStep; + vec3 x = scale * (mq + stage.shift) + (stage.offset + octaveShift); + vec4 n = cultmath_snoise_grad(x); + vec3 g = vec3(n.x, n.y, n.z); + vec3 mg = stretched ? stage.frequency * (g + squeeze * dot(direction, g) * direction) : stage.frequency * g; + if (ridged) + { + float s = sign(n.w); + value += amplitude * (1.0 - abs(n.w)); + gradient += -amplitude * scale * s * mg; + } + else + { + value += amplitude * n.w; + gradient += amplitude * scale * mg; + } + + float weight = abs(amplitude); + float factor = abs(scale) * norm; + float nu = factor * path.speed; + float rounding = 9.5367431640625e-7 * (abs(scale) * (cultmath_csum(abs(mq)) + cultmath_csum(abs(stage.shift))) + cultmath_csum(abs(stage.offset)) + cultmath_csum(abs(octaveShift)) + 1.7320508 * nu * halfLength); + float valueSlack = 6.103515625e-5 + CULTMATH_SNOISE_LIPSCHITZ * rounding; + float gradientSlack = 2.44140625e-4 + CULTMATH_SNOISE_HESSIAN * rounding; + float hessianSlack = 9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding; + mat3 hm = cultmath_snoise_hessian(x); + float frobenius = sqrt(hm[0][0] * hm[0][0] + hm[0][1] * hm[0][1] + hm[0][2] * hm[0][2] + hm[1][0] * hm[1][0] + hm[1][1] * hm[1][1] + hm[1][2] * hm[1][2] + + hm[2][0] * hm[2][0] + hm[2][1] * hm[2][1] + hm[2][2] * hm[2][2]); + float hessianBound = min(CULTMATH_SNOISE_HESSIAN, frobenius + hessianSlack + CULTMATH_SNOISE_THIRD * nu * halfLength); + curvature += weight * factor * factor * hessianBound; + if (ridged) + { + vec3 w = scale * mv; + float reach = hessianBound * nu * halfLength; + float gl = min(CULTMATH_SNOISE_LIPSCHITZ, length(g) + gradientSlack + reach); + float travel = (abs(dot(g, w)) + gradientSlack * length(w)) * halfLength + (hessianBound * nu * length(w) + gl * factor * path.acceleration) * (0.5 * halfLength * halfLength) + 2.0 * valueSlack; + fold += 2.0 * weight * max(travel - abs(n.w), 0.0); + } + + scale *= lacunarity; + amplitude *= gain; + } + + SpanNoise result; + result.field = vec4(gradient, value); + result.curvature = curvature; + result.fold = fold; + return result; +} + +SpanNoise cultmath_span_fbm(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(false, path, stage, octaves, lacunarity, gain); +} + +SpanNoise cultmath_span_ridged(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(true, path, stage, octaves, lacunarity, gain); +} + float cultmath_value_noise(vec2 position) { vec2 cell = floor(position); diff --git a/packages/cultmath/shaders/CultMath.hlsl b/packages/cultmath/shaders/CultMath.hlsl index 8379b5f1..b3e8f915 100644 --- a/packages/cultmath/shaders/CultMath.hlsl +++ b/packages/cultmath/shaders/CultMath.hlsl @@ -302,10 +302,10 @@ float3x3 cultmath_snoise_hessian(float3 value) // The integral over [t0, t1] of clamp(a + b t, lo, hi); see the C# math.integral_clamp_linear comment. float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1) { - if (b == 0.0) - return clamp(a, lo, hi) * (t1 - t0); - lo = min(lo, hi); + if (b == 0.0) + return t1 != t0 ? clamp(a, lo, hi) * (t1 - t0) : 0.0; + float direction = t1 < t0 ? -1.0 : 1.0; float start = min(t0, t1); float stop = max(t0, t1); @@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float float leave = clamp(max(atLo, atHi), start, stop); float before = b > 0.0 ? lo : hi; float after = b > 0.0 ? hi : lo; - float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave); + float held = enter > start ? before * (enter - start) : 0.0; + float kept = stop > leave ? after * (stop - leave) : 0.0; + float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept; return direction * total; } @@ -603,6 +605,119 @@ CultCellular cultmath_cellular(float3 p) #include "CultMath.Cycles.hlsl" #include "CultMath.Affine.hlsl" +// Span stages (invariant 8): the SpanPath, SpanStage and SpanNoise of math.Span.cs, field for field in +// C#'s order, and cultmath_span_stage, its octave stage, statement for statement so the text is a bit-for-bit +// mirror. They follow the includes because the bounds read CULTMATH_SNOISE_LIPSCHITZ, _HESSIAN and _THIRD. +// A shader caller builds a SpanStage itself (SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)) +// is the identity of math.SpanStage.Identity, which has no shader form); see math.span_stage's comments for +// the span, the transform, the bounds and the allowances. +struct SpanPath +{ + float3 midpoint; + float3 velocity; + float speed; + float acceleration; + float halfLength; +}; + +struct SpanStage +{ + float frequency; + float stretch; + float3 axis; + float3 shift; + float3 offset; + float3 octaveStep; +}; + +struct SpanNoise +{ + float4 field; + float curvature; + float fold; +}; + +// The octave stage behind cultmath_span_fbm and cultmath_span_ridged, as math.span_stage is behind span_fbm +// and span_ridged. The Frobenius norm of the Hessian is written out in C#'s order (GLSL has no _mRC member +// access); the C# local named step is octaveShift here (it would shadow GLSL's step()). +SpanNoise cultmath_span_stage(bool ridged, SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + float3 q = path.midpoint; + float halfLength = path.halfLength; + octaves = clamp(octaves, 0, 16); + bool stretched = dot(stage.axis, stage.axis) > 0.0 && stage.stretch != 1.0; + float3 direction = stretched ? normalize(stage.axis) : float3(0.0, 0.0, 0.0); + float squeeze = stretched ? 1.0 / stage.stretch - 1.0 : 0.0; + float norm = abs(stage.frequency) * (stretched ? max(1.0, 1.0 / stage.stretch) : 1.0); + float3 mq = stretched ? stage.frequency * (q + squeeze * dot(direction, q) * direction) : stage.frequency * q; + float3 mv = stretched ? stage.frequency * (path.velocity + squeeze * dot(direction, path.velocity) * direction) : stage.frequency * path.velocity; + float amplitude = 1.0; + float scale = 1.0; + float value = 0.0; + float3 gradient = float3(0.0, 0.0, 0.0); + float curvature = 0.0; + float fold = 0.0; + for (int i = 0; i < octaves; i++) + { + float3 octaveShift = (float)i * stage.octaveStep; + float3 x = scale * (mq + stage.shift) + (stage.offset + octaveShift); + float4 n = cultmath_snoise_grad(x); + float3 g = float3(n.x, n.y, n.z); + float3 mg = stretched ? stage.frequency * (g + squeeze * dot(direction, g) * direction) : stage.frequency * g; + if (ridged) + { + float s = sign(n.w); + value += amplitude * (1.0 - abs(n.w)); + gradient += -amplitude * scale * s * mg; + } + else + { + value += amplitude * n.w; + gradient += amplitude * scale * mg; + } + + float weight = abs(amplitude); + float factor = abs(scale) * norm; + float nu = factor * path.speed; + float rounding = 9.5367431640625e-7 * (abs(scale) * (cultmath_csum(abs(mq)) + cultmath_csum(abs(stage.shift))) + cultmath_csum(abs(stage.offset)) + cultmath_csum(abs(octaveShift)) + 1.7320508 * nu * halfLength); + float valueSlack = 6.103515625e-5 + CULTMATH_SNOISE_LIPSCHITZ * rounding; + float gradientSlack = 2.44140625e-4 + CULTMATH_SNOISE_HESSIAN * rounding; + float hessianSlack = 9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding; + float3x3 hm = cultmath_snoise_hessian(x); + float frobenius = sqrt(hm[0][0] * hm[0][0] + hm[0][1] * hm[0][1] + hm[0][2] * hm[0][2] + hm[1][0] * hm[1][0] + hm[1][1] * hm[1][1] + hm[1][2] * hm[1][2] + + hm[2][0] * hm[2][0] + hm[2][1] * hm[2][1] + hm[2][2] * hm[2][2]); + float hessianBound = min(CULTMATH_SNOISE_HESSIAN, frobenius + hessianSlack + CULTMATH_SNOISE_THIRD * nu * halfLength); + curvature += weight * factor * factor * hessianBound; + if (ridged) + { + float3 w = scale * mv; + float reach = hessianBound * nu * halfLength; + float gl = min(CULTMATH_SNOISE_LIPSCHITZ, length(g) + gradientSlack + reach); + float travel = (abs(dot(g, w)) + gradientSlack * length(w)) * halfLength + (hessianBound * nu * length(w) + gl * factor * path.acceleration) * (0.5 * halfLength * halfLength) + 2.0 * valueSlack; + fold += 2.0 * weight * max(travel - abs(n.w), 0.0); + } + + scale *= lacunarity; + amplitude *= gain; + } + + SpanNoise result; + result.field = float4(gradient, value); + result.curvature = curvature; + result.fold = fold; + return result; +} + +SpanNoise cultmath_span_fbm(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(false, path, stage, octaves, lacunarity, gain); +} + +SpanNoise cultmath_span_ridged(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(true, path, stage, octaves, lacunarity, gain); +} + float cultmath_value_noise(float2 position) { float2 cell = floor(position); diff --git a/packages/cultmath/src/CultMath/SpanNoise.cs b/packages/cultmath/src/CultMath/SpanNoise.cs new file mode 100644 index 00000000..dcdf8385 --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanNoise.cs @@ -0,0 +1,29 @@ +namespace CultMath; + +/// +/// The result of and (invariant 8's one struct +/// return shape): an octave stage's value and gradient at the midpoint of a span, with the bounds that +/// hold over the whole span. +/// +public struct SpanNoise +{ + /// (∇, value): the stage's gradient in the span's space and its value, in the float4(gradient.xyz, value.w) layout of a bare value-and-gradient return. + public float4 field; + + /// An upper bound, at every point the span can reach, on the operator norm of the field's Hessian in the span's space. + public float curvature; + + /// + /// An upper bound on how far a ridged sum can leave its midpoint branch over the span (each octave's + /// 1 - |n| against the same octave continued with the midpoint's sign of n); 0 for + /// and for any octave that cannot cross zero over the span. + /// + public float fold; + + public SpanNoise(float4 field, float curvature, float fold) + { + this.field = field; + this.curvature = curvature; + this.fold = fold; + } +} diff --git a/packages/cultmath/src/CultMath/SpanNoise.cs.meta b/packages/cultmath/src/CultMath/SpanNoise.cs.meta new file mode 100644 index 00000000..d838deb2 --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanNoise.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: fe0da4c343e24c3781b076943f08ddbc diff --git a/packages/cultmath/src/CultMath/SpanPath.cs b/packages/cultmath/src/CultMath/SpanPath.cs new file mode 100644 index 00000000..cc6524d9 --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanPath.cs @@ -0,0 +1,26 @@ +namespace CultMath; + +/// +/// The span and bound over: the path q(t), +/// |t| <= , through at t = 0 with there, +/// |q'| <= and |q''| <= along it. speed is at least +/// |velocity|; speed, acceleration and halfLength are non-negative. Name every field: the three floats +/// have no order a caller could mistake for a convention. +/// +public struct SpanPath +{ + /// q(0), the point the field and its gradient are evaluated at. + public float3 midpoint; + + /// q'(0). + public float3 velocity; + + /// An upper bound on |q'(t)| over the span; at least |velocity|. + public float speed; + + /// An upper bound on |q''(t)| over the span. + public float acceleration; + + /// The span covers |t| <= halfLength; 0 is the midpoint alone. + public float halfLength; +} diff --git a/packages/cultmath/src/CultMath/SpanPath.cs.meta b/packages/cultmath/src/CultMath/SpanPath.cs.meta new file mode 100644 index 00000000..3c58b0e9 --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanPath.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3a8c1d52e7b94f06a1d5c2b9e04f7a31 diff --git a/packages/cultmath/src/CultMath/SpanStage.cs b/packages/cultmath/src/CultMath/SpanStage.cs new file mode 100644 index 00000000..faeccd3e --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanStage.cs @@ -0,0 +1,33 @@ +namespace CultMath; + +/// +/// The transform of one octave stage of a warp chain, as and +/// read it. Octave o samples snoise at x_o = f_o (M q + shift) + offset + +/// o octaveStep, with f_o = lacunarity^o and M = (I + (1/ - 1) a a^T), +/// a = normalize(); a zero axis, or a stretch of 1, means no stretch. stretch must be +/// positive and finite, and nothing guards it. default(SpanStage) has frequency 0, which samples +/// one point: start from . +/// +public struct SpanStage +{ + /// The stage whose span_fbm/span_ridged field equals fbm_grad/ridged_grad: frequency 1, stretch 1, every vector zero. + public static readonly SpanStage Identity = new SpanStage { frequency = 1.0f, stretch = 1.0f }; + + /// The scale of M; its sign is kept. + public float frequency; + + /// The factor the axis direction is stretched by (M scales it by 1/stretch); 1 is none. + public float stretch; + + /// The direction stretch applies along; any length, zero for none. + public float3 axis; + + /// Added to M q before the per-octave scaling. + public float3 shift; + + /// Added after the per-octave scaling. + public float3 offset; + + /// Added to the octave coordinate once per octave index: o octaveStep. + public float3 octaveStep; +} diff --git a/packages/cultmath/src/CultMath/SpanStage.cs.meta b/packages/cultmath/src/CultMath/SpanStage.cs.meta new file mode 100644 index 00000000..7edaee93 --- /dev/null +++ b/packages/cultmath/src/CultMath/SpanStage.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9d46b0f27c1e4a85b3e8f1a60c2d5b97 diff --git a/packages/cultmath/src/CultMath/math.Span.cs b/packages/cultmath/src/CultMath/math.Span.cs index 364bd04b..3e898818 100644 --- a/packages/cultmath/src/CultMath/math.Span.cs +++ b/packages/cultmath/src/CultMath/math.Span.cs @@ -5,15 +5,18 @@ public static partial class math // An upper bound on the operator norm of the third-derivative tensor of snoise(float3): // max over unit u of |D^3 snoise(p)[u, u, u]|, the next order after SNOISE_LIPSCHITZ (gradient) // and SNOISE_HESSIAN (curvature). Provenance: NoiseBoundTests.MeasureThird (slow, explicit) takes - // the largest such norm over 1e6 seeded points of [-256, 256]^3 (central differences of - // snoise_hessian at h = 1e-3, then an ascent over unit vectors for the tensor's operator norm), - // refines the 1e4 largest by ascent over the point, and multiplies the maximum by 1.10; it prints - // the witness start. NoiseBoundTests.ThirdConstantPinsSampled pins the value against a fast - // re-measure and the ascent from that witness. Measured on the tied kernel (snoise_grad's order), - // not on any pre-tie splice. Measured 488.1047 (a dense sweep of 20,000 directions at the witness - // found 99.9998% of the tensor norm there). Empirical with a margin, not a proof. A change to the snoise kernel - // must re-run MeasureThird and re-pin it. - public const float SNOISE_THIRD = 536.9152f; + // the largest such norm over 1e6 seeded points of [-256, 256]^3 of the closed-form tensor (snoise_hessian's + // per-corner derivation differentiated once more, in double, with the kernel's lattice, tie order and + // hash; NoiseBoundTests.ThirdTensorMatchesDifferencesOfTheHessian pins it against differences of the + // double Hessian), then an ascent over unit vectors for the tensor's operator norm, refines the 1e4 + // largest by ascent over the point, and multiplies the maximum by 1.10; it prints the witness start. + // NoiseBoundTests.ThirdConstantPinsSampled pins the value against a fast re-measure and the ascent + // from that witness. Measured on the tied kernel (snoise_grad's order), not on any pre-tie splice: + // refined maximum 487.0917, a dense sweep of 20,000 directions at the witness's ascent end found + // 99.9987% of the tensor norm there. The inner maximisation is a local ascent and the outer a sampled + // search refined by ascent: empirical with a margin, not a proof. A change to the snoise kernel must + // re-run MeasureThird and re-pin it. + public const float SNOISE_THIRD = 535.8009f; // Analytic Hessian of snoise(float3): the symmetric matrix of second partial derivatives, same // lattice, hash and tie order as snoise_grad, whose kernel it differentiates once more. Every local @@ -100,14 +103,17 @@ public static partial class math // at the bound the line starts beyond, the stretch on the ramp (a trapezoid: its length times the // line's value at the stretch's midpoint), and the stretch held at the far bound. b = 0 is the // constant clamp(a, lo, hi); t1 < t0 is the negative of the integral over [t1, t0]; lo above hi - // clamps to hi everywhere, as clamp does. A crossing that overflows (b tiny) lies past the span and - // cuts nothing. + // clamps to hi everywhere, as clamp does, and is normalised (lo = min(lo, hi)) before anything reads it, + // so no clamp ever sees lo above hi (GLSL leaves that undefined). A crossing that overflows (b tiny) lies past the span and + // cuts nothing. A held stretch contributes only when its length is positive, so an infinite bound + // (relu has hi = +inf) times a zero length never forms: the integral of a ramp clamped on one side + // only is finite. public static float integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1) { - if (b == 0.0f) - return clamp(a, lo, hi) * (t1 - t0); - lo = min(lo, hi); + if (b == 0.0f) + return t1 != t0 ? clamp(a, lo, hi) * (t1 - t0) : 0.0f; + var direction = t1 < t0 ? -1.0f : 1.0f; var start = min(t0, t1); var stop = max(t0, t1); @@ -117,7 +123,139 @@ public static partial class math var leave = clamp(max(atLo, atHi), start, stop); var before = b > 0.0f ? lo : hi; var after = b > 0.0f ? hi : lo; - var total = before * (enter - start) + (a + b * (0.5f * (enter + leave))) * (leave - enter) + after * (stop - leave); + var held = enter > start ? before * (enter - start) : 0.0f; + var kept = stop > leave ? after * (stop - leave) : 0.0f; + var total = held + (a + b * (0.5f * (enter + leave))) * (leave - enter) + kept; return direction * total; } + + private static float span_frobenius(float3x3 m) => + sqrt(m._m00 * m._m00 + m._m01 * m._m01 + m._m02 * m._m02 + m._m10 * m._m10 + m._m11 * m._m11 + m._m12 * m._m12 + + m._m20 * m._m20 + m._m21 * m._m21 + m._m22 * m._m22); + + // Plain fbm of snoise over a span: the octave stage of a warp chain, its value and gradient at the + // span's midpoint and the bounds that hold over the whole span. See span_stage for the span, the + // transform and the bound rules. At SpanStage.Identity the field equals fbm_grad(path.midpoint, + // octaves, lacunarity, gain) bit for bit. + public static SpanNoise span_fbm(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) => + span_stage(false, path, stage, octaves, lacunarity, gain); + + // Ridged fbm over a span: octave o contributes gain^o (1 - |n_o|), ridged_grad's convention. fold + // bounds how far the sum can leave the branch it is on at the midpoint. At SpanStage.Identity the field + // equals ridged_grad bit for bit. + // + // The Span Lab's stage is 1 - 2|n| per octave. With G the sum of gain^o over the octaves (signed: a + // negative gain gives its alternating sum), the lab's value is 2 value - G, and its gradient, + // curvature and fold are twice this one's. A consumer in the lab form applies that mapping and does + // not derive its own bounds. + public static SpanNoise span_ridged(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) => + span_stage(true, path, stage, octaves, lacunarity, gain); + + // The span is SpanPath: q(t), |t| <= halfLength, through midpoint with velocity at t = 0, |q'| <= speed + // and |q''| <= acceleration along it (speed at least |velocity|; all three and halfLength non-negative). + // The transform is SpanStage: octave o samples snoise_grad at x_o(q) = f_o (M q + shift) + offset + + // o octaveStep with f_o = lacunarity^o, amplitude gain^o and M = frequency (I + (1/stretch - 1) a a^T), + // a = normalize(axis); a zero axis, or stretch 1, means no stretch. stretch must be positive and + // finite, and nothing guards it, as nothing guards lacunarity. octaves clamps to [0, 16] as fbm_grad's + // does. The field is the octave sum in q: gradient f_o M grad n_o (M is symmetric), the same octave + // loop as fbm_grad and ridged_grad. + // + // Bounds, per octave, from SNOISE_LIPSCHITZ (L), SNOISE_HESSIAN (H) and SNOISE_THIRD (T) only. With + // |M| = |frequency| max(1, 1/stretch) (1 without stretch) and nu = f_o |M| speed, the noise-space speed: + // * |x(t) - x_mid| <= nu halfLength, so the Hessian of n at x(t) has operator norm at most + // Hl = min(H, ||hessian(x_mid)||_F + e_h + T nu halfLength) (the Hessian is T-Lipschitz in operator + // norm, and the Frobenius norm bounds the operator norm), and |grad n(x(t))| at most + // Gl = min(L, |grad n_mid| + e_g + Hl nu halfLength). + // * curvature = sum gain^o f_o^2 |M|^2 Hl: the Hessian in q of n(x_o(q)) is f_o^2 M H M. + // * fold, ridged only. Over the span the octave's value departs from its midpoint branch + // gain^o (1 - s n) (s the sign of n at the midpoint) only where n has changed sign, and there + // |n(t)| <= V - |n_mid|, V bounding |n(t) - n_mid|. Writing n(t) - n_mid as the integral of + // grad n . x', x' = w + rho with w = f_o M velocity and |rho| <= f_o |M| acceleration |t|, and + // grad n = grad n_mid + delta with |delta| <= Hl nu |t|: + // V = (|grad n_mid . w| + e_g |w|) halfLength + (Hl nu |w| + Gl f_o |M| acceleration) halfLength^2 / 2 + // + 2 e_n, + // and the octave's fold is 2 gain^o max(V - |n_mid|, 0), zero when it cannot reach zero. + // + // The float32 snoise, snoise_grad and snoise_hessian are not the exact functions, and the field a + // consumer reads is the float one, so what is read at the midpoint carries an allowance, by the + // derivation af_snoise's comment gives (math.Affine.cs): the skew and the corner offsets round the + // coordinate by about 2u of the magnitudes it is formed from, which moves a result by its derivative + // times that, and the caller's scaling of the point and a GPU's evaluation take 2u to 16u = 2^-20. + // S_o is the sum of those magnitudes, |f_o| (|M q|_1 + |shift|_1) + |offset|_1 + |o octaveStep|_1, plus + // the span's reach sqrt(3) nu halfLength (the coordinate of a path point). The allowances are the + // value's e_n = 2^-14 + L 2^-20 S_o, the gradient length's e_g = 2^-12 + H 2^-20 S_o (H is the + // gradient's own derivative's pin) and the Hessian's Frobenius norm's e_h = 2^-10 + T 2^-20 S_o (T is + // the Hessian's derivative's pin): af_snoise's absolute terms for the first two, and 2^-10 for the + // Hessian's, which the kernel's rounding at small coordinates (2.3e-5 absolute, measured against a + // double kernel) sits well under. Two samples of that probe (6000 points per band, |x| from 2^-2 to + // 2^23) observed the error's maxima, as fractions of e_n, e_g and e_h: 0.057, 0.046 and 0.075 in the + // cut's own sample (seed not recorded; the 0.075 is against T 2^-20 |x|_1 alone, at the smallest + // band), and 0.064, 0.0485 and 0.057 in an audit's (seed 0x501). They are samples, not bounds: a new + // seed moves them. What they say is that the allowances enclose the error with a margin of about 15. + // No new pin enters. + // + // Domain: no coordinate range is checked, clamped or refused. The allowances grow with S_o, so + // curvature saturates at H (true everywhere) by S_o of about 1.1e5, and fold turns positive for every + // octave whose V + 2 e_n reaches |n_mid|: the bounds stay sound at every magnitude and stop being + // informative where float32 noise is (design.md, Precision domain). + private static SpanNoise span_stage(bool ridged, SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) + { + var q = path.midpoint; + var halfLength = path.halfLength; + octaves = clamp(octaves, 0, 16); + var stretched = dot(stage.axis, stage.axis) > 0.0f && stage.stretch != 1.0f; + var direction = stretched ? normalize(stage.axis) : new float3(0.0f, 0.0f, 0.0f); + var squeeze = stretched ? 1.0f / stage.stretch - 1.0f : 0.0f; + var norm = abs(stage.frequency) * (stretched ? max(1.0f, 1.0f / stage.stretch) : 1.0f); + var mq = stretched ? stage.frequency * (q + squeeze * dot(direction, q) * direction) : stage.frequency * q; + var mv = stretched ? stage.frequency * (path.velocity + squeeze * dot(direction, path.velocity) * direction) : stage.frequency * path.velocity; + var amplitude = 1.0f; + var scale = 1.0f; + var value = 0.0f; + var gradient = new float3(0.0f, 0.0f, 0.0f); + var curvature = 0.0f; + var fold = 0.0f; + for (var i = 0; i < octaves; i++) + { + var step = (float)i * stage.octaveStep; + var x = scale * (mq + stage.shift) + (stage.offset + step); + var n = snoise_grad(x); + var g = new float3(n.x, n.y, n.z); + var mg = stretched ? stage.frequency * (g + squeeze * dot(direction, g) * direction) : stage.frequency * g; + if (ridged) + { + var s = (float)sign(n.w); + value += amplitude * (1.0f - abs(n.w)); + gradient += -amplitude * scale * s * mg; + } + else + { + value += amplitude * n.w; + gradient += amplitude * scale * mg; + } + + var weight = abs(amplitude); + var factor = abs(scale) * norm; + var nu = factor * path.speed; + var rounding = 9.5367431640625e-7f * (abs(scale) * (csum(abs(mq)) + csum(abs(stage.shift))) + csum(abs(stage.offset)) + csum(abs(step)) + 1.7320508f * nu * halfLength); + var valueSlack = 6.103515625e-5f + SNOISE_LIPSCHITZ * rounding; + var gradientSlack = 2.44140625e-4f + SNOISE_HESSIAN * rounding; + var hessianSlack = 9.765625e-4f + SNOISE_THIRD * rounding; + var local = min(SNOISE_HESSIAN, span_frobenius(snoise_hessian(x)) + hessianSlack + SNOISE_THIRD * nu * halfLength); + curvature += weight * factor * factor * local; + if (ridged) + { + var w = scale * mv; + var reach = local * nu * halfLength; + var gl = min(SNOISE_LIPSCHITZ, length(g) + gradientSlack + reach); + var travel = (abs(dot(g, w)) + gradientSlack * length(w)) * halfLength + (local * nu * length(w) + gl * factor * path.acceleration) * (0.5f * halfLength * halfLength) + 2.0f * valueSlack; + fold += 2.0f * weight * max(travel - abs(n.w), 0.0f); + } + + scale *= lacunarity; + amplitude *= gain; + } + + return new SpanNoise(new float4(gradient, value), curvature, fold); + } } diff --git a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs index 14ea07c0..f643d2c3 100644 --- a/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/GlslMirrorTests.cs @@ -45,6 +45,54 @@ public sealed class GlslMirrorTests } } + // Counting stand-ins for the mirror's clamp, one per overload, declared in the mirror's class so they + // hide math.clamp for every function in it. Each counts a call whose minimum exceeds its maximum in any + // component and forwards to math.clamp. (The int vector overloads only forward: no mirror function + // clamps integer vectors with swapped bounds, and counting them would be unreachable.) + private const string CountingClampShadows = @" +public static int ClampMinAboveMax; +static float clamp(float x, float a, float b) { if (a > b) ClampMinAboveMax++; return math.clamp(x, a, b); } +static float2 clamp(float2 x, float2 a, float2 b) { if (a.x > b.x || a.y > b.y) ClampMinAboveMax++; return math.clamp(x, a, b); } +static float3 clamp(float3 x, float3 a, float3 b) { if (a.x > b.x || a.y > b.y || a.z > b.z) ClampMinAboveMax++; return math.clamp(x, a, b); } +static float4 clamp(float4 x, float4 a, float4 b) { if (a.x > b.x || a.y > b.y || a.z > b.z || a.w > b.w) ClampMinAboveMax++; return math.clamp(x, a, b); } +static int clamp(int x, int a, int b) { if (a > b) ClampMinAboveMax++; return math.clamp(x, a, b); } +static int2 clamp(int2 x, int2 a, int2 b) { if (a.x > b.x || a.y > b.y) ClampMinAboveMax++; return math.clamp(x, a, b); } +static int3 clamp(int3 x, int3 a, int3 b) { if (a.x > b.x || a.y > b.y || a.z > b.z) ClampMinAboveMax++; return math.clamp(x, a, b); } +static int4 clamp(int4 x, int4 a, int4 b) { if (a.x > b.x || a.y > b.y || a.z > b.z || a.w > b.w) ClampMinAboveMax++; return math.clamp(x, a, b); } +"; + + /// + /// The lowering keeps HLSL's statement order (CommittedGlslEqualsLowering pins the GLSL as that + /// lowering), and GLSL leaves clamp(x, minVal, maxVal) undefined for minVal above maxVal, so no clamp + /// reached by cultmath_integral_clamp_linear may see lo above hi, on any path and for any spelling of the + /// normalisation. The mirror is compiled with counting clamps and the function is run over the edge grid + /// (lo above hi, infinite bounds, zero-length spans, b = 0 and b != 0): the count of such clamp calls + /// stays 0 after every call. The cases are HlslSourceCompatibilityTests.IntegralClampLinearCases: the grid fixes + /// a in {-2.5, 0, 1.5}, so a normalisation keyed on a past 1.75 never clamps wrongly there; the committed + /// fixture's random cases reach it. + /// + [Fact] + public void IntegralClampLinearNeverClampsWithMinAboveMax() + { + var (assembly, errors) = HlslSourceCompatibilityTests.CompileShaderMirror(CountingClampShadows); + Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); + var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!; + var shader = Activator.CreateInstance(shaderType); + var count = shaderType.GetField("ClampMinAboveMax")!; + var function = shaderType.GetMethod("cultmath_integral_clamp_linear", BindingFlags.Instance | BindingFlags.NonPublic)!; + + var bad = new List(); + foreach (var c in HlslSourceCompatibilityTests.IntegralClampLinearCases) + { + count.SetValue(null, 0); + function.Invoke(shader, c.Cast().ToArray()); + if ((int)count.GetValue(null)! > 0) + bad.Add($"({string.Join(", ", c)})"); + } + + Assert.True(bad.Count == 0, $"{bad.Count} cases clamp with the minimum above the maximum:{Environment.NewLine}{string.Join(Environment.NewLine, bad.Take(12))}"); + } + // Identifier boundaries: cultmath_lerp is a CultMath function, lerp( is HLSL. private static readonly Regex HlslOnly = new( @"\b(float|u?int|bool)[234]\b|\bfloat3x3\b|\blerp\(|\bfrac\(|\bsaturate\(|\brsqrt\(|\basuint\(|\basfloat\(|\batan2\(|static const|\((float|int|uint)\)|Texture2D|SamplerState|#include|precision (high|medium|low)p"); @@ -638,8 +686,68 @@ public sealed class GlslMirrorTests var lo = Uniform(r, -2.0f, 0.5f); return new object[] { Uniform(r, -4.0f, 4.0f), r.Next(8) == 0 ? 0.0f : Uniform(r, -4.0f, 4.0f), lo, lo + Uniform(r, 0.0f, 3.0f), Uniform(r, -3.0f, 3.0f), Uniform(r, -3.0f, 3.0f) }; }), + // The edge cases the family above never draws: lo above hi (b = 0 and b != 0; the lowering must + // normalise before any clamp sees it) and infinite bounds (hi = +inf, lo = -inf, both). + new Family("float cultmath_integral_clamp_linear(float, float, float, float, float, float)", "ulp-bounded, lo above hi and infinite bounds", + r => + { + var a = Uniform(r, -4.0f, 4.0f); + var slope = Uniform(r, -4.0f, 4.0f); + var (t0, t1) = (Uniform(r, -3.0f, 3.0f), Uniform(r, -3.0f, 3.0f)); + return r.Next(5) switch + { + 0 => new object[] { a, 0.0f, Uniform(r, 0.5f, 2.0f), Uniform(r, -2.0f, 0.4f), t0, t1 }, + 1 => new object[] { a, slope, Uniform(r, 0.5f, 2.0f), Uniform(r, -2.0f, 0.4f), t0, t1 }, + 2 => new object[] { a, slope, Uniform(r, -2.0f, 1.0f), float.PositiveInfinity, t0, t1 }, + 3 => new object[] { a, slope, float.NegativeInfinity, Uniform(r, -1.0f, 2.0f), t0, t1 }, + _ => new object[] { a, slope, float.NegativeInfinity, float.PositiveInfinity, t0, t1 }, + }; + }), + // Zero-length spans (t1 = t0 three times in four) with a flat or sloped line, and infinite clamp bounds + // on either side or both: an infinite bound times a zero length must give +0, and a flat line clamped + // to an infinite bound over a nonzero span is infinite, never NaN. + new Family("float cultmath_integral_clamp_linear(float, float, float, float, float, float)", "ulp-bounded, zero-length spans and flat infinite clamps", + r => + { + float Line() => r.Next(3) switch { 0 => 0.0f, 1 => -0.0f, _ => Uniform(r, -4.0f, 4.0f) }; + float Bound() => r.Next(3) switch { 0 => float.NegativeInfinity, 1 => float.PositiveInfinity, _ => Uniform(r, -2.0f, 2.0f) }; + var (a, b, lo, hi) = (Line(), Line(), Bound(), Bound()); + var t0 = Uniform(r, -3.0f, 3.0f); + var t1 = r.Next(4) != 0 ? t0 : Uniform(r, -3.0f, 3.0f); + return new object[] { a, b, lo, hi, t0, t1 }; + }), + // The span stages over their struct arguments: realistic stages and spans (SpanStageTests' own draw), the + // midpoint a Point3, one span in four a point. + new Family("SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float)", "ulp-bounded", SpanArguments), + new Family("SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float)", "ulp-bounded", SpanArguments), }).ToArray(); + private static object[] SpanArguments(System.Random r) + { + var stage = SpanStageTests.Stage.Draw(r); + var halfLength = r.Next(4) == 0 ? 0.0f : new[] { 0.02f, 0.1f, 0.5f }[r.Next(3)]; + var span = SpanStageTests.Span.Draw(r, Point3(r), halfLength, Uniform(r, 0.0f, 2.0f)); + return new object[] { span.Path, stage.S, stage.Octaves, stage.Lacunarity, stage.Gain }; + } + + /// + /// The committed fixture's cases of the three cultmath_integral_clamp_linear families, decoded into the + /// C# arguments (a, b, lo, hi, t0, t1): the random cases the clamp-normalisation tests also run. + /// + internal static IReadOnlyList IntegralClampLinearFixtureCases + { + get + { + const string clamp = "float cultmath_integral_clamp_linear(float, float, float, float, float, float)"; + var method = Resolve(clamp); + return JsonNode.Parse(File.ReadAllText(FixturePath))!["functions"]!.AsArray() + .Where(family => (string)family!["glsl"]! == clamp) + .SelectMany(family => family!["cases"]!.AsArray()) + .Select(entry => Arguments(method, ((string)entry!).Split(" -> ")[0]).Select(a => (float)a).ToArray()) + .ToArray(); + } + } + // The fixture text, each case's arguments given by family and point index. private static string GoldenFixture(Func arguments) { @@ -682,6 +790,7 @@ public sealed class GlslMirrorTests { ["float"] = typeof(float), ["int"] = typeof(int), ["vec2"] = typeof(float2), ["vec3"] = typeof(float3), ["vec4"] = typeof(float4), ["ivec3"] = typeof(int3), ["ivec4"] = typeof(int4), + ["SpanPath"] = typeof(SpanPath), ["SpanStage"] = typeof(SpanStage), }; // The C# math method a GLSL signature names: cultmath_ dropped, parameter types mapped. diff --git a/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs b/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs index 4ca598ad..1e01ba43 100644 --- a/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/HlslSourceCompatibilityTests.cs @@ -39,21 +39,65 @@ public sealed class HlslSourceCompatibilityTests Assert.NotNull(ShaderFunction(shader, "cultmath_af_snoise", typeof(float3), typeof(float3), typeof(float))); Assert.NotNull(ShaderFunction(shader, "cultmath_af_fbm", typeof(float3), typeof(float3), typeof(float), typeof(int), typeof(float), typeof(float))); Assert.NotNull(ShaderFunction(shader, "cultmath_af_frustum_ball", typeof(float2), typeof(float), typeof(float), typeof(float), typeof(float))); + var (spanPath, spanStage) = SpanTypes(shader); + Assert.NotNull(ShaderFunction(shader, "cultmath_span_fbm", spanPath, spanStage, typeof(int), typeof(float), typeof(float))); + Assert.NotNull(ShaderFunction(shader, "cultmath_span_ridged", spanPath, spanStage, typeof(int), typeof(float), typeof(float))); + Assert.NotNull(ShaderFunction(shader, "cultmath_span_stage", typeof(bool), spanPath, spanStage, typeof(int), typeof(float), typeof(float))); } + // The mirror's struct types are private nested types of HlslShader (a struct without a modifier). + private static (Type Path, Type Stage) SpanTypes(Type shader) => + (shader.GetNestedType("SpanPath", BindingFlags.Public | BindingFlags.NonPublic)!, shader.GetNestedType("SpanStage", BindingFlags.Public | BindingFlags.NonPublic)!); + // HLSL functions carry no access modifier, so they compile as private instance methods. private static MethodInfo? ShaderFunction(Type shader, string name, params Type[] parameters) => shader.GetMethod(name, BindingFlags.Instance | BindingFlags.NonPublic, null, parameters, null); - // Mirror internals with no public C# math counterpart. Any other cultmath_* function must match one. - private static readonly string[] MirrorOnly = { "cultmath_snoise_mod289", "cultmath_snoise_permute" }; - - private static readonly float[] Specials = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -2.75f, 3.0f, 1.0e-7f, 1.0e4f, -1.0e4f, 1.0e7f, float.NaN }; + // Shader constants with no C# twin of the same name (empty when every constant has one). + private static readonly string[] ConstantsWithoutTwin = Array.Empty(); /// - /// Every mirror function with a C# math counterpart returns the same bits (any NaN equals any NaN; + /// Every static const of the compiled HLSL mirror (a static literal CULTMATH_* field, whatever its access) is the + /// same float32, bit for bit, as the math constant of the same name without the prefix (private ones + /// included), so a pin edited in one runtime and not the other fails here whichever constant it is. + /// A constant with neither a twin nor a listing in ConstantsWithoutTwin fails too. + /// + [Fact] + public void EveryShaderConstantEqualsItsCSharpTwin() + { + var (assembly, errors) = CompileShaderMirror(); + Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); + var shader = assembly!.GetType("CultMathHlsl.HlslShader")!; + var constants = shader.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) + .Where(f => f.IsLiteral && f.Name.StartsWith("CULTMATH_", StringComparison.Ordinal)) + .ToArray(); + Assert.True(constants.Length >= 10, $"only {constants.Length} shader constants were found by reflection"); + foreach (var constant in constants) + { + var name = constant.Name["CULTMATH_".Length..]; + var twin = typeof(math).GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (twin is null) + { + Assert.True(ConstantsWithoutTwin.Contains(constant.Name), $"{constant.Name} has no math.{name} and is not listed in ConstantsWithoutTwin"); + continue; + } + + var shaderValue = (float)constant.GetRawConstantValue()!; + var mathValue = (float)twin.GetValue(null)!; + Assert.True(BitConverter.SingleToInt32Bits(shaderValue) == BitConverter.SingleToInt32Bits(mathValue), + $"{constant.Name} is {shaderValue:R} in the shader and math.{name} is {mathValue:R}"); + } + } + + // Special values every mirror argument takes: zeros, ties, negatives, large values, NaN and both infinities. + private static readonly float[] Specials = { 0.0f, -0.0f, 1.0f, -1.0f, 0.5f, -2.75f, 3.0f, 1.0e-7f, 1.0e4f, -1.0e4f, 1.0e7f, float.NaN, float.PositiveInfinity, float.NegativeInfinity }; + + /// + /// Every mirror function matches the math function of the same name, public or private (a function with no + /// twin fails, and nothing is exempt), with a struct parameter mapped by name and fields to the CultMath + /// type of the same name, and returns the same bits (any NaN equals any NaN; /// -0 differs from 0) over random inputs, every special value in every argument (ties, zeros, - /// negatives, NaN, large values, integer lattice points), special pairs, and vectors whose first two + /// negatives, NaN, infinities, large values, integer lattice points), special pairs, and vectors whose first two /// components tie (x0.x == x0.y in snoise). This proves the text of CultMath.hlsl computes the same /// float32 results as C# math on the CPU. It does not prove GPU agreement: driver sin precision alone /// makes cultmath_hash and value noise differ bit for bit on hardware. The mirror's intrinsics are C# @@ -116,20 +160,15 @@ public sealed class HlslSourceCompatibilityTests foreach (var mirror in shaderType.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).Where(m => m.Name.StartsWith("cultmath_"))) { var types = mirror.GetParameters().Select(p => p.ParameterType).ToArray(); - var counterpart = typeof(math).GetMethod(mirror.Name["cultmath_".Length..], BindingFlags.Public | BindingFlags.Static, null, types, null); - if (counterpart is null) - { - Assert.Contains(mirror.Name, MirrorOnly); - continue; - } + var counterpart = Twin(shaderType, mirror.Name["cultmath_".Length..], types); + Assert.True(counterpart is not null, $"{mirror.Name} has no C# math function of the same name and parameter types, public or private"); compared.Add(mirror.Name); foreach (var input in cases) { var slot = 0; - var args = types.Select(t => Scalars.Contains(t) ? Scalar(t, input(slot++)) - : Activator.CreateInstance(t, Components(t).Select(f => Scalar(f.FieldType, input(slot++))).ToArray())!).ToArray(); - var expected = Values(counterpart.Invoke(null, args)!); + var args = types.Select(t => Build(t, () => input(slot++))).ToArray(); + var expected = Values(counterpart!.Invoke(null, args.Select(a => CopyFields(a, TwinType(shaderType, a.GetType())!)).ToArray())!); var actual = Values(mirror.Invoke(shader, args)!); if (!BitwiseEqual(expected, actual)) { @@ -139,7 +178,160 @@ public sealed class HlslSourceCompatibilityTests } Assert.True(mismatches.Count == 0, $"{mismatches.Count} mismatches:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}"); - Assert.Equal(120, compared.Count); + Assert.Equal(125, compared.Count); + } + + /// + /// The 1500 arguments (a, b, lo, hi, t0, t1) at which integral_clamp_linear's zero-length rule, its lo + /// normalisation and its infinite bounds all bite: a in {-2.5, 0, 1.5}, b in {-1.5, -0, 0, 2}, lo in + /// {-inf, -1, 0.5, 3, +inf}, hi in {-inf, -0.5, 1, 4, +inf}, and (t0, t1) in {(0, 0), (1.25, 1.25), (-1, 2), + /// (2, -1), (0, 1e-30)}. One spelling, read by the mirror grid test and by the GLSL clamp shadow test. + /// + internal static IReadOnlyList IntegralClampLinearEdgeGrid { get; } = BuildIntegralClampLinearEdgeGrid(); + + private static List BuildIntegralClampLinearEdgeGrid() + { + var inf = float.PositiveInfinity; + var grid = new List(); + foreach (var a in new[] { -2.5f, 0.0f, 1.5f }) + foreach (var b in new[] { -1.5f, -0.0f, 0.0f, 2.0f }) + foreach (var lo in new[] { -inf, -1.0f, 0.5f, 3.0f, inf }) + foreach (var hi in new[] { -inf, -0.5f, 1.0f, 4.0f, inf }) + foreach (var (t0, t1) in new[] { (0.0f, 0.0f), (1.25f, 1.25f), (-1.0f, 2.0f), (2.0f, -1.0f), (0.0f, 1.0e-30f) }) + grid.Add(new[] { a, b, lo, hi, t0, t1 }); + return grid; + } + + /// + /// The edge grid followed by the committed GLSL fixture's cases of the three integral_clamp_linear families + /// (GlslMirrorTests.IntegralClampLinearFixtureCases, decoded): one list, read by the mirror test and by the + /// clamp-shadow test. The grid fixes a in {-2.5, 0, 1.5}, so a guard keyed on a past 1.75 hides from it + /// (Soul s4); the fixture's random a in [-4, 4] closes that. + /// + internal static IReadOnlyList IntegralClampLinearCases => IntegralClampLinearEdgeGrid.Concat(GlslMirrorTests.IntegralClampLinearFixtureCases).ToArray(); + + /// + /// The compiled mirror's cultmath_integral_clamp_linear returns C#'s bits on every edge case (the grid and + /// the fixture's cases), and C#'s result is never NaN there. The generic case set catches each of the function's guards (the held and + /// kept stretch guards, the flat branch's zero-length guard) only by its rotation: with the kept guard + /// removed it finds 0 mismatches, with the held guard removed 1 once infinities join the specials, and the + /// edge grid finds 246 for either every time. + /// + [Fact] + public void IntegralClampLinearMirrorMatchesCSharpOnTheEdgeCases() + { + var (assembly, errors) = CompileShaderMirror(); + Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); + var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!; + var shader = Activator.CreateInstance(shaderType); + var mirror = ShaderFunction(shaderType, "cultmath_integral_clamp_linear", Enumerable.Repeat(typeof(float), 6).ToArray())!; + + var mismatches = new List(); + var cases = IntegralClampLinearCases; + foreach (var c in cases) + { + var expected = integral_clamp_linear(c[0], c[1], c[2], c[3], c[4], c[5]); + var actual = (float)mirror.Invoke(shader, c.Cast().ToArray())!; + Assert.False(float.IsNaN(expected), $"C# returns NaN at ({string.Join(", ", c)})"); + if (BitConverter.SingleToInt32Bits(expected) != BitConverter.SingleToInt32Bits(actual)) + mismatches.Add($"({string.Join(", ", c)}): C# {expected:R}, mirror {actual:R}"); + } + + Assert.True(mismatches.Count == 0, $"{mismatches.Count} of {cases.Count} cases differ:{Environment.NewLine}{string.Join(Environment.NewLine, mismatches.Take(12))}"); + } + + /// + /// At a SpanStage the shader has no identity constant for, so C#'s identity (copied field by field) is the + /// argument: the mirror's cultmath_span_fbm and cultmath_span_ridged return the field of the mirror's own + /// cultmath_fbm_grad and cultmath_ridged_grad, bit for bit, for 0 to 20 octaves over random and simplex-tie + /// points. The shader-side statement of math.SpanStage.Identity (design decision a: no shader identity). + /// + [Fact] + public void SpanStagesAtIdentityAreFbmAndRidgedInTheMirror() + { + var (assembly, errors) = CompileShaderMirror(); + Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); + var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!; + var shader = Activator.CreateInstance(shaderType); + var (pathType, stageType) = SpanTypes(shaderType); + var spanArguments = new[] { pathType, stageType, typeof(int), typeof(float), typeof(float) }; + var pairs = new[] + { + (Span: ShaderFunction(shaderType, "cultmath_span_fbm", spanArguments)!, Plain: ShaderFunction(shaderType, "cultmath_fbm_grad", typeof(float3), typeof(int), typeof(float), typeof(float))!), + (Span: ShaderFunction(shaderType, "cultmath_span_ridged", spanArguments)!, Plain: ShaderFunction(shaderType, "cultmath_ridged_grad", typeof(float3), typeof(int), typeof(float), typeof(float))!), + }; + var identity = CopyFields(SpanStage.Identity, stageType); + + var random = new System.Random(0x57A6E2); + var compared = 0; + foreach (var p in SpanStageTests.Points(random)) + { + var octaves = random.Next(0, 21); + var lacunarity = 1.4f + 1.2f * random.NextSingle(); + var gain = 0.3f + 0.5f * random.NextSingle(); + var path = CopyFields(new SpanPath { midpoint = p, velocity = new float3(0.6f, 0.0f, 0.8f), speed = 1.0f, acceleration = 0.5f, halfLength = 0.25f }, pathType); + foreach (var (span, plain) in pairs) + { + var field = FieldOf(span.Invoke(shader, new object[] { path, identity, octaves, lacunarity, gain })!, "field"); + var expected = plain.Invoke(shader, new object[] { p, octaves, lacunarity, gain })!; + Assert.True(BitwiseEqual(Values(expected), Values(field)), $"{span.Name} at {p}, {octaves} octaves: {string.Join(" ", Values(field).Select(v => v.Value))} is not {plain.Name} {string.Join(" ", Values(expected).Select(v => v.Value))}"); + compared++; + } + } + + Assert.True(compared >= 460, $"only {compared} identity comparisons ran"); + } + + /// + /// The generic comparison draws every argument from [-100, 100] (and specials), where the octave loop's + /// frequency, stretch and span terms saturate: 0 or 16 octaves, a Hessian bound pinned at its global value. + /// This sweep draws SpanStageTests' realistic stages and spans (4000, one in four with a zero axis, one in + /// four with a point span, halfLength 0), both stages, and the mirror must return C#'s bits for the whole + /// SpanNoise. Each of stretched and unstretched stages, and of positive and zero fold, must be met at least + /// 500 times, or the sweep proves nothing about that branch. + /// + [Fact] + public void SpanStageMirrorMatchesCSharpBitForBitOnRealisticSpans() + { + var (assembly, errors) = CompileShaderMirror(); + Assert.True(assembly is not null, string.Join(Environment.NewLine, errors)); + var shaderType = assembly!.GetType("CultMathHlsl.HlslShader")!; + var shader = Activator.CreateInstance(shaderType); + var (pathType, stageType) = SpanTypes(shaderType); + var spanArguments = new[] { pathType, stageType, typeof(int), typeof(float), typeof(float) }; + var mirrors = new[] { ShaderFunction(shaderType, "cultmath_span_fbm", spanArguments)!, ShaderFunction(shaderType, "cultmath_span_ridged", spanArguments)! }; + + var random = new System.Random(0x5EED4); + float Next(float extent) => (random.NextSingle() * 2.0f - 1.0f) * extent; + int stretched = 0, unstretched = 0, folded = 0, flat = 0; + for (var i = 0; i < 4000; i++) + { + var drawn = SpanStageTests.Stage.Draw(random); + var stage = drawn.S; + if (i % 4 == 1) + stage.axis = new float3(0.0f, 0.0f, 0.0f); + var halfLength = i % 4 == 2 ? 0.0f : new[] { 0.02f, 0.1f, 0.5f, 2.0f }[random.Next(4)]; + var span = SpanStageTests.Span.Draw(random, new float3(Next(30.0f), Next(30.0f), Next(30.0f)), halfLength, random.NextSingle() * 2.0f); + var arguments = new object[] { CopyFields(span.Path, pathType), CopyFields(stage, stageType), drawn.Octaves, drawn.Lacunarity, drawn.Gain }; + if (dot(stage.axis, stage.axis) > 0.0f && stage.stretch != 1.0f) + stretched++; + else + unstretched++; + for (var m = 0; m < 2; m++) + { + var expected = m == 0 ? span_fbm(span.Path, stage, drawn.Octaves, drawn.Lacunarity, drawn.Gain) : span_ridged(span.Path, stage, drawn.Octaves, drawn.Lacunarity, drawn.Gain); + var actual = mirrors[m].Invoke(shader, arguments)!; + Assert.True(BitwiseEqual(Values(expected), Values(actual)), + $"{mirrors[m].Name} differs from C# at midpoint {span.Path.midpoint}, halfLength {halfLength:R}, {drawn.Octaves} octaves: C# {string.Join(" ", Values(expected).Select(v => v.Value))}, mirror {string.Join(" ", Values(actual).Select(v => v.Value))}"); + if (m == 1 && expected.fold > 0.0f) + folded++; + else if (m == 1) + flat++; + } + } + + Assert.True(stretched >= 500 && unstretched >= 500 && folded >= 500 && flat >= 500, + $"the sweep meets a branch too rarely: stretched {stretched}, unstretched {unstretched}, fold > 0 {folded}, fold = 0 {flat} of 4000"); } /// @@ -352,14 +544,66 @@ public sealed class HlslSourceCompatibilityTests } // Integer arguments take the bit pattern of the float input, so specials become 0, 0x80000000, NaN bits, ... - private static readonly Type[] Scalars = { typeof(float), typeof(int), typeof(uint) }; + // A bool argument is true when its float input is positive. + private static readonly Type[] Scalars = { typeof(float), typeof(int), typeof(uint), typeof(bool) }; private static object Scalar(Type t, float f) => t == typeof(float) ? f + : t == typeof(bool) ? f > 0.0f : t == typeof(int) ? BitConverter.SingleToInt32Bits(f) : (object)(uint)BitConverter.SingleToInt32Bits(f); - // Scoped to scalar-typed fields: this only ever builds function ARGUMENTS (float2/3/4, int2/3/4, ...), - // never a struct-of-struct return, so it must not recurse. - private static FieldInfo[] Components(Type t) => t.GetFields().Where(f => !f.IsStatic && Scalars.Contains(f.FieldType)).ToArray(); + private static FieldInfo[] InstanceFields(Type t) => t.GetFields(BindingFlags.Public | BindingFlags.Instance); + + // One function ARGUMENT from the case's scalars, in field order: a scalar from its float, a vector by the + // constructor taking its scalar fields, a struct field by field, recursively. + private static object Build(Type t, Func next) + { + if (Scalars.Contains(t)) + return Scalar(t, next()); + var fields = InstanceFields(t); + var constructor = t.GetConstructor(fields.Select(f => f.FieldType).ToArray()); + if (constructor is not null) + return constructor.Invoke(fields.Select(f => Build(f.FieldType, next)).ToArray()); + var value = Activator.CreateInstance(t)!; + foreach (var field in fields) + field.SetValue(value, Build(field.FieldType, next)); + return value; + } + + // The CultMath type a mirror parameter type stands for: itself, or for a type nested in HlslShader the + // CultMath type of the same name, only if both have the same instance fields by name, type (mapped + // likewise) and order. Null when there is no such twin. + private static Type? TwinType(Type shaderType, Type mirror) + { + if (mirror.DeclaringType != shaderType) + return mirror; + var twin = typeof(math).Assembly.GetType("CultMath." + mirror.Name); + if (twin is null) + return null; + var mine = InstanceFields(mirror); + var theirs = InstanceFields(twin); + return mine.Length == theirs.Length && mine.Zip(theirs).All(p => p.First.Name == p.Second.Name && TwinType(shaderType, p.First.FieldType) == p.Second.FieldType) ? twin : null; + } + + // The math function of the mirror function's name, with its parameter types mapped, public or private. + private static MethodInfo? Twin(Type shaderType, string name, Type[] mirrorTypes) + { + var types = mirrorTypes.Select(t => TwinType(shaderType, t)).ToArray(); + return types.Any(t => t is null) ? null + : typeof(math).GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, null, types!, null); + } + + // A struct value as another struct type of the same fields by name: itself when the type is the same. + private static object CopyFields(object source, Type target) + { + if (source.GetType() == target) + return source; + var copy = Activator.CreateInstance(target)!; + foreach (var field in InstanceFields(target)) + field.SetValue(copy, CopyFields(source.GetType().GetField(field.Name)!.GetValue(source)!, field.FieldType)); + return copy; + } + + private static object FieldOf(object value, string name) => value.GetType().GetField(name)!.GetValue(value)!; /// /// One flattened scalar leaf of a (possibly nested) return value: its declaration path (field @@ -412,8 +656,11 @@ public sealed class HlslSourceCompatibilityTests : string.Empty); } - /// The documented HLSL-to-C# transformations, and nothing else. - internal static string TransformHlslToCSharp(string hlsl) + /// + /// The documented HLSL-to-C# transformations, and nothing else. is C# source a + /// test supplies, placed first in the class body: it is not a transformation of the HLSL. + /// + internal static string TransformHlslToCSharp(string hlsl, string members = "") { // 1. Include guards (#ifndef/#define/#endif) are dropped; C# has no textual include. var source = Regex.Replace(hlsl, @"(?m)^\s*#(ifndef|define|endif)\b.*$", string.Empty); @@ -430,18 +677,19 @@ public sealed class HlslSourceCompatibilityTests // 5. Struct fields gain `public`; HLSL has no field access-modifier concept, and a C# struct's // fields default to private, which would hide them from the mirror test's reflection-based - // field comparison (invariant 8's one struct return shape, e.g. CultCellular). + // field comparison (invariant 8's one struct return shape, e.g. CultCellular) and the struct + // arguments' copies (SpanPath, SpanStage), which reflection reads and writes by field name. source = Regex.Replace(source, @"(?ms)(struct\s+\w+\s*\{)(.*?)(\};)", m => m.Groups[1].Value + Regex.Replace(m.Groups[2].Value, @"(?m)^(\s*)(\w+\s+\w+;)", "$1public $2") + m.Groups[3].Value); // 6. The file body is wrapped in a class; C# has no free functions. - return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + source + "\n}\n"; + return "using CultMath;\nusing static CultMath.math;\nnamespace CultMathHlsl;\npublic class HlslShader\n{\n" + members + source + "\n}\n"; } - internal static (Assembly? Assembly, IReadOnlyList Errors) CompileShaderMirror() + internal static (Assembly? Assembly, IReadOnlyList Errors) CompileShaderMirror(string members = "") { var hlsl = ReadShaderSource(FindCultMathRoot()); - var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl)); + var tree = CSharpSyntaxTree.ParseText(TransformHlslToCSharp(hlsl, members)); var references = ((string)AppContext.GetData("TRUSTED_PLATFORM_ASSEMBLIES")!) .Split(Path.PathSeparator) .Append(typeof(math).Assembly.Location) diff --git a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs index 312770ba..f97d5bfe 100644 --- a/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/NoiseBoundTests.cs @@ -469,8 +469,8 @@ public sealed class NoiseBoundTests /// /// Pins SNOISE_LIPSCHITZ: no |snoise_grad| over 1e5 seeded points exceeds it, and the ascent from - /// MeasureLipschitz's witness reaches a maximum that, times 1.10, is the constant to within half a - /// percent, so a constant lowered (or raised) by 1% fails here. + /// MeasureLipschitz's witness reaches a maximum that, times 1.10, equals the constant bit for bit + /// (the expression MeasureLipschitz prints), so any moved constant fails here. /// [Fact] public void LipschitzConstantPinsSampledGradients() @@ -479,8 +479,8 @@ public sealed class NoiseBoundTests Assert.True(sampled <= SNOISE_LIPSCHITZ, $"|snoise_grad| reaches {sampled:R}, above SNOISE_LIPSCHITZ {SNOISE_LIPSCHITZ:R}"); var refined = AscendGradientNorm(LipschitzWitness); Assert.True(refined <= SNOISE_LIPSCHITZ, $"|snoise_grad| reaches {refined:R}, above SNOISE_LIPSCHITZ {SNOISE_LIPSCHITZ:R}"); - var ratio = SNOISE_LIPSCHITZ / (1.10f * refined); - Assert.True(MathF.Abs(ratio - 1.0f) <= 0.005f, $"SNOISE_LIPSCHITZ is {ratio:R} times 1.10 x the re-measured {refined:R}; re-run MeasureLipschitz and re-pin"); + var measured = 1.10f * refined; + Assert.True(SNOISE_LIPSCHITZ == measured, $"SNOISE_LIPSCHITZ is {SNOISE_LIPSCHITZ:R}, the re-measured 1.10 x {refined:R} is {measured:R}; re-run MeasureLipschitz and re-pin"); } // ---- The curvature and 2D Lipschitz constants (math.Affine.cs, math.Interval.cs) ---- @@ -609,8 +609,8 @@ public sealed class NoiseBoundTests /// /// Pins SNOISE_HESSIAN: no Hessian norm over 1e5 seeded points exceeds it, and the ascent from - /// MeasureHessian's witness reaches a maximum that, times 1.10, is the constant to within half a - /// percent, so a constant moved by 1% fails here. + /// MeasureHessian's witness reaches a maximum that, times 1.10, equals the constant bit for bit + /// (the expression MeasureHessian prints), so any moved constant fails here. /// [Fact] public void HessianConstantPinsSampledCurvature() @@ -619,39 +619,202 @@ public sealed class NoiseBoundTests Assert.True(sampled <= SNOISE_HESSIAN, $"the Hessian norm reaches {sampled:R}, above SNOISE_HESSIAN {SNOISE_HESSIAN:R}"); var refined = Ascend(HessianNorm, HessianWitness); Assert.True(refined <= SNOISE_HESSIAN, $"the Hessian norm reaches {refined:R}, above SNOISE_HESSIAN {SNOISE_HESSIAN:R}"); - var ratio = SNOISE_HESSIAN / (1.10 * refined); - Assert.True(Math.Abs(ratio - 1.0) <= 0.005, $"SNOISE_HESSIAN is {ratio:R} times 1.10 x the re-measured {refined:R}; re-run MeasureHessian and re-pin"); + var measured = (float)(refined * 1.10); + Assert.True(SNOISE_HESSIAN == measured, $"SNOISE_HESSIAN is {SNOISE_HESSIAN:R}, the re-measured 1.10 x {refined:R} is {measured:R}; re-run MeasureHessian and re-pin"); } // ---- The third-derivative constant (math.Span.cs) ---- - // The symmetric third-derivative tensor of snoise at p, entry (i * 3 + j) * 3 + k: central differences - // of snoise_hessian along each axis at h = 1e-3 over the exact float32 steps, symmetrised over the six - // index orders (the Hessian is symmetric, so only the derivative index can differ). - private static double[] ThirdTensor(float3 p) + private static float3 CultMod289(float3 v) => v - floor(v * (1.0f / 289.0f)) * 289.0f; + private static float4 CultMod289(float4 v) => v - floor(v * (1.0f / 289.0f)) * 289.0f; + private static float4 CultPermute(float4 v) => CultMod289(((v * 34.0f) + 10.0f) * v); + + // The four simplex corners of snoise at (vx, vy, vz): each corner's offset x_c from the point, and its + // gradient direction p_c, in double, with snoise_hessian's lattice, step() tie order and (float) hash, + // so one corner table serves the Hessian and its derivative. Key names the lattice cell and traversal + // order: two points with the same key are differentiated through the same corners. + internal static (double[][] X, double[][] P, double[] Key) Corners(double vx, double vy, double vz) { - const float h = 1.0e-3f; - var d = new double[27]; - for (var k = 0; k < 3; k++) + const double cx = 1.0 / 6.0; + const double cy = 1.0 / 3.0; + var skew = (vx + vy + vz) * cy; + double ix = Math.Floor(vx + skew), iy = Math.Floor(vy + skew), iz = Math.Floor(vz + skew); + var unskew = (ix + iy + iz) * cx; + double x0 = vx - ix + unskew, y0 = vy - iy + unskew, z0 = vz - iz + unskew; + double gx = x0 >= y0 ? 1.0 : 0.0, gy = y0 >= z0 ? 1.0 : 0.0, gz = 1.0 - (x0 >= z0 ? 1.0 : 0.0); + double lx = 1.0 - gx, ly = 1.0 - gy, lz = 1.0 - gz; + double i1x = Math.Min(gx, lz), i1y = Math.Min(gy, lx), i1z = Math.Min(gz, ly); + double i2x = Math.Max(gx, lz), i2y = Math.Max(gy, lx), i2z = Math.Max(gz, ly); + var x = new[] { - var e = new float3(k == 0 ? h : 0.0f, k == 1 ? h : 0.0f, k == 2 ? h : 0.0f); - var forward = Step(k == 0 ? p.x : k == 1 ? p.y : p.z, h, out var back); - var plus = snoise_hessian(p + e); - var minus = snoise_hessian(p - e); + new[] { x0, y0, z0 }, + new[] { x0 - i1x + cx, y0 - i1y + cx, z0 - i1z + cx }, + new[] { x0 - i2x + cy, y0 - i2y + cy, z0 - i2z + cy }, + new[] { x0 - 0.5, y0 - 0.5, z0 - 0.5 }, + }; + + var i = CultMod289(new float3((float)ix, (float)iy, (float)iz)); + var hash = CultPermute(CultPermute(CultPermute( + i.z + new float4(0.0f, (float)i1z, (float)i2z, 1.0f)) + i.y + new float4(0.0f, (float)i1y, (float)i2y, 1.0f)) + i.x + new float4(0.0f, (float)i1x, (float)i2x, 1.0f)); + const float n = 0.142857142857f; + var ns = new float3(2.0f * n, 0.5f * n - 1.0f, n); + var j = hash - 49.0f * floor(hash * ns.z * ns.z); + var xs = floor(j * ns.z); + var ys = floor(j - 7.0f * xs); + var gxs = xs * ns.x + ns.y; + var gys = ys * ns.x + ns.y; + var h = 1.0f - abs(gxs) - abs(gys); + var b0 = new float4(gxs.x, gxs.y, gys.x, gys.y); + var b1 = new float4(gxs.z, gxs.w, gys.z, gys.w); + var s0 = floor(b0) * 2.0f + 1.0f; + var s1 = floor(b1) * 2.0f + 1.0f; + var sh = -step(h, 0.0f); + var a0 = new float4(b0.x, b0.z, b0.y, b0.w) + new float4(s0.x, s0.z, s0.y, s0.w) * new float4(sh.x, sh.x, sh.y, sh.y); + var a1 = new float4(b1.x, b1.z, b1.y, b1.w) + new float4(s1.x, s1.z, s1.y, s1.w) * new float4(sh.z, sh.z, sh.w, sh.w); + var p0 = new float3(a0.x, a0.y, h.x); + var p1 = new float3(a0.z, a0.w, h.y); + var p2 = new float3(a1.x, a1.y, h.z); + var p3 = new float3(a1.z, a1.w, h.w); + var norm = 1.79284291400159f - 0.85373472095314f * new float4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3)); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + double[] Of(float3 v) => new double[] { v.x, v.y, v.z }; + return (x, new[] { Of(p0), Of(p1), Of(p2), Of(p3) }, new[] { ix, iy, iz, i1x, i1y, i1z, i2x, i2y, i2z }); + } + + // The Hessian of snoise at v in double, entry i * 3 + j: snoise_hessian's per-corner derivation, summed + // over Corners. A corner with m = 0.5 - |x|^2 <= 0 contributes nothing. + internal static double[] HessianDouble(double vx, double vy, double vz) + { + var (x, p, _) = Corners(vx, vy, vz); + var hessian = new double[9]; + for (var c = 0; c < 4; c++) + { + var m = 0.5 - (x[c][0] * x[c][0] + x[c][1] * x[c][1] + x[c][2] * x[c][2]); + if (m <= 0.0) + continue; + var s = p[c][0] * x[c][0] + p[c][1] * x[c][1] + p[c][2] * x[c][2]; for (var i = 0; i < 3; i++) - for (var j = 0; j < 3; j++) - d[(i * 3 + j) * 3 + k] = (plus[i][j] - (double)minus[i][j]) / (forward + back); + for (var k = 0; k < 3; k++) + hessian[i * 3 + k] += 105.0 * (48.0 * m * m * s * x[c][i] * x[c][k] + - 8.0 * m * m * m * (p[c][k] * x[c][i] + p[c][i] * x[c][k] + (i == k ? s : 0.0))); } + return hessian; + } + + // The value of snoise at v in double: 105 sum m^4 s over Corners, the kernel's exact value (the tied + // kernel, as HessianDouble is its exact Hessian). + internal static double ValueDouble(double vx, double vy, double vz) + { + var (x, p, _) = Corners(vx, vy, vz); + var total = 0.0; + for (var c = 0; c < 4; c++) + { + var m = 0.5 - (x[c][0] * x[c][0] + x[c][1] * x[c][1] + x[c][2] * x[c][2]); + if (m <= 0.0) + continue; + total += 105.0 * m * m * m * m * (p[c][0] * x[c][0] + p[c][1] * x[c][1] + p[c][2] * x[c][2]); + } + + return total; + } + + private static double Delta(int a, int b) => a == b ? 1.0 : 0.0; + + // The symmetric third-derivative tensor of snoise at p, entry (i * 3 + j) * 3 + k, in closed form: + // snoise_hessian's per-corner entry 105 (48 m^2 s x_i x_j - 8 m^3 (p_j x_i + p_i x_j + s delta_ij)) + // differentiated by x_k (dm = -2 x_k, ds = p_k, every corner's x moves one for one with p), which sums + // to 105 (-192 m s x_i x_j x_k + 48 m^2 (p_k x_i x_j + p_j x_i x_k + p_i x_j x_k) + // + 48 m^2 s (delta_ik x_j + delta_jk x_i + delta_ij x_k) - 8 m^3 (p_j delta_ik + p_i delta_jk + p_k delta_ij)). + // Symmetric in i, j and k by construction, so there is no symmetrising pass. + private static double[] ThirdTensor(float3 p) + { + var (x, g, _) = Corners(p.x, p.y, p.z); var t = new double[27]; - for (var i = 0; i < 3; i++) - for (var j = 0; j < 3; j++) - for (var k = 0; k < 3; k++) - t[(i * 3 + j) * 3 + k] = (d[(i * 3 + j) * 3 + k] + d[(i * 3 + k) * 3 + j] + d[(j * 3 + i) * 3 + k] - + d[(j * 3 + k) * 3 + i] + d[(k * 3 + i) * 3 + j] + d[(k * 3 + j) * 3 + i]) / 6.0; + for (var c = 0; c < 4; c++) + { + var m = 0.5 - (x[c][0] * x[c][0] + x[c][1] * x[c][1] + x[c][2] * x[c][2]); + if (m <= 0.0) + continue; + var s = g[c][0] * x[c][0] + g[c][1] * x[c][1] + g[c][2] * x[c][2]; + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + for (var k = 0; k < 3; k++) + t[(i * 3 + j) * 3 + k] += 105.0 * (-192.0 * m * s * x[c][i] * x[c][j] * x[c][k] + + 48.0 * m * m * (g[c][k] * x[c][i] * x[c][j] + g[c][j] * x[c][i] * x[c][k] + g[c][i] * x[c][j] * x[c][k]) + + 48.0 * m * m * s * (Delta(i, k) * x[c][j] + Delta(j, k) * x[c][i] + Delta(i, j) * x[c][k]) + - 8.0 * m * m * m * (g[c][j] * Delta(i, k) + g[c][i] * Delta(j, k) + g[c][k] * Delta(i, j))); + } + return t; } + /// + /// ThirdTensor is the third derivative of snoise: HessianDouble is the kernel's Hessian (within float + /// rounding of snoise_hessian over 200 seeded points, which proves Corners is the kernel's lattice), + /// and the closed form equals central differences of HessianDouble, Richardson-extrapolated from steps + /// h = 1e-3 and 2e-3 (error O(h^4)), at every one of 27 entries of every point whose neighbours share + /// its lattice cell and clear every corner's support edge. Dropping or mis-signing any term of the closed form moves some entry by far more + /// than the tolerance. + /// + [Fact] + public void ThirdTensorMatchesDifferencesOfTheHessian() + { + var random = new System.Random(0x7D1); + var compared = 0; + var largest = 0.0; + var worst = 0.0; + for (var n = 0; n < 400; n++) + { + var p = new float3(Uniform(random, -256.0f, 256.0f), Uniform(random, -256.0f, 256.0f), Uniform(random, -256.0f, 256.0f)); + var hd = HessianDouble(p.x, p.y, p.z); + var kernel = snoise_hessian(p); + if (n < 200) + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + Assert.True(Math.Abs(hd[i * 3 + j] - kernel[i][j]) <= 0.1, $"HessianDouble({p}) entry {i},{j} is {hd[i * 3 + j]:R}, snoise_hessian {kernel[i][j]:R}"); + + double[] At(int axis, double delta) => + HessianDouble(p.x + (axis == 0 ? delta : 0.0), p.y + (axis == 1 ? delta : 0.0), p.z + (axis == 2 ? delta : 0.0)); + var (corner, _, key) = Corners(p.x, p.y, p.z); + // A corner whose support edge (m = 0) lies within the differencing window has a kink in the + // Hessian's second derivative there, which the O(h^4) extrapolation does not cover. + if (corner.Any(c => Math.Abs(0.5 - (c[0] * c[0] + c[1] * c[1] + c[2] * c[2])) < 0.01)) + continue; + var sameCell = true; + for (var axis = 0; axis < 3; axis++) + foreach (var delta in new[] { -2.0e-3, 2.0e-3 }) + sameCell &= Corners(p.x + (axis == 0 ? delta : 0.0), p.y + (axis == 1 ? delta : 0.0), p.z + (axis == 2 ? delta : 0.0)).Key.SequenceEqual(key); + if (!sameCell) + continue; + + var closed = ThirdTensor(p); + compared++; + for (var k = 0; k < 3; k++) + { + var (plus1, minus1, plus2, minus2) = (At(k, 1.0e-3), At(k, -1.0e-3), At(k, 2.0e-3), At(k, -2.0e-3)); + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + { + var d1 = (plus1[i * 3 + j] - minus1[i * 3 + j]) / 2.0e-3; + var d2 = (plus2[i * 3 + j] - minus2[i * 3 + j]) / 4.0e-3; + var richardson = (4.0 * d1 - d2) / 3.0; + var entry = closed[(i * 3 + j) * 3 + k]; + largest = Math.Max(largest, Math.Abs(entry)); + worst = Math.Max(worst, Math.Abs(entry - richardson) / (1.0 + Math.Abs(entry))); + } + } + } + + output.WriteLine($"IV-REPORT third oracle: {compared} points, largest entry {largest:R}, worst relative difference from the Richardson differences {worst:R}"); + Assert.True(compared >= 250, $"only {compared} of 400 points kept their lattice cell and cleared the support edges"); + Assert.True(largest >= 200.0, $"the largest third-derivative entry compared is {largest:R}: the check has no teeth"); + Assert.True(worst <= 1.0e-6, $"the closed-form third tensor differs from the differenced Hessian by {worst:R} (relative)"); + } + // T(u, u, u) for a symmetric 3-tensor. private static double Cubic(double[] t, double[] u) { @@ -748,8 +911,8 @@ public sealed class NoiseBoundTests } /// - /// The provenance of SNOISE_THIRD: the largest operator norm of snoise's third-derivative tensor - /// (ThirdNorm) over 1e6 seeded points of [-256, 256]^3, the 1e4 largest refined by ascent over the point, + /// The provenance of SNOISE_THIRD: the largest operator norm of snoise's closed-form third-derivative + /// tensor (ThirdNorm, ThirdTensor) over 1e6 seeded points of [-256, 256]^3, the 1e4 largest refined by ascent over the point, /// times 1.10, on the tied kernel. Prints the start whose ascent reached the maximum, which /// ThirdConstantPinsSampled climbs from again, and a sweep of 20,000 directions at the point the /// ascent ends on (the tensor norm there against the largest cubic form the sweep finds, so the @@ -780,12 +943,12 @@ public sealed class NoiseBoundTests } // The start MeasureThird printed as its witness. - private static readonly float3 ThirdWitness = new(-168.3755f, -3.8205261f, -109.16948f); + private static readonly float3 ThirdWitness = new(-184.81363f, -34.250977f, 68.26865f); /// /// Pins SNOISE_THIRD: no third-derivative norm over 1e5 seeded points exceeds it, and the ascent from - /// MeasureThird's witness reaches a maximum that, times 1.10, is the constant to within half a - /// percent, so a constant moved by 1% fails here. + /// MeasureThird's witness reaches a maximum that, times 1.10, equals the constant bit for bit + /// (the expression MeasureThird prints), so any moved constant fails here. /// [Fact] public void ThirdConstantPinsSampled() @@ -794,8 +957,8 @@ public sealed class NoiseBoundTests Assert.True(sampled <= SNOISE_THIRD, $"the third-derivative norm reaches {sampled:R}, above SNOISE_THIRD {SNOISE_THIRD:R}"); var refined = Ascend(ThirdNorm, ThirdWitness); Assert.True(refined <= SNOISE_THIRD, $"the third-derivative norm reaches {refined:R}, above SNOISE_THIRD {SNOISE_THIRD:R}"); - var ratio = SNOISE_THIRD / (1.10 * refined); - Assert.True(Math.Abs(ratio - 1.0) <= 0.005, $"SNOISE_THIRD is {ratio:R} times 1.10 x the re-measured {refined:R}; re-run MeasureThird and re-pin"); + var measured = (float)(refined * 1.10); + Assert.True(SNOISE_THIRD == measured, $"SNOISE_THIRD is {SNOISE_THIRD:R}, the re-measured 1.10 x {refined:R} is {measured:R}; re-run MeasureThird and re-pin"); } /// @@ -856,7 +1019,7 @@ public sealed class NoiseBoundTests /// /// Pins SNOISE2_LIPSCHITZ as LipschitzConstantPinsSampledGradients pins SNOISE_LIPSCHITZ: 1e5 seeded - /// points under it, and the ascent from MeasureLipschitz2's witness within half a percent of it / 1.10. + /// points under it, and the ascent from MeasureLipschitz2's witness, times 1.10, equals it bit for bit (the expression MeasureLipschitz2 prints). /// [Fact] public void Lipschitz2ConstantPins() @@ -865,8 +1028,8 @@ public sealed class NoiseBoundTests Assert.True(sampled <= SNOISE2_LIPSCHITZ, $"|grad snoise2| reaches {sampled:R}, above SNOISE2_LIPSCHITZ {SNOISE2_LIPSCHITZ:R}"); var refined = Ascend2(Gradient2Norm, Lipschitz2Witness); Assert.True(refined <= SNOISE2_LIPSCHITZ, $"|grad snoise2| reaches {refined:R}, above SNOISE2_LIPSCHITZ {SNOISE2_LIPSCHITZ:R}"); - var ratio = SNOISE2_LIPSCHITZ / (1.10 * refined); - Assert.True(Math.Abs(ratio - 1.0) <= 0.005, $"SNOISE2_LIPSCHITZ is {ratio:R} times 1.10 x the re-measured {refined:R}; re-run MeasureLipschitz2 and re-pin"); + var measured = (float)(refined * 1.10); + Assert.True(SNOISE2_LIPSCHITZ == measured, $"SNOISE2_LIPSCHITZ is {SNOISE2_LIPSCHITZ:R}, the re-measured 1.10 x {refined:R} is {measured:R}; re-run MeasureLipschitz2 and re-pin"); } /// diff --git a/packages/cultmath/tests/CultMath.Tests/SpanPrimitiveTests.cs b/packages/cultmath/tests/CultMath.Tests/SpanPrimitiveTests.cs index 44814195..dea5ab0c 100644 --- a/packages/cultmath/tests/CultMath.Tests/SpanPrimitiveTests.cs +++ b/packages/cultmath/tests/CultMath.Tests/SpanPrimitiveTests.cs @@ -319,6 +319,63 @@ public sealed class SpanPrimitiveTests Assert.True(zeroSlopes > 10_000 && reversed > 40_000 && both > 2_000 && neither > 2_000, $"the sample meets a branch too rarely: {zeroSlopes} flat, {reversed} reversed, {both} both, {neither} neither"); } + /// + /// An infinite bound never reached by the line contributes nothing, not infinity times zero: relu + /// (hi = +inf) and its mirror (lo = -inf) give the finite integral, whether the line crosses the finite + /// bound inside the span or not. Both branches give +0 for a zero-length span. + /// + [Fact] + public void IntegralClampLinearIsFiniteForInfiniteBounds() + { + var inf = float.PositiveInfinity; + // t over [0, 1] clamped below at 0: all ramp, 1/2. + Assert.Equal(0.5f, integral_clamp_linear(0.0f, 1.0f, 0.0f, inf, 0.0f, 1.0f)); + // t - 1/2 over [0, 1] clamped below at 0: 0 until 1/2, then the ramp's 1/8. + Assert.Equal(0.125f, integral_clamp_linear(-0.5f, 1.0f, 0.0f, inf, 0.0f, 1.0f)); + // The falling line 1/2 - t clamped below at 0 is the mirror: 1/8 over [0, 1]. + Assert.Equal(0.125f, integral_clamp_linear(0.5f, -1.0f, 0.0f, inf, 0.0f, 1.0f)); + // lo = -inf: clamp(t, -inf, 1/2) over [0, 1] is t up to 1/2 then 1/2 (1/8 + 1/4), and 1 - t clamped + // above at 1/2 is 1/2 until t = 1/2 and then falls (1/4 + 1/8). + Assert.Equal(0.375f, integral_clamp_linear(0.0f, 1.0f, -inf, 0.5f, 0.0f, 1.0f)); + Assert.Equal(0.375f, integral_clamp_linear(1.0f, -1.0f, -inf, 0.5f, 0.0f, 1.0f)); + // Both infinite: the line itself, the trapezoid, in either direction. + Assert.Equal(7.5f, integral_clamp_linear(0.0f, 1.0f, -inf, inf, 1.0f, 4.0f)); + Assert.Equal(-7.5f, integral_clamp_linear(0.0f, 1.0f, -inf, inf, 4.0f, 1.0f)); + // A line starting on the finite bound and rising into the infinite one: all ramp, the integral of t over [2, 4]. + Assert.Equal(6.0f, integral_clamp_linear(0.0f, 1.0f, 2.0f, inf, 2.0f, 4.0f)); + + // The flat branch obeys the same zero-length rule: a stretch of zero length contributes +0 however + // infinite the bound it would be clamped to, and a nonzero length of an infinite constant is infinite. + // The bit patterns are compared because Assert.Equal treats -0 as 0. + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(-2.3244874f, -0.0f, 0.0f, -inf, 1.429815f, 1.429815f))); + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(1.5f, 0.0f, inf, inf, 2.0f, 2.0f))); + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(1.5f, -0.0f, -inf, -inf, 0.0f, 0.0f))); + Assert.Equal(inf, integral_clamp_linear(1.5f, 0.0f, inf, inf, 0.0f, 2.0f)); + Assert.Equal(inf, integral_clamp_linear(1.5f, 0.0f, -inf, -inf, 2.0f, 0.0f)); + + // The sloped branch gives +0 for a zero-length span too, not -0: the direction sign never meets a + // zero total, whichever way the line leans and whatever the bounds. + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(1.0f, 2.0f, -inf, inf, 1.0f, 1.0f))); + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(-3.0f, -2.0f, 0.0f, 5.0f, 1.0f, 1.0f))); + Assert.Equal(0, BitConverter.SingleToInt32Bits(integral_clamp_linear(0.5f, 1.0f, 0.0f, inf, 0.0f, 0.0f))); + } + + /// + /// lo above hi is normalised before the flat branch reads it: with b = 0 the answer is hi times the + /// span (what clamp(a, lo, hi) gives with lo = min(lo, hi)), and with b != 0 the same. The GLSL + /// lowering would otherwise call clamp with minVal above maxVal, which is undefined there. + /// + [Fact] + public void IntegralClampLinearNormalisesBeforeTheFlatBranch() + { + // b = 0: a above both, below both and between them all give hi. + foreach (var a in new[] { 9.0f, -9.0f, 3.5f }) + Assert.Equal(2.0f * 3.0f, integral_clamp_linear(a, 0.0f, 5.0f, 2.0f, 0.0f, 3.0f)); + // b != 0: hi everywhere, whichever way the line runs. + Assert.Equal(2.0f * 3.0f, integral_clamp_linear(0.0f, 1.0f, 5.0f, 2.0f, 0.0f, 3.0f)); + Assert.Equal(2.0f * 3.0f, integral_clamp_linear(9.0f, -1.0f, 5.0f, 2.0f, 0.0f, 3.0f)); + } + [Fact] public void IntegralClampLinearHasExactValuesOnTheBinaryFloats() { diff --git a/packages/cultmath/tests/CultMath.Tests/SpanStageTests.cs b/packages/cultmath/tests/CultMath.Tests/SpanStageTests.cs new file mode 100644 index 00000000..8324b5a1 --- /dev/null +++ b/packages/cultmath/tests/CultMath.Tests/SpanStageTests.cs @@ -0,0 +1,893 @@ +using Xunit; +using static CultMath.math; + +namespace CultMath.Tests; + +/// +/// span_fbm and span_ridged (math.Span.cs): the octave stages of a warp chain over a span. At the identity +/// transform they are fbm_grad and ridged_grad; their gradient is the derivative of their value through the +/// stretch and the per-octave scaling; curvature bounds the Hessian of the field in q and fold the departure +/// of a ridged sum from its midpoint branch, at every point of every span the tests draw, in the regime +/// where the local third-derivative term is what holds the bound and in the one where the global pin is. +/// Lines starting IV-REPORT carry the numbers the cut report quotes. +/// +public sealed class SpanStageTests +{ + private readonly ITestOutputHelper output; + + public SpanStageTests(ITestOutputHelper output) => this.output = output; + + private static float Uniform(System.Random random, float lo, float hi) => lo + (hi - lo) * random.NextSingle(); + + private static float3 Vector(System.Random random, float lo, float hi) => new(Uniform(random, lo, hi), Uniform(random, lo, hi), Uniform(random, lo, hi)); + + private static float3 UnitVector(System.Random random) + { + float3 v; + do + { + v = Vector(random, -1.0f, 1.0f); + } + while (dot(v, v) is > 1.0f or < 0.01f); + return normalize(v); + } + + private static float LogUniform(System.Random random, float lo, float hi) => MathF.Exp(Uniform(random, MathF.Log(lo), MathF.Log(hi))); + + private static float Signed(System.Random random, float magnitude) => random.Next(2) == 0 ? magnitude : -magnitude; + + private static readonly float3 None = new(0.0f, 0.0f, 0.0f); + + private static double L1(double[] v) => Math.Abs(v[0]) + Math.Abs(v[1]) + Math.Abs(v[2]); + + // One draw of the stage parameters: every one of them off its identity value, over the ranges the stage + // takes in use (stage-parameter-range): |frequency| 1e-3..10, stretch 1e-2..1e2, |lacunarity| 0.5..3, + // |gain| 0.1..1.2, the last three and frequency with random signs where a sign is meaningful, + // octaveStep nonzero in every component. + internal readonly record struct Stage(SpanStage S, int Octaves, float Lacunarity, float Gain) + { + public static Stage Draw(System.Random random, int maxOctaves = 8, int minOctaves = 1) => new( + new SpanStage + { + frequency = Signed(random, LogUniform(random, 1.0e-3f, 10.0f)), + stretch = LogUniform(random, 1.0e-2f, 1.0e2f), + axis = UnitVector(random) * Uniform(random, 0.5f, 3.0f), + shift = Vector(random, -8.0f, 8.0f), + offset = Vector(random, -8.0f, 8.0f), + octaveStep = new float3(Signed(random, Uniform(random, 0.1f, 3.0f)), Signed(random, Uniform(random, 0.1f, 3.0f)), Signed(random, Uniform(random, 0.1f, 3.0f))), + }, + random.Next(minOctaves, maxOctaves + 1), + Signed(random, Uniform(random, 0.5f, 3.0f)), + Signed(random, Uniform(random, 0.1f, 1.2f))); + + // A draw whose octave coordinates stay under `limit` for every q with |q|_1 <= qL1: the fastest + // octave's |f_o| |M| is capped by rejection. For the derivative checks, where float rounding of the + // coordinate must not swamp a difference quotient. + public static Stage DrawSmooth(System.Random random, float limit, float qL1, int maxOctaves = 8, int minOctaves = 1) + { + while (true) + { + var stage = Draw(random, maxOctaves, minOctaves); + var worst = 0.0; + for (var o = 0; o < stage.Octaves; o++) + worst = Math.Max(worst, Math.Abs(stage.Scale(o)) * (stage.Norm() * qL1 + L1(Components(stage.S.shift))) + L1(Components(stage.S.offset)) + o * L1(Components(stage.S.octaveStep))); + if (worst < limit) + return stage; + } + } + + public SpanNoise Fbm(SpanPath path) => span_fbm(path, S, Octaves, Lacunarity, Gain); + + public SpanNoise Ridged(SpanPath path) => span_ridged(path, S, Octaves, Lacunarity, Gain); + + public SpanNoise Fbm(SpanPath path, int octaves) => span_fbm(path, S, octaves, Lacunarity, Gain); + + public SpanNoise Ridged(SpanPath path, int octaves) => span_ridged(path, S, octaves, Lacunarity, Gain); + + // M = frequency (I + (1/stretch - 1) a a^T) in double, a the normalised axis (none for a zero axis). + public double[,] Matrix() + { + var m = new double[3, 3]; + var a = Components(S.axis); + var length = Math.Sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + m[i, j] = S.frequency * ((i == j ? 1.0 : 0.0) + (length > 0.0 && S.stretch != 1.0f ? (1.0 / S.stretch - 1.0) * a[i] * a[j] / (length * length) : 0.0)); + return m; + } + + // Octave o's noise-space point for the span point q, in double from the same inputs. + public double[] PointD(float3 q, int octave) + { + var m = Matrix(); + var scale = Math.Pow(Lacunarity, octave); + var x = new double[3]; + var qv = Components(q); + var shift = Components(S.shift); + var offset = Components(S.offset); + var step = Components(S.octaveStep); + for (var i = 0; i < 3; i++) + { + var mq = 0.0; + for (var j = 0; j < 3; j++) + mq += m[i, j] * qv[j]; + x[i] = scale * (mq + shift[i]) + offset[i] + octave * step[i]; + } + + return x; + } + + public float3 Point(float3 q, int octave) + { + var x = PointD(q, octave); + return new float3((float)x[0], (float)x[1], (float)x[2]); + } + + // S_o: the magnitudes octave o's coordinate roundings are relative to, plus the span's reach. + public double Magnitude(SpanPath path, int octave) + { + var m = Matrix(); + var qv = Components(path.midpoint); + var mq = new double[3]; + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + mq[i] += m[i, j] * qv[j]; + var scale = Math.Abs(Scale(octave)); + return scale * (L1(mq) + L1(Components(S.shift))) + L1(Components(S.offset)) + octave * L1(Components(S.octaveStep)) + + Math.Sqrt(3.0) * scale * Norm() * path.speed * path.halfLength; + } + + public double MaxMagnitude(SpanPath path) + { + var largest = 0.0; + for (var o = 0; o < Octaves; o++) + largest = Math.Max(largest, Magnitude(path, o)); + return largest; + } + + // Octave o as a stage of its own: one octave at the scaled frequency, shift and offset. + public Stage OneOctave(int octave) => new( + new SpanStage { frequency = S.frequency * (float)Scale(octave), stretch = S.stretch, axis = S.axis, shift = S.shift * (float)Scale(octave), offset = S.offset + (float)octave * S.octaveStep, octaveStep = None }, + 1, 1.0f, 1.0f); + + public double Amplitude(int octave) => Math.Pow(Gain, octave); + + public double Scale(int octave) => Math.Pow(Lacunarity, octave); + + // The spectral norm of M: frequency times the larger of 1 and 1/stretch. + public double Norm() => Math.Abs(S.frequency) * Math.Max(1.0, 1.0 / S.stretch); + } + + private static double[] Components(float3 v) => new double[] { v.x, v.y, v.z }; + + private static SpanPath PathAt(float3 q) => new() { midpoint = q, velocity = None, speed = 0.0f, acceleration = 0.0f, halfLength = 0.0f }; + + // A span through q: velocity v, constant acceleration c of norm `acceleration`, so q(t) = q + v t + c t^2 / 2; + // the speed bound is |v| plus what c can add over halfLength. + internal readonly record struct Span(SpanPath Path, float3 Acceleration) + { + public static Span Draw(System.Random random, float3 q, float halfLength, float acceleration) + { + var velocity = UnitVector(random) * Uniform(random, 0.2f, 3.0f); + return new Span(new SpanPath { midpoint = q, velocity = velocity, speed = length(velocity) + acceleration * halfLength, acceleration = acceleration, halfLength = halfLength }, UnitVector(random) * acceleration); + } + + public float3 At(float t) => Path.midpoint + Path.velocity * t + Acceleration * (0.5f * t * t); + } + + // The operator norm of a symmetric 3x3 matrix (entries in row-major order): the larger |eigenvalue|. + private static double SymmetricNorm(double[] a) + { + var p1 = a[1] * a[1] + a[2] * a[2] + a[5] * a[5]; + var q = (a[0] + a[4] + a[8]) / 3.0; + if (p1 == 0.0) + return Math.Max(Math.Abs(a[0]), Math.Max(Math.Abs(a[4]), Math.Abs(a[8]))); + var p2 = (a[0] - q) * (a[0] - q) + (a[4] - q) * (a[4] - q) + (a[8] - q) * (a[8] - q) + 2.0 * p1; + var s = Math.Sqrt(p2 / 6.0); + var b = new[] { (a[0] - q) / s, a[1] / s, a[2] / s, a[3] / s, (a[4] - q) / s, a[5] / s, a[6] / s, a[7] / s, (a[8] - q) / s }; + var r = Math.Clamp((b[0] * (b[4] * b[8] - b[5] * b[7]) - b[1] * (b[3] * b[8] - b[5] * b[6]) + b[2] * (b[3] * b[7] - b[4] * b[6])) / 2.0, -1.0, 1.0); + var phi = Math.Acos(r) / 3.0; + return Math.Max(Math.Abs(q + 2.0 * s * Math.Cos(phi)), Math.Abs(q + 2.0 * s * Math.Cos(phi + 2.0 * Math.PI / 3.0))); + } + + // The exact Hessian in q of the stage field at span point q (signed per octave for a ridged sum), by the + // chain rule from the double kernel's Hessian: sum over octaves of weight_o f_o^2 M H_o M, with every + // octave point computed in double from the same SpanStage. + private static double[] FieldHessian(Stage stage, float3 q, bool ridged) + { + var m = stage.Matrix(); + var total = new double[9]; + for (var o = 0; o < stage.Octaves; o++) + { + var x = stage.PointD(q, o); + var h = NoiseBoundTests.HessianDouble(x[0], x[1], x[2]); + var weight = stage.Amplitude(o) * stage.Scale(o) * stage.Scale(o) * (ridged ? -Math.Sign(NoiseBoundTests.ValueDouble(x[0], x[1], x[2])) : 1.0); + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + { + var sum = 0.0; + for (var k = 0; k < 3; k++) + for (var l = 0; l < 3; l++) + sum += m[i, k] * h[k * 3 + l] * m[l, j]; + total[i * 3 + j] += weight * sum; + } + } + + return total; + } + + private static bool SameBits(float4 a, float4 b) => + BitConverter.SingleToInt32Bits(a.x) == BitConverter.SingleToInt32Bits(b.x) && BitConverter.SingleToInt32Bits(a.y) == BitConverter.SingleToInt32Bits(b.y) + && BitConverter.SingleToInt32Bits(a.z) == BitConverter.SingleToInt32Bits(b.z) && BitConverter.SingleToInt32Bits(a.w) == BitConverter.SingleToInt32Bits(b.w); + + internal static IEnumerable Points(System.Random random) + { + for (var k = 0; k < 200; k++) + yield return Vector(random, -60.0f, 60.0f); + // The simplex tie set: x = y = z, and integer lattice points. + for (var k = 0; k < 30; k++) + { + var t = Uniform(random, -50.0f, 50.0f); + yield return new float3(t, t, t); + yield return new float3(random.Next(-40, 41), random.Next(-40, 41), random.Next(-40, 41)); + } + } + + /// + /// The exact reference the enclosure tests lean on is the kernel: the double ValueDouble is snoise to + /// within float rounding at small coordinates, so a sign taken from it is the kernel's sign. + /// + [Fact] + public void ExactReferenceIsTheKernel() + { + var random = new System.Random(0xE4AC7); + for (var k = 0; k < 300; k++) + { + var p = Vector(random, -20.0f, 20.0f); + Assert.InRange(NoiseBoundTests.ValueDouble(p.x, p.y, p.z) - snoise(p), -2.0e-4, 2.0e-4); + } + } + + /// + /// At SpanStage.Identity (also with an axis, stretch 1) and zero shift, offset and octaveStep, the field of + /// span_fbm and span_ridged is fbm_grad's and ridged_grad's, bit for bit, for 0 to 20 octaves (the clamp + /// included), every lacunarity and gain drawn, over random and simplex-tie points: any change to the octave + /// loop's order, scaling or accumulation, and any bit the rounding allowances put in the field, fails. + /// + [Fact] + public void SpanStagesAtIdentityAreFbmAndRidged() + { + var random = new System.Random(0x57A6E1); + foreach (var p in Points(random)) + { + var octaves = random.Next(0, 21); + var lacunarity = Uniform(random, 1.4f, 2.6f); + var gain = Uniform(random, 0.3f, 0.8f); + var stage = SpanStage.Identity; + if (random.Next(2) == 1) + stage.axis = UnitVector(random) * 2.0f; + var path = new SpanPath { midpoint = p, velocity = UnitVector(random), speed = 1.0f, acceleration = 0.5f, halfLength = 0.25f }; + var fbm = span_fbm(path, stage, octaves, lacunarity, gain); + var ridged = span_ridged(path, stage, octaves, lacunarity, gain); + Assert.True(SameBits(fbm.field, fbm_grad(p, octaves, lacunarity, gain)), $"span_fbm at {p}, {octaves} octaves: {fbm.field} is not fbm_grad {fbm_grad(p, octaves, lacunarity, gain)}"); + Assert.True(SameBits(ridged.field, ridged_grad(p, octaves, lacunarity, gain)), $"span_ridged at {p}, {octaves} octaves: {ridged.field} is not ridged_grad {ridged_grad(p, octaves, lacunarity, gain)}"); + Assert.Equal(0.0f, fbm.fold); + } + } + + /// + /// The gradient is the derivative of the value through the whole transform: with every parameter off its + /// identity (frequency of either sign, stretch both ways, axis, shift, offset, octaveStep, lacunarity and + /// gain of either sign, 4 to 8 octaves) field.xyz equals the fourth-order central difference of field.w + /// along each axis, within a tolerance built from the third-derivative pin (truncation) and float32 + /// rounding of the coordinates, and both agree with an independent octave sum. A dropped M or lacunarity + /// factor, M applied to the wrong side, a clamp on frequency, stretch or the octaveStep index, or a dropped + /// sign (abs of the scale or the amplitude) moves the field by the gradient itself. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpanStageGradientMatchesDifferences(bool ridged) + { + var random = new System.Random(ridged ? 0x6A1D : 0x6A1C); + var worst = 0.0; + var checkedCases = 0; + var spans = 0; + for (var k = 0; k < 6000 && spans < 80; k++) + { + var stage = Stage.DrawSmooth(random, 300.0f, 9.0f, 8, 4); + var q = Vector(random, -3.0f, 3.0f); + // The fold's crease at n = 0 is not differentiable: keep every octave's stencil clear of it. + if (ridged && Enumerable.Range(0, stage.Octaves).Any(o => abs(snoise(stage.Point(q, o))) < 0.15f)) + continue; + spans++; + var span = Span.Draw(random, q, 0.1f, 0.1f); + SpanNoise At(float3 p) + { + var path = span.Path; + path.midpoint = p; + return ridged ? stage.Ridged(path) : stage.Fbm(path); + } + + var centre = At(q); + // The value and gradient from an independent assembly of the octaves: pins shift, offset, octaveStep, + // M, lacunarity and gain themselves, which a value/gradient self-consistency check cannot see. + var matrix = stage.Matrix(); + var referenceValue = 0.0; + var referenceGradient = new double[3]; + for (var o = 0; o < stage.Octaves; o++) + { + var n = snoise_grad(stage.Point(q, o)); + var sign = ridged ? -Math.Sign(n.w) : 1.0; + referenceValue += stage.Amplitude(o) * (ridged ? 1.0 - Math.Abs(n.w) : n.w); + var g = new double[] { n.x, n.y, n.z }; + for (var i = 0; i < 3; i++) + for (var j = 0; j < 3; j++) + referenceGradient[i] += sign * stage.Amplitude(o) * stage.Scale(o) * matrix[i, j] * g[j]; + } + + var scale = 0.0; + for (var o = 0; o < stage.Octaves; o++) + scale += Math.Abs(stage.Amplitude(o) * stage.Scale(o)) * stage.Norm(); + var amplitudes = Enumerable.Range(0, stage.Octaves).Sum(o => Math.Abs(stage.Amplitude(o))); + Assert.True(Math.Abs(referenceValue - centre.field.w) <= 2.0e-3 * (1.0 + amplitudes), $"stage {stage}: the value is {centre.field.w:R}, an independent octave sum gives {referenceValue:R}"); + for (var i = 0; i < 3; i++) + Assert.True(Math.Abs(referenceGradient[i] - centre.field[i]) <= 1.0e-2 * (1.0 + scale), $"stage {stage}: gradient {"xyz"[i]} is {centre.field[i]:R}, an independent octave sum gives {referenceGradient[i]:R}"); + // A step of 0.02 in the noise space of the fastest octave. + var fastest = stage.Norm() * Enumerable.Range(0, stage.Octaves).Max(o => Math.Abs(stage.Scale(o))); + var h = (float)(0.02 / fastest); + for (var axis = 0; axis < 3; axis++) + { + var e = new float3(axis == 0 ? h : 0.0f, axis == 1 ? h : 0.0f, axis == 2 ? h : 0.0f); + double F(float3 p) => At(p).field.w; + var step = (double)(q + e)[axis] - (q - e)[axis]; + var d1 = (F(q + e) - F(q - e)) / step; + var d2 = (F(q + e * 2.0f) - F(q - e * 2.0f)) / ((double)(q + e * 2.0f)[axis] - (q - e * 2.0f)[axis]); + var estimate = (4.0 * d1 - d2) / 3.0; + var tolerance = 4.0e-2 * scale; + var error = Math.Abs(estimate - centre.field[axis]); + worst = Math.Max(worst, error / scale); + Assert.True(error <= tolerance, $"stage {stage}: d/d{"xyz"[axis]} of the value is {estimate:R} by differences and {centre.field[axis]:R} by span_{(ridged ? "ridged" : "fbm")} (tolerance {tolerance:R})"); + checkedCases++; + } + } + + Assert.True(checkedCases >= 120, $"only {checkedCases} derivatives were compared"); + output.WriteLine($"IV-REPORT span gradient ({(ridged ? "ridged" : "fbm")}): {checkedCases} derivatives, worst error {worst:R} of the summed octave gain"); + } + + /// + /// stretch 1, or a zero axis, is the isotropic stage bit for bit whatever the other of the two is; a non-unit + /// axis is its normalisation (the tested axes normalise exactly); a stretch that does apply changes the + /// result. + /// + [Fact] + public void SpanStageStretchAndAxis() + { + var random = new System.Random(0x57E7C4); + for (var k = 0; k < 60; k++) + { + var stage = Stage.DrawSmooth(random, 300.0f, 15.0f, 3, 3); + var q = Vector(random, -5.0f, 5.0f); + var span = Span.Draw(random, q, 0.2f, 0.3f); + var plain = stage with { S = stage.S with { stretch = 1.0f } }; + var noAxis = stage with { S = stage.S with { axis = None, stretch = 3.0f } }; + var reference = plain.Ridged(span.Path); + var isotropic = (plain with { S = plain.S with { axis = None } }).Ridged(span.Path); + var viaNoAxis = noAxis.Ridged(span.Path); + var narrow = (stage with { S = stage.S with { axis = None, stretch = 0.25f } }).Ridged(span.Path); + Assert.True(SameBits(reference.field, isotropic.field) && reference.curvature == isotropic.curvature && reference.fold == isotropic.fold, "stretch 1 with an axis differs from no axis"); + Assert.True(SameBits(isotropic.field, viaNoAxis.field) && isotropic.curvature == viaNoAxis.curvature && isotropic.fold == viaNoAxis.fold, "a zero axis with stretch 3 differs from the isotropic stage"); + Assert.True(SameBits(isotropic.field, narrow.field) && isotropic.curvature == narrow.curvature && isotropic.fold == narrow.fold, "a zero axis with stretch 0.25 differs from the isotropic stage"); + + var unit = stage with { S = stage.S with { axis = new float3(0.0f, 1.0f, 0.0f) } }; + var scaled = stage with { S = stage.S with { axis = new float3(0.0f, 2.0f, 0.0f) } }; + var a = unit.Fbm(span.Path); + var b = scaled.Fbm(span.Path); + Assert.True(SameBits(a.field, b.field) && a.curvature == b.curvature, "a non-unit axis differs from its normalisation"); + var stretched = unit.Fbm(span.Path); + Assert.False(SameBits(stretched.field, (unit with { S = unit.S with { stretch = 1.0f } }).Fbm(span.Path).field), "a stretch of " + unit.S.stretch + " did not change the field"); + } + } + + // The coordinate bands the enclosure tests draw: a large offset (0 keeps the drawn one) lifts S_o through + // the decades; Bucket names the decade a span's largest S_o is in. + private static readonly float[] OffsetScales = { 0.0f, 40.0f, 400.0f, 4.0e3f, 4.0e4f, 4.0e5f, 4.0e6f }; + + private static int Bucket(double magnitude) => magnitude < 1.0e2 ? 0 : magnitude < 1.0e3 ? 1 : magnitude < 1.0e4 ? 2 : magnitude < 1.0e5 ? 3 : magnitude < 1.0e6 ? 4 : 5; + + // byStep lifts the coordinate through octaveStep (octave o adds o octaveStep) instead of offset. + private static Stage Banded(System.Random random, float offsetScale, int octaves, bool byStep) + { + var stage = Stage.Draw(random, octaves); + if (offsetScale > 0.0f) + stage = stage with { S = byStep ? stage.S with { octaveStep = Vector(random, -offsetScale, offsetScale) } : stage.S with { offset = Vector(random, -offsetScale, offsetScale) } }; + return stage; + } + + private static void CheckCurvature(Stage stage, bool ridged, Span span, SpanNoise result, int samples, ref double tightest, ref int checkedPoints) + { + for (var s = 0; s < samples; s++) + { + var t = span.Path.halfLength * (2.0f * s / (samples - 1) - 1.0f); + var norm = SymmetricNorm(FieldHessian(stage, span.At(t), ridged)); + Assert.True(norm <= result.curvature * (1.0 + 1.0e-3), $"stage {stage}, span {span}: the Hessian norm at t = {t:R} is {norm:R}, above curvature {result.curvature:R}"); + tightest = Math.Min(tightest, result.curvature > 0.0 ? 1.0 - norm / result.curvature : 1.0); + checkedPoints++; + } + } + + /// + /// span-bounds-enclose, curvature: over seeded spans the operator norm of the exact field q-Hessian (chain + /// rule from the double kernel's Hessian, every octave point in double, M and the octave scales) at 64 + /// points per span never exceeds curvature, in the local regime (short spans, where the third-derivative + /// term is the bound) and the global one (long spans, where SNOISE_HESSIAN is), and in every coordinate + /// band from S_o under 1e2 to over 1e6, where the float32 kernel is no longer the exact one; at + /// halfLength 0 curvature is the widened local tier, the Frobenius norm of the midpoint Hessians plus + /// e_h, capped at the pin, scaled by amplitude, f_o^2 and |M|^2; and a span through MeasureHessian's + /// witness stays enclosed in 200 directions, which the Frobenius norm at the midpoint alone does not + /// guarantee. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpanCurvatureEnclosesTheHessianAlongTheSpan(bool ridged) + { + var random = new System.Random(ridged ? 0xC0DE2 : 0xC0DE1); + var tightest = 1.0; + var checkedPoints = 0; + var buckets = new int[6]; + foreach (var offsetScale in OffsetScales) + foreach (var halfLength in new[] { 0.02f, 0.5f, 8.0f }) + for (var k = 0; k < 8; k++) + { + var stage = Banded(random, offsetScale, 3, k % 2 == 1); + var span = Span.Draw(random, Vector(random, -30.0f, 30.0f), halfLength, Uniform(random, 0.0f, 2.0f)); + var result = ridged ? stage.Ridged(span.Path) : stage.Fbm(span.Path); + buckets[Bucket(stage.MaxMagnitude(span.Path))]++; + CheckCurvature(stage, ridged, span, result, 64, ref tightest, ref checkedPoints); + } + + Assert.True(buckets.All(count => count >= 3), $"the coordinate bands drawn are {string.Join(", ", buckets)} spans: a band is not reached"); + + // halfLength 0: the widened local tier. + for (var k = 0; k < 40; k++) + { + var stage = Stage.DrawSmooth(random, 100.0f, 90.0f, 3); + var q = Vector(random, -30.0f, 30.0f); + var span = Span.Draw(random, q, 0.0f, 1.0f); + var result = ridged ? stage.Ridged(span.Path) : stage.Fbm(span.Path); + var expected = 0.0; + for (var o = 0; o < stage.Octaves; o++) + { + var h = snoise_hessian(stage.Point(q, o)); + var frobenius = Math.Sqrt(Enumerable.Range(0, 9).Sum(i => (double)h[i / 3][i % 3] * h[i / 3][i % 3])); + var slack = Math.Pow(2.0, -10) + SNOISE_THIRD * Math.Pow(2.0, -20) * stage.Magnitude(span.Path, o); + expected += Math.Abs(stage.Amplitude(o)) * stage.Scale(o) * stage.Scale(o) * stage.Norm() * stage.Norm() * Math.Min(SNOISE_HESSIAN, frobenius + slack); + } + + Assert.InRange(result.curvature, expected * (1.0 - 1.0e-3), expected * (1.0 + 1.0e-3)); + } + + // Through the Hessian witness: one octave at the identity transform, the span's midpoint 0.1 to 0.5 away, + // moving toward the witness at speed 1, so the witness lies on the span. + var witness = new float3(-13.7920685f, -9.672134f, 61.563934f); + var identity = new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + var through = 0.0; + for (var k = 0; k < 200; k++) + { + var u = UnitVector(random); + var distance = Uniform(random, 0.1f, 0.5f); + var span = new Span(new SpanPath { midpoint = witness + u * distance, velocity = -u, speed = 1.0f, acceleration = 0.0f, halfLength = 0.6f }, None); + var result = ridged ? identity.Ridged(span.Path) : identity.Fbm(span.Path); + CheckCurvature(identity, ridged, span, result, 64, ref tightest, ref checkedPoints); + through = Math.Max(through, SymmetricNorm(FieldHessian(identity, witness, ridged))); + } + + Assert.True(through > 40.0, $"the witness Hessian norm is {through:R}: the through-the-witness spans do not reach the curvature"); + output.WriteLine($"IV-REPORT span curvature ({(ridged ? "ridged" : "fbm")}): {checkedPoints} points enclosed, bands {string.Join("/", buckets)}, least relative slack {tightest:R}, witness norm {through:R}"); + } + + /// + /// local-tier-coefficients-unpinned: a fixed span (found by an audit, kept) at SpanStage.Identity, one + /// octave, where the Hessian's operator norm at the end of the span (11.768) exceeds the local tier built + /// with half of SNOISE_THIRD's term (about 10.1) and the exact norm along the 64 sampled points stays + /// under curvature (12.526 before the allowance). + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpanCurvatureEnclosesThePinnedRankOneSpan(bool ridged) + { + var stage = new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + var span = new Span(new SpanPath { midpoint = new float3(144.93297f, -286.08800f, -53.59360f), velocity = new float3(-0.02139f, 0.17122f, -0.98500f), speed = 1.0f, acceleration = 0.0f, halfLength = 0.01f }, None); + var result = ridged ? stage.Ridged(span.Path) : stage.Fbm(span.Path); + var tightest = 1.0; + var points = 0; + CheckCurvature(stage, ridged, span, result, 64, ref tightest, ref points); + var end = SymmetricNorm(FieldHessian(stage, span.At(span.Path.halfLength), ridged)); + Assert.True(end > 10.5 && result.curvature >= end, $"the end-of-span norm is {end:R} and curvature {result.curvature:R}: the span no longer pins the local tier"); + } + + // Octave o's amplitude-weighted value (n_o gain^o, or gain^o (1 - |n_o|) for ridged) at `point`, read from + // production: the difference of span_fbm's (span_ridged's) field at o + 1 and at o octaves. + private static double[] OctaveValues(Stage stage, float3 point, bool ridged) + { + var path = PathAt(point); + var values = new double[stage.Octaves]; + var previous = 0.0; + for (var k = 1; k <= stage.Octaves; k++) + { + var total = ridged ? stage.Ridged(path, k).field.w : stage.Fbm(path, k).field.w; + values[k - 1] = total - previous; + previous = total; + } + + return values; + } + + // The departure of the octave sum from its midpoint branch, sum_o gain^o (s_o n_o - |n_o|) with s_o the + // sign of n_o at the midpoint, peaked over `samples` points of the span: Float for the field span_ridged + // and span_fbm themselves evaluate (read octave by octave through OctaveValues), Exact for the double + // kernel. Rounding bounds what the float differences of the accumulating sums can lose. + private static (double Float, double Exact, double Rounding) BranchDeparture(Stage stage, Span span, int samples) + { + var amplitudes = new float[stage.Octaves]; + var weight = 1.0f; + var sum = 0.0; + for (var o = 0; o < stage.Octaves; o++) + { + amplitudes[o] = weight; + sum += Math.Abs(weight); + weight *= stage.Gain; + } + + var middle = OctaveValues(stage, span.Path.midpoint, false); + var signs = Enumerable.Range(0, stage.Octaves).Select(o => Math.Sign(middle[o]) * Math.Sign(amplitudes[o])).ToArray(); + var peakFloat = 0.0; + var peakExact = 0.0; + for (var s = 0; s < samples; s++) + { + var t = span.Path.halfLength * (2.0f * s / (samples - 1) - 1.0f); + var point = span.At(t); + var n = OctaveValues(stage, point, false); + var r = OctaveValues(stage, point, true); + var departureFloat = 0.0; + var departureExact = 0.0; + for (var o = 0; o < stage.Octaves; o++) + { + departureFloat += signs[o] * n[o] - (amplitudes[o] - r[o]); + var x = stage.PointD(point, o); + var exact = NoiseBoundTests.ValueDouble(x[0], x[1], x[2]); + departureExact += stage.Amplitude(o) * (signs[o] * exact - Math.Abs(exact)); + } + + peakFloat = Math.Max(peakFloat, Math.Abs(departureFloat)); + peakExact = Math.Max(peakExact, Math.Abs(departureExact)); + } + + return (peakFloat, peakExact, stage.Octaves * Math.Pow(2.0, -22) * sum); + } + + // Newton steps from q to the exact kernel's n = 0 (the float gradient steers, the double value decides). + private static float3 ProjectToZero(float3 q) + { + for (var iteration = 0; iteration < 4; iteration++) + { + var n = snoise_grad(q); + var gg = (double)n.x * n.x + (double)n.y * n.y + (double)n.z * n.z; + if (gg < 1.0e-12) + break; + var v = NoiseBoundTests.ValueDouble(q.x, q.y, q.z); + q = new float3((float)(q.x - v * n.x / gg), (float)(q.y - v * n.y / gg), (float)(q.z - v * n.z / gg)); + } + + return q; + } + + /// + /// span-bounds-enclose, fold: for span_ridged, the value's departure from the midpoint branch (each + /// octave's gain^o (s n - |n|), s the sign of n at the midpoint, summed) never exceeds fold at 64 points of + /// every span, measured twice: for the float field span_ridged itself evaluates at the path point, and for + /// the exact octave sum (double kernel); in every coordinate band from S_o under 1e2 to over 1e6. fold is + /// positive on spans that do cross zero (and the departure there is not trivially zero), exactly 0 for + /// span_fbm and when no octave can reach zero. + /// + [Fact] + public void SpanFoldEnclosesTheBranchDeviation() + { + var random = new System.Random(0xF01D); + var crossing = 0; + var largest = 0.0; + var tightestFloat = 1.0; + var tightestExact = 1.0; + var buckets = new int[6]; + foreach (var offsetScale in OffsetScales) + for (var k = 0; k < 60; k++) + { + var stage = Banded(random, offsetScale, 3, k / 2 % 2 == 1); + var halfLength = new[] { 0.02f, 0.1f, 0.5f, 2.0f }[random.Next(4)]; + // Draw midpoints with small noise values so spans cross zero, as well as arbitrary ones. + var q = Vector(random, -30.0f, 30.0f); + if (k % 2 == 0) + { + var best = q; + var smallest = float.MaxValue; + for (var trial = 0; trial < 40; trial++) + { + var candidate = Vector(random, -30.0f, 30.0f); + var value = abs(snoise(stage.Point(candidate, 0))); + if (value < smallest) + (smallest, best) = (value, candidate); + } + + q = best; + } + + var span = Span.Draw(random, q, halfLength, Uniform(random, 0.0f, 2.0f)); + buckets[Bucket(stage.MaxMagnitude(span.Path))]++; + var fold = stage.Ridged(span.Path).fold; + Assert.Equal(0.0f, stage.Fbm(span.Path).fold); + var (peakFloat, peakExact, rounding) = BranchDeparture(stage, span, 64); + Assert.True(peakFloat <= fold * (1.0 + 1.0e-3) + 1.0e-6 + rounding, $"stage {stage}, span {span}: the float branch departure is {peakFloat:R}, above fold {fold:R}"); + Assert.True(peakExact <= fold * (1.0 + 1.0e-3) + 1.0e-6, $"stage {stage}, span {span}: the exact branch departure is {peakExact:R}, above fold {fold:R}"); + if (fold > 0.0) + crossing++; + largest = Math.Max(largest, Math.Max(peakFloat, peakExact)); + if (fold > 0.0 && peakFloat > 0.0) + tightestFloat = Math.Min(tightestFloat, peakFloat / fold); + if (fold > 0.0 && peakExact > 0.0) + tightestExact = Math.Min(tightestExact, peakExact / fold); + } + + Assert.True(buckets.All(count => count >= 3), $"the coordinate bands drawn are {string.Join(", ", buckets)} spans: a band is not reached"); + Assert.True(crossing >= 100, $"only {crossing} of {OffsetScales.Length * 60} spans had a positive fold"); + Assert.True(largest > 0.1, $"the largest branch departure drawn is {largest:R}: the check has no teeth"); + output.WriteLine($"IV-REPORT span fold: {crossing} spans with fold > 0, bands {string.Join("/", buckets)}, largest departure {largest:R}, tightest departure / fold {tightestFloat:R} (float) {tightestExact:R} (exact)"); + + // An octave that cannot reach zero: a midpoint where |n| is large and a short span give fold exactly 0. + var calm = new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + var found = 0; + for (var k = 0; k < 400 && found < 20; k++) + { + var q = Vector(random, -30.0f, 30.0f); + if (abs(snoise(q)) < 0.6f) + continue; + found++; + Assert.Equal(0.0f, calm.Ridged(new SpanPath { midpoint = q, velocity = new float3(1.0f, 0.0f, 0.0f), speed = 1.0f, acceleration = 0.0f, halfLength = 0.01f }).fold); + } + + Assert.True(found >= 5, $"only {found} calm midpoints found"); + } + + /// + /// The Gl reach term over a long span: a straight path from rest along -s grad n (s the sign of n at the + /// midpoint), identity stage, one octave, half-length 6, found by an audit and kept. The path reaches + /// n = 0 and the branch departure (about 0.387) is covered only if the gradient bound grows by + /// Hl nu halfLength over the span; a reach that shrinks with halfLength (nu / halfLength) puts fold at + /// 0.384 and fails here, as does any reach right only for half-lengths up to sqrt 2. + /// + [Fact] + public void SpanFoldEnclosesTheLongPathFromRest() + { + var stage = new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + const float halfLength = 6.0f; + const float acceleration = 0.0055555557f; + var span = new Span(new SpanPath { midpoint = new float3(7.634474f, 24.228996f, -3.7595012f), velocity = None, speed = acceleration * halfLength, acceleration = acceleration, halfLength = halfLength }, new float3(-0.9375727f, -0.07116994f, 0.3404294f) * acceleration); + var fold = stage.Ridged(span.Path).fold; + var (departureFloat, departureExact, rounding) = BranchDeparture(stage, span, 129); + Assert.True(Math.Max(departureFloat, departureExact) > 0.3, $"the branch departure is {Math.Max(departureFloat, departureExact):R}: the span no longer crosses zero"); + Assert.True(departureFloat <= fold * (1.0 + 1.0e-3) + 1.0e-6 + rounding, $"the float branch departure is {departureFloat:R}, above fold {fold:R}"); + Assert.True(departureExact <= fold * (1.0 + 1.0e-3) + 1.0e-6, $"the exact branch departure is {departureExact:R}, above fold {fold:R}"); + } + + /// + /// A midpoint within float noise of n = 0, on a path that does not move: halfLength 0 (a point), and + /// halfLength 1 under a speed bound far above the path's motion (speed is an upper bound, so that is a + /// legal SpanPath, and composed chains pass loose ones). The exact kernel's sign at such a midpoint is + /// opposite to the float one in about a fifth of the draws, so fold must still cover the exact + /// departure there: a fold forced to 0 at halfLength 0, or an S_o whose reach term drives e_n below + /// zero under the loose bound, leaves it 0 and fails. span_fbm's fold is 0 throughout. + /// Near the origin S_o is tiny (scales 1e-4 and 1e-2 of the coordinate), and only the value allowance's + /// absolute term 2^-14 carries the kernel's own rounding: the exact departure there is the coordinate's + /// rounding times the gradient (1e-11 to 1e-9), so the exact check's additive tolerance is 1e-13, under it. + /// A larger tolerance absorbs a dropped absolute term whole. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpanFoldEnclosesTheExactBranchAtANearZeroMidpoint(bool loose) + { + var random = new System.Random(loose ? 0x100E : 0x4A11); + var stage = new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + var crossing = 0; + foreach (var scale in new[] { 1.0e-4f, 1.0e-2f, 30.0f, 300.0f, 3000.0f }) + for (var k = 0; k < 300; k++) + { + var q = ProjectToZero(Vector(random, -scale, scale)); + var speed = loose ? 4.0f * (abs(q.x) + abs(q.y) + abs(q.z) + 20.0f) : 0.0f; + var span = new Span(new SpanPath { midpoint = q, velocity = None, speed = speed, acceleration = 0.0f, halfLength = loose ? 1.0f : 0.0f }, None); + var fold = stage.Ridged(span.Path).fold; + var (departureFloat, departureExact, rounding) = BranchDeparture(stage, span, 2); + Assert.Equal(0.0f, stage.Fbm(span.Path).fold); + Assert.True(departureExact <= fold * (1.0 + 1.0e-3) + 1.0e-13, $"midpoint {q}, speed {speed:R}: the exact branch departure is {departureExact:R}, above fold {fold:R}"); + Assert.True(departureFloat <= fold * (1.0 + 1.0e-3) + 1.0e-9 + rounding, $"midpoint {q}, speed {speed:R}: the float branch departure is {departureFloat:R}, above fold {fold:R}"); + if (departureExact > 0.0) + crossing++; + } + + Assert.True(crossing >= 80, $"only {crossing} of 1500 midpoints had an exact sign opposite to the float one: the check has no teeth"); + } + + /// + /// The step term of S_o, alone: an octaveStep of random direction and magnitude 1e3 to 1e5 (log-uniform) at + /// the identity stage, two octaves, the midpoint moved until octave 1's coordinate (2 q + step) sits on + /// the kernel's n = 0 (a Newton projection of that coordinate, then q = (x - step) / 2). There the + /// coordinate's float rounding (ulp 0.004 to 0.008) is what separates the float kernel from the exact one, + /// and only |o octaveStep|_1 in S_o carries it: the exact branch departure stays within fold, span_fbm's + /// fold is 0, and at least 100 draws have an exact sign opposite to the float one. Without the step term + /// fold is the 2^-14 and the small-coordinate rounding alone, and the departure passes it. + /// + [Fact] + public void SpanFoldEnclosesTheExactBranchAtLargeOctaveSteps() + { + var random = new System.Random(0x57E9); + var crossing = 0; + for (var k = 0; k < 600; k++) + { + var step = UnitVector(random) * LogUniform(random, 1.0e3f, 1.0e5f); + var transform = SpanStage.Identity; + transform.octaveStep = step; + var stage = new Stage(transform, 2, 2.0f, 0.5f); + var q = (ProjectToZero(Vector(random, -30.0f, 30.0f) + step) - step) * 0.5f; + var span = new Span(new SpanPath { midpoint = q, velocity = None, speed = 0.0f, acceleration = 0.0f, halfLength = 0.0f }, None); + var fold = stage.Ridged(span.Path).fold; + var (_, departureExact, _) = BranchDeparture(stage, span, 2); + Assert.Equal(0.0f, stage.Fbm(span.Path).fold); + Assert.True(departureExact <= fold * (1.0 + 1.0e-3) + 1.0e-9, $"octaveStep {step}, midpoint {q}: the exact branch departure is {departureExact:R}, above fold {fold:R}"); + if (departureExact > 0.0) + crossing++; + } + + Assert.True(crossing >= 100, $"only {crossing} of 600 midpoints had an exact sign opposite to the float one: the check has no teeth"); + } + + /// + /// The fold's acceleration term, alone, at the identity stage and under a transform (frequency 2.5, stretch + /// 0.5, shift and offset): the midpoint velocity is zero and the acceleration points along the gradient + /// (in q), against the sign of n, so n reaches zero and crosses it on the way to the span's ends by + /// acceleration only. Without the acceleration term, with the gradient bound under the gradient the path + /// reaches, or with the transform's norm f_o |M| misplaced in it, the departure from the midpoint branch + /// exceeds fold. + /// + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SpanFoldCoversAccelerationAlone(bool transformed) + { + var random = new System.Random(transformed ? 0xACCE2 : 0xACCE1); + var stage = transformed + ? new Stage(new SpanStage { frequency = 2.5f, stretch = 0.5f, axis = new float3(0.0f, 1.0f, 0.0f), shift = new float3(0.3f, -1.2f, 2.1f), offset = new float3(5.0f, -3.0f, 7.0f), octaveStep = None }, 1, 2.0f, 0.5f) + : new Stage(SpanStage.Identity, 1, 2.0f, 0.5f); + var m = stage.Matrix(); + var cases = 0; + var crossings = 0; + var tightest = double.MaxValue; + for (var trial = 0; trial < 400_000 && cases < 150; trial++) + { + var q = Vector(random, -60.0f, 60.0f); + var n = snoise_grad(stage.Point(q, 0)); + var gradient = new float3((float)(m[0, 0] * n.x + m[0, 1] * n.y + m[0, 2] * n.z), (float)(m[1, 0] * n.x + m[1, 1] * n.y + m[1, 2] * n.z), (float)(m[2, 0] * n.x + m[2, 1] * n.y + m[2, 2] * n.z)); + var halfLength = Uniform(random, 0.05f, 0.15f); + var acceleration = Uniform(random, 4.0f, 12.0f); + // n at the midpoint close enough to zero that the acceleration alone carries it across. + if (length(gradient) < 1.5f || abs(n.w) > 0.4f * length(gradient) * acceleration * halfLength * halfLength * 0.5f) + continue; + var c = -(float)Math.Sign(n.w) * acceleration * normalize(gradient); + var span = new Span(new SpanPath { midpoint = q, velocity = None, speed = acceleration * halfLength, acceleration = acceleration, halfLength = halfLength }, c); + var fold = stage.Ridged(span.Path).fold; + var departure = 0.0; + for (var s = 0; s < 64; s++) + { + var value = (double)snoise(stage.Point(span.At(halfLength * (2.0f * s / 63 - 1.0f)), 0)); + departure = Math.Max(departure, Math.Abs(Math.Sign(n.w) * value - Math.Abs(value))); + } + + if (departure > 0.0) + crossings++; + Assert.True(departure <= fold * (1.0 + 1.0e-3), $"q {q}, acceleration {acceleration:R}, half-length {halfLength:R}: the branch departure is {departure:R}, above fold {fold:R}"); + if (departure > 0.0) + tightest = Math.Min(tightest, departure / fold); + cases++; + } + + Assert.True(crossings >= 100, $"only {crossings} of {cases} paths crossed zero by acceleration alone"); + output.WriteLine($"IV-REPORT span fold, acceleration only ({(transformed ? "transformed" : "identity")}): {crossings} of {cases} paths cross zero, tightest departure / fold {tightest:R}"); + } + + /// + /// The fold's velocity term under a stretch: the velocity runs along the stretched axis (M scales it by + /// 1/stretch), the midpoint is where n reaches zero inside the span, so the departure is about twice + /// |grad n . w| halfLength and a velocity stretched the wrong way (or not at all) puts fold under it. + /// + [Theory] + [InlineData(0.25f)] + [InlineData(0.5f)] + [InlineData(4.0f)] + public void SpanFoldCoversTheStretchedVelocity(float stretch) + { + var random = new System.Random(0x57E1C + (int)(stretch * 8.0f)); + var cases = 0; + var crossings = 0; + var tightest = double.MaxValue; + for (var trial = 0; trial < 400_000 && cases < 60; trial++) + { + var axis = UnitVector(random); + var stage = new Stage(new SpanStage { frequency = Uniform(random, 0.5f, 2.0f), stretch = stretch, axis = axis, shift = Vector(random, -4.0f, 4.0f), offset = Vector(random, -4.0f, 4.0f), octaveStep = None }, 1, 2.0f, 0.5f); + var q = Vector(random, -30.0f, 30.0f); + var velocity = axis * Uniform(random, 0.5f, 2.0f); + var halfLength = 0.05f; + var path = new SpanPath { midpoint = q, velocity = velocity, speed = length(velocity), acceleration = 0.0f, halfLength = halfLength }; + var n = snoise_grad(stage.Point(q, 0)); + var m = stage.Matrix(); + var w = new double[3]; + for (var i = 0; i < 3; i++) + w[i] = m[i, 0] * velocity.x + m[i, 1] * velocity.y + m[i, 2] * velocity.z; + var along = Math.Abs(n.x * w[0] + n.y * w[1] + n.z * w[2]); + if (along < 2.0 || Math.Abs(n.w) > 0.5 * along * halfLength) + continue; + var fold = stage.Ridged(path).fold; + var departure = 0.0; + for (var s = 0; s < 64; s++) + { + var t = halfLength * (2.0f * s / 63 - 1.0f); + var value = (double)snoise(stage.Point(q + velocity * t, 0)); + departure = Math.Max(departure, Math.Abs(Math.Sign(n.w) * value - Math.Abs(value))); + } + + if (departure > 0.0) + crossings++; + Assert.True(departure <= fold * (1.0 + 1.0e-3), $"stage {stage}, q {q}: the branch departure is {departure:R}, above fold {fold:R}"); + if (departure > 0.0) + tightest = Math.Min(tightest, departure / fold); + cases++; + } + + Assert.True(crossings >= 30, $"only {crossings} of {cases} paths crossed zero along the stretched velocity"); + output.WriteLine($"IV-REPORT span fold, stretched velocity {stretch}: {crossings} of {cases} paths cross zero, tightest departure / fold {tightest:R}"); + } + + /// + /// Both bounds are sums over the octaves, weighted by |gain|^o: for a stage of several octaves, + /// curvature and fold equal the weighted sum of the bounds of each octave taken as a stage of its own, + /// to within the rounding of forming the octave's coordinate another way. A weight misplaced (gain + /// instead of |gain|, a division for a product), a dropped octave or a mis-scaled octave fails. + /// + [Fact] + public void SpanBoundsAreSumsOverTheOctaves() + { + var random = new System.Random(0x0C7A5); + var compared = 0; + for (var k = 0; k < 80; k++) + { + var stage = Stage.DrawSmooth(random, 300.0f, 90.0f, 5, 2); + var span = Span.Draw(random, Vector(random, -30.0f, 30.0f), new[] { 0.02f, 0.1f, 0.5f }[k % 3], Uniform(random, 0.0f, 2.0f)); + var whole = stage.Ridged(span.Path); + double curvature = 0.0, fold = 0.0; + for (var o = 0; o < stage.Octaves; o++) + { + var one = stage.OneOctave(o).Ridged(span.Path); + var weight = Math.Abs(stage.Amplitude(o)); + curvature += weight * one.curvature; + fold += weight * one.fold; + } + + Assert.InRange(whole.curvature, curvature * (1.0 - 5.0e-3) - 1.0e-4, curvature * (1.0 + 5.0e-3) + 1.0e-4); + Assert.InRange(whole.fold, fold * (1.0 - 5.0e-3) - 1.0e-4, fold * (1.0 + 5.0e-3) + 1.0e-4); + if (fold > 1.0e-3) + compared++; + } + + Assert.True(compared >= 20, $"only {compared} of 80 stages had a fold to compare"); + } +} diff --git a/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json b/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json index 605a60a6..32b0fcce 100644 --- a/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json +++ b/packages/cultmath/tests/CultMath.Tests/fixtures/glsl-parity.json @@ -26478,6 +26478,1054 @@ "bfdafc5c 404ff2aa bfb94d31 bfa4f005 bfc84ee5 c007567c -> 3f4bbcce", "c001df13 00000000 bfe57b78 bfe564b4 40132a80 3f82db94 -> 40128a76" ] + }, + { + "glsl": "float cultmath_integral_clamp_linear(float, float, float, float, float, float)", + "tolerance": "ulp-bounded, lo above hi and infinite bounds", + "cases": [ + "bf0b2a84 c0384244 ff800000 3f9a007c bfc80642 3f5f8200 -> be62fdf0", + "bf903e72 be013e00 ff800000 3f8f124e bfed81c9 4015f3f2 -> c09b895f", + "bffd4268 c0197f86 bfa70b86 7f800000 3ffe5ff8 403e73b0 -> bfa5200e", + "3fb686b4 3ff53690 3f75cfa4 7f800000 be0b6280 4027937e -> 4127a25f", + "bf6bb2f0 c0383fa6 ff800000 3e887584 3faa2b50 3fdde568 -> c009d85d", + "40753aee bf2c5544 bff5b17c 7f800000 3eb30210 402b63e8 -> 40d19059", + "3fd7e728 3f14eae8 3feba88f bff6ba08 bf9bd95e 3f98cd80 -> c094bc52", + "bf55b874 00000000 3f797988 bfe54df5 bfe638a0 3fe6ac28 -> c0ce6a80", + "3fefa58c 00000000 3f9f2a45 bee349b4 bf88b77e c008c9f7 -> 3ef305bd", + "bffd1d32 bff52532 3f14b7bc 7f800000 3f5aeb88 c03b152e -> c0937293", + "4009824e 4052b500 3fc0ea57 bd8e5390 bf7fd568 c0074df4 -> 3d9ea9bd", + "bfdc0802 4061136c be801d20 7f800000 4026e128 40380bc4 -> 4008013a", + "404ffab4 c04ecac4 3fc30ebc bf5da486 be657b80 bff22a7a -> 3fb8d46a", + "3e979c80 3fc0a8cc 3e84ffd0 7f800000 bff86de6 c01bf43d -> be03ea8c", + "c06f0bd2 bf78e25c ff800000 3f877bf0 be80f2f0 bf904c04 -> 402bb6a3", + "3fb318a4 00000000 3f1431a4 bf507b64 bfb71488 bdc34ac0 -> bf8b283f", + "bf87a14e 3dc402c0 ff800000 7f800000 3fe7c320 3fe21f08 -> 3d205f08", + "bfa11404 3f9901dc 3ebdb318 7f800000 3f28ace8 3f096210 -> bd39812e", + "c05920f3 bfc9fe26 3f1ee26e be71a1f0 bf82654b c01a834a -> 3ea89ad5", + "c00556d0 3feebf88 3ef66d70 7f800000 3ea319f0 3f547630 -> 3e7c0825", + "3f828c34 bfe13e0c bd879b50 7f800000 bf9ee05a 3ff55834 -> 40351a31", + "3f656160 bfe976f4 3ff8708d be711278 c00d46e0 4004c750 -> bf810990", + "4049950c 00000000 3f952f50 bc8be500 3fc0e688 3fcb9310 -> babaa746", + "3fabe24c c00711ba 3e068070 7f800000 bf63a110 bf5d45ac -> 3da264c8", + "c0233b98 3fff1294 ff800000 7f800000 402d3600 bf8aac5f -> 40626228", + "3feaba90 c053efca bfecf755 7f800000 40364634 bfffca21 -> c0e1bf93", + "4028f0cc c0612055 3f54dc54 7f800000 402ad238 40290ec6 -> bcbbaf79", + "beb04698 00000000 3f91dbd1 bf53ed66 c00b6465 3f074058 -> c00f62e0", + "406fa864 00000000 3f8b20ef 3e6939a0 bfea8224 3f191d90 -> 3f0db236", + "405fd6fe c0040094 ff800000 7f800000 4002336c be60d030 -> c06a8a31", + "bfb75c48 40201468 ff800000 7f800000 401b77ac 3f03cff8 -> c089cb06", + "40056938 3fe8379c ff800000 7f800000 3fa99ba0 3f9ad530 -> bf018a9f", + "c0556eb0 bc1a2300 3febcf2d bf9750a7 401435c0 c0068484 -> 40a71cf1", + "bef4a0d0 c0415561 bf7228f4 7f800000 403de0a8 bf882153 -> 3fc8f311", + "3e800c00 3e8da060 ff800000 3f7a9082 bda76760 3ff23f68 -> 3f7cfbd7", + "c0467619 00000000 3f4bbbfc bfd7b41d bfd61757 bde41880 -> c028612a", + "3ed56780 bf10add8 3dee9d20 7f800000 bfb672ae 3ffb8258 -> 3fbd0bef", + "4012d6b4 c06f1e30 ff800000 bf78a9fa bf853cf4 3f966510 -> c01493b8", + "c04d58e5 c06ee722 ff800000 3ed04950 3f141838 3fe7dd48 -> c1174a8e", + "3ea47f80 00000000 3f8a8816 3e8963e8 be6dc380 bf4225c8 -> be1096c4", + "4066fa60 c060a340 ff800000 7f800000 3f090cac bf6a8060 -> c0c6a530", + "c077191e 401e0304 3feefde4 bfc95e30 402dbf82 c0021a52 -> 40ef01d3", + "bfd20336 3e8d73f0 3f4e9f9a 3c550000 3f341614 3f8712b0 -> 3b95dd74", + "400b9982 401c9b94 3eb71990 7f800000 3f87b77c 3f41d668 -> bfaae6cb", + "3d15a000 c0799975 ff800000 beedd366 3e647880 bf775c28 -> 3f11e714", + "bf799da4 403f9dfa ff800000 bed08894 4012d1f4 3d588080 -> 3f70da61", + "3fbe1938 00000000 3ff40bd3 beaf5330 3f890abc 401b8f60 -> beee7080", + "3fbd70bc 3f7e1758 ff800000 7f800000 bef05ab8 4026cf76 -> 40fa0e87", + "c0229dc4 c036d32c ff800000 bf150af0 bffe5218 bfdccf14 -> be1c157f", + "bfe6ffe0 bf050038 3f01e041 bfac9dd0 c023de33 3ebccc20 -> c07cd000", + "3f687838 bd8e3b60 3f8f366c bff818ea c000ee81 3e4c9570 -> c08957e4", + "40722c5a 00000000 3fe691e1 be4fc540 3f4be2d8 3fb86d78 -> be05e3b8", + "3ff4b594 00000000 3f1dfd73 bf0697d8 3ffe2e30 c001ddfd -> 400718b2", + "c02fd649 3e5cc9a0 3fceabb6 bfb7fe6e c03c4bcd c0006605 -> bfac3327", + "c014e4f0 c0405a98 ff800000 3f66a948 c0105010 c016cee8 -> bdbb4834", + "bee72060 bedb74f0 3f0387d0 3e392a40 3e224820 c005bf10 -> bed02668", + "40665c3a 00000000 3fcf9ca4 bf880bb7 3d7a3a40 bedafe30 -> 3f05005c", + "3e912a30 bb7e1400 3ff1f167 bed87abc bfc5ea97 bf1a21fc -> becc6313", + "beb05d40 00000000 3fc08ab6 bdce7480 c004a2b4 bf4b4290 -> be03f88a", + "4007114a bf958182 bfa630e2 7f800000 3d523180 3f4c1688 -> 3f9a2e6d", + "bfc774c6 bfd46d30 ff800000 7f800000 bf0b93a0 3f89ac50 -> c04f49cb", + "c05a4f76 00000000 3faf89fd bfdd25e1 3f7c6fb0 be91f730 -> 400c8ee2", + "3db006c0 bf96efd4 ff800000 7f800000 3f98cc04 3fa107e0 -> bdb31de7", + "3fe5ebd4 402a5cb6 ff800000 7f800000 3fbd84a0 4036fb00 -> 412701cd", + "be130280 bf83421c 3f2d0fb4 bfc6d309 3fda2328 bfac537c -> 4097a0d3", + "bf998358 c07f2a7b bfdf42db 7f800000 3fe33530 3ec8fa80 -> 401a54fe", + "c003a422 c051b8fa 3fd169fb 3ec3f8b8 3ec1a460 3eb330b8 -> bc310236", + "c07db7cb 40652b7c bf8650a4 7f800000 3ee24aa0 bfbcc298 -> 4000b7e7", + "c050b426 c064494a ff800000 7f800000 be2c1ea0 3fda20ac -> c133bf2e", + "3ff5bf38 00000000 3febe24a bf246e24 bf8b52fe 3fee7658 -> bff2a785", + "3fef8560 00000000 3fd6bcb1 bf2f4aa8 be05c480 c018ada4 -> 3fc5a368", + "407e47fc bfee3d8c 3f3fd258 bef79ec8 be86f720 3f373aa8 -> bef28150", + "407a9322 4077a70c ff800000 3f16e5c0 4039278e 3e53b450 -> bfcaad7c", + "3ff74adc c074514c ff800000 7f800000 c036692f c00d50d4 -> 40ee1477", + "c0109599 407dcdfc ff800000 3e9826e8 3ff9ef34 bfbad3e8 -> 40f87d8c", + "c064d37e 3fabd540 ff800000 3f0b3400 403d2aee be4aadc0 -> 40ae0317", + "404dda66 405d6af2 bfecf98f 7f800000 3fdd9fe0 3f9e5abc -> c08419fa", + "3f8b94c0 00000000 3fd01cae bfabedbb c0053eb8 3f34f7f8 -> c06fbe1b", + "bfa782ce bfe55598 ff800000 7f800000 bfc85400 bf4eb930 -> 3f1e65b3", + "3fd827f8 c0721f90 ff800000 3faf1ee4 bf6c2dd0 bfd3bc9c -> bf801f26", + "3fe3ef4c 3ff5aa80 bd3e6540 7f800000 3b849000 c036e89e -> bf3e81f6", + "c02720c9 00000000 3fb39588 bffd0e72 bfa5ccee be3dcb80 -> c00c7148", + "4025b204 c0178ad4 ff800000 3f966f62 4035bc96 bf2b03dc -> 3fea7626", + "3f4c5f00 4027a106 bfc3f62d 7f800000 c02341c1 40397f36 -> 4127220a", + "4020b9d8 bfca59f4 ff800000 befd6a32 bffea84d 3fc48c50 -> bfdf5315", + "3fb6059c c03aeb32 ff800000 3f258852 403fb10c 4039b3a0 -> 3f2c3f36", + "3f66acd0 00000000 3f998fea bd8d6c10 3c347200 beab4b58 -> 3cc37c8b", + "bff98cd2 3fb23794 ff800000 3f9b5268 bfcfafe2 3e806180 -> c0ae1e58", + "3f8dc374 3f98a9f8 ff800000 7f800000 bb9b6000 bfe8226a -> bd2be404", + "3f974950 400380f8 ff800000 3d3dd240 c024f56a 400d0d90 -> c07f64b5", + "c07d5b40 bf3a9a3c bf194916 7f800000 3f22b430 bf990456 -> 3f8c5559", + "c07aaed0 402e17d8 ff800000 3fbe27ba bf0fb180 bee83ca0 -> bf121273", + "c00db16a 00000000 3f42cc80 bf57ab38 3ff8c090 3f2679ac -> 3f8b7054", + "3f2cbc60 00000000 3fa3b2db bfc92692 3e942528 c0390c40 -> 409ff36f", + "405fd66a c06cf16e 3e7196c0 7f800000 bf7c1f74 c0305c79 -> c193a29a", + "c044711a c031a2d6 ff800000 7f800000 bebb27d8 3f1155a0 -> c0481673", + "3cf92600 40353762 ff800000 beb7b1b2 bfe10d3c bfd7a9aa -> beb5ece8", + "bfbf100e 00000000 3f3505d8 bff3e8be 3ff63008 bfac1142 -> 40c7401d", + "3f207060 404afd72 bf21122c 7f800000 bf309db8 bfff02d4 -> 3f51c609", + "bf5fae3c 3f0789e0 3f6b63ec 7f800000 3fd8303c bfd41c44 -> c044e8b3", + "3d089500 bf897fbc ff800000 bf4a15df 402b3ed8 c0343436 -> 40c967b0", + "3fddab38 c00a9c2c 3f69bb9a bf8fd314 3fbe5748 3f862be0 -> 3efc747a", + "c061e050 bfa97d26 ff800000 3fda986a 3f9d338c bff7ceed -> 411af31e", + "404a3c54 404c5c1c bf6b4b74 7f800000 bf9c49de be6718b0 -> 3f58a2fb", + "406590ca 3f04dcb0 3fcaaecd bef9b444 4039241a bfe373d9 -> 4011c234", + "3fb551f8 c0020910 3fdfaf6f bfdeb6d5 bfe83ede 4005a37a -> c0d94966", + "c016a797 bfb2770e ff800000 7f800000 401cfafa bfa01d21 -> 413d2608", + "3f1c90a8 00000000 3fc3bac8 bf888bc0 3fe88cdc 3e344ba0 -> 3fe008e9", + "c0559cc6 bfb71176 3fed70d2 bf903083 3f4f3594 c023931e -> 40729e34", + "3fae840c 00000000 3f97115a 3e924de0 3d02eb80 3fb3f834 -> 3ec90777", + "3fc27378 3f494498 bf7d02b8 7f800000 c012410c 400ab734 -> 40d1dbb7", + "bf9e10e2 3f390bf0 ff800000 7f800000 3f972c38 3ed4cf28 -> 3f00e4ca", + "c07d7329 00000000 3f0da4a8 3d818360 c03ddfa2 bfbf8568 -> 3dbe7380", + "3e116060 00000000 3f420c52 3ec06c78 3f564cb8 bf92c392 -> bf3edb08", + "4063ad1e c07e35a3 ff800000 3f462e80 c00212a7 bf6ed200 -> 3f59e669", + "4044483e 402d13a2 3d50ef40 7f800000 402d46cc 40146fd2 -> c075046a", + "3fac2414 3f8b40ac be01e540 7f800000 bf682450 c02f685b -> 3dfedeea", + "c05e65ff c05526f6 3f909a1c bf042384 bfe455e8 bf6eb6bc -> bee10136", + "be776a10 403fd276 3f89268b bf2a53f8 3d3d1180 3e9358c0 -> be249f9f", + "4064ee2e 00000000 3f5e66be 3ec864c0 3e805e10 bf7f648c -> befa2911", + "4074ae32 00000000 3fcdee98 bf64354e bfffca31 3f252220 -> c016cfdc", + "bf87c254 00000000 3fc36d6f 3eaff918 bffe4cd0 400b1ff8 -> 3fb70933", + "c052c89e 4013111e ff800000 3f96e1b2 3f19be14 3f5ed320 -> bedd88e9", + "c009eb35 3fddee68 ff800000 3f044578 bf3ef864 bfa138ba -> 3ffff3a9", + "40691b74 00000000 3fe4e5aa bfb43ed8 3fe3f06c c01bd382 -> 40bdf578", + "3f830a4c c0531431 ff800000 be90363a 40370edc c03245db -> 4139b209", + "3fb8e818 c02ff12b 3fc3a58f bfcd1179 c018b019 3f67df5c -> c0a8bedc", + "3fc93094 3f22f3a0 ff800000 7f800000 4009c918 c01fb787 -> c0d99551", + "3fc13038 bee9dde8 3fcc6902 3dd5f6c0 3df0a1a0 3fcc1fc8 -> 3e1e094a", + "407033da 3f6849a8 bfce467d 7f800000 3fda1130 3fad13ac -> bfe73ad0", + "c031489e c07e39cd ff800000 7f800000 4019c98e c02a48fe -> 4136f1e3", + "c046dc6d 00000000 3fad6b49 bfd899d8 400b65bc bf10f6a4 -> 40949b6a", + "c05e533f c06d210a ff800000 7f800000 3f206210 be288850 -> 405b3391", + "bf7e85d0 40416500 3f8fdc10 be82a520 3fa35f7c c0056fbe -> 3f5b917e", + "40396ff6 402734d2 ff800000 3f60c942 bfc38654 c002fd20 -> 3f6b736f", + "404b1340 00000000 3fc2fb3c bfae7f84 3e430fe0 3e03e530 -> 3dac39e9", + "3f8f488c 00000000 3f6cafbe bf8aa584 3fbddcd4 bf809474 -> 402c76f4", + "40663df8 3f8cd5dc 3f9f57b4 bd8e8900 bfe9f0a6 3f955598 -> be556613", + "bf3bf700 4031fd74 ff800000 7f800000 40096f2c 40372942 -> 408e78dd", + "3f3342d0 c0272e74 ff800000 7f800000 bfeab9ec c0281aa2 -> c0a59170", + "3f3da9b8 00000000 3fb2875a bf319468 3fd6bb68 c000ec58 -> 4023e82d", + "40341d6e 40582d68 ff800000 bf1bc556 bfe0d240 bffe1562 -> 3f4d1a78", + "bf757580 c05a3300 ff800000 bea6f0e8 c02f51c4 c032f8fa -> 3c987746", + "3f86552c 3ed0c070 ff800000 7f800000 40146420 bfaf990a -> c092bb03", + "bdbde020 3f2abbb8 3f2459f1 bfe3ca0b 3f8583a4 3e88a890 -> 3fb0cd8b", + "bedefe38 3f839e38 ff800000 7f800000 40125092 3fb84be8 -> bfa05876", + "3ce15b00 c0108368 ff800000 3fc394ba 3feeb264 c0303e82 -> 3e29a2c0", + "bf995e24 bfd7be92 ff800000 3eddd4cc 3f4f9ff8 3fab76f4 -> bfcbaca5", + "404e6a02 401151c4 ff800000 7f800000 3ea4ac10 bf1f8304 -> c02e4f3d", + "bf0d3864 bfbd35da ff800000 7f800000 3f9e5764 3ff89eb8 -> c002f92a", + "3fb41270 3ee36b90 3ef72070 7f800000 40224ce2 403fee2c -> 3f9c3661", + "c044e618 3fe4f860 ff800000 bf3ab3d6 c02a551f bfc0667b -> c0fc1a14", + "3ea3d780 4021e21a ff800000 7f800000 403324b8 bff2fb70 -> c0db5ad7", + "3fd54e2c 3e944f40 ff800000 3fcca272 40391af2 bff0c14f -> c0e79db9", + "c01b99d7 3f90b97c ff800000 3edae39c 3ee34220 3e7c4700 -> 3ece6418", + "c01ae2cc 3f06c418 bebfa5e0 7f800000 c03305be c0029bb0 -> be90f9fd", + "bffa97b2 c0436485 bfbd3ae5 7f800000 3f685908 bf686f08 -> 3fe960f8", + "4052b922 3faad6ec ff800000 7f800000 be46c010 4001a130 -> 4120547c", + "c003292a 3ff31fd0 be378c10 7f800000 bed82bc8 3fdbb9f8 -> 3e008adc", + "c028fa52 402ef08c ff800000 3f970044 3fd06e10 401db5a4 -> 3f7c6de2", + "bf90e906 40570af6 3fe4ea7d beb7e66c bfa3f8a8 bef68628 -> be9308c8", + "3f59deb0 405c57b2 ff800000 bf4de4a9 403de08c bfad59ea -> 40993e1e", + "bfaf4754 bf8fbd24 3f25c0ac bf858e0c c03acd27 3fecabb0 -> c09f307e", + "bf90e182 40282582 3fc46df6 be554ce0 bf8271ba 3fe93108 -> bf177dcf", + "406af308 403a4800 ff800000 3f32aef0 bf8ba032 3fd28940 -> 3ff382a1", + "bf5d937c bfd430f6 ff800000 3f7dcd38 bedb1fd8 bfca9e2c -> bf3f7814", + "401da4d4 40116556 3f98ec71 be8d3d0c bfce7124 4012e7a8 -> bf89ff65", + "3fe228d8 00000000 3f9f5144 bd4de720 bfc2152a c003ee96 -> 3ce080a7", + "bfd65224 bf871dca bfc4fa72 7f800000 3e38c7c0 3f0a0458 -> bf0d4dd8", + "3eedae10 c0467403 ff800000 bead9056 3faca370 3fba4780 -> bed3e1b9", + "3f9285bc c035a27e ff800000 7f800000 3f8532a4 c0322863 -> c15d6539", + "400e0294 c0645562 ff800000 bf1e37fc 3f2ab790 bfa0f5e6 -> 3f983c47", + "406445f8 c056a1ed ff800000 7f800000 bf750180 3e926600 -> 40baa1c2", + "3f7ed1b0 3fd8acf4 3fdd35f6 bfab6e2e 3f9f492c bfa55d57 -> 405966f4", + "c073d5f9 4006818a ff800000 bf2ec36c 40021aee 3fc28b80 -> 3eb34fac", + "3fc831b0 00000000 3fdd735e bf1fdb88 bd8b8a80 bfb0546d -> 3f5152f5", + "c067b803 c053ee97 ff800000 7f800000 bf8d0f45 bf39c214 -> be65295f", + "40454632 3e33fe40 bef215e0 7f800000 3f873d84 bfc71e5b -> c0fdfdd3", + "c0371b4a 00000000 3f0d7f2c bf9b65f8 3f08eedc bd93ca60 -> 3f3cabee", + "4062a26e 4026084e 3f23621f bff89ca8 400e0fdc 3f65e738 -> 40244a41", + "bebe03d8 3d2fdd80 ff800000 3f835eb0 3f11c9b0 3eaa4138 -> 3daab3ea", + "c0657d7b 3e276860 bf506f04 7f800000 403635f6 4021d69e -> 3e84b269", + "bfafc7b8 bfc778be 3eb2bac8 7f800000 bf254a34 bf9c07fc -> be5744a1", + "c0249e78 bfae5444 bfb04f0c 7f800000 3f2a68e0 403dd784 -> c04acf36", + "c068736e bebe7aa8 ff800000 7f800000 402a1540 c01ed528 -> 4196ac89", + "3fd0127c bf5169a8 3fa4f77c bf8b48c4 3ff02d34 bfd3d383 -> 4075ecde", + "405aa754 c0423731 be096ae0 7f800000 3f1cc1f0 401cf950 -> 3e6661fd", + "3f86b240 3fabb848 ff800000 bf6ee45f 402ca76a 3f352178 -> 3fedb7fa", + "3ecdd950 00000000 3fd260dc bf811808 bfb00f52 400ccb1a -> c066c75e", + "bea6feb0 00000000 3fcca4b9 3ea249c0 bf2cfa20 3d67ae80 -> 3e6dac20", + "3e4a0840 00000000 3ffae0e6 be04b4c0 3e11a6d0 3fad4750 -> be20c604", + "c0799f76 bf85594e 3fe783e2 3ebf4b78 be03f160 bff50393 -> bf2ac2e6", + "bf8c7388 4032a74c ff800000 3fe1876e bfb9ecec bedcf780 -> c0738770", + "3e93efb0 be81bd00 ff800000 3ec55218 becf8b40 3e3f1150 -> 3e3fdd04", + "406241ca c05de18a ff800000 3e80529c bf271fe8 3e73c080 -> 3e64a338", + "c025c02c 00000000 3f5603c2 bf6d7634 c01aa5c0 bf56b0c0 -> bfbb5354", + "3fef160c c0039d40 ff800000 3f874ce4 3e683f80 c0037449 -> c01a4b84", + "40267ca2 00000000 3f31b7ee bfe701df bee92cf0 c01afc8e -> 40631bbc", + "befbf118 401fad5a ff800000 7f800000 bed93b70 c02a5de4 -> 411b762d", + "407c371e c07c0783 ff800000 3f5d20c4 3fa0bb70 c033ef8b -> c0447def", + "4029034e 403c0c0a 3feb36aa bfff3283 3fcad320 c01c200a -> 41005d70", + "3fd1dcc8 c06ea2f7 ff800000 3fff5a82 4038caf0 c03aedd4 -> 409ff98d", + "bfc1e3d2 3f8f931c bee4a428 7f800000 4004a430 4016cbd2 -> 3e8cbd70", + "4070c488 bffe9414 ff800000 7f800000 bf09ef98 3e37a380 -> 403d563f", + "bf8adb0a 00000000 3f530965 be6b63c8 c03047db 3e033610 -> bf29a108", + "3fceea28 3e77c340 ff800000 7f800000 401ff396 bf846d89 -> c0cad675", + "4057d9c8 00000000 3f937e61 bfe2a4ae 3d64f300 3e46d0e0 -> be7aafaf", + "c04cbc5e bf19041c ff800000 becedaf0 c00c7b5c bed40f78 -> c089dd29", + "c034a3ce c0451745 ff800000 3de07e90 c00b3cd8 3da72fe0 -> bfb2f3c5", + "bf2408a4 bfa6832c 3fe35a97 bf5850d4 3fdb6c9c 3fafe434 -> 3e93235a", + "3edb64e0 00000000 3f1f66ec 3eb80448 bfc4daee bf910b46 -> 3e14f87a", + "3fdc6cbc 40590126 ff800000 7f800000 c00231b2 c027f93b -> 406956a4", + "4040c116 40220f9e bfa796c8 7f800000 bf8e5c1a bed7c408 -> 3f3d3b10", + "3f86c7e4 00000000 3fb33aa0 bfc61af4 bf882bc6 c03e63cf -> 403d4a4a", + "40135a32 00000000 3f010b69 bfc53aac 401c8ada 3f067e58 -> 403d666e", + "3fb9ea54 3f667088 ff800000 7f800000 40126b4e bfaa9476 -> c0da143e", + "c0458ed9 40793090 bfaa3bb6 7f800000 bf67a36c 3fc9ac68 -> bf5677fd", + "40688054 c059af7c bfd79392 7f800000 3e2cf960 400c88b0 -> bddc33e8", + "4050b46a c008e7a1 bfc31ca2 7f800000 bbee3c00 3f8f245c -> 40154f21", + "3eca3830 3fddaec4 3fa6bd9f bfe8d46a 3f5c7528 c010e3f2 -> 40b5e713", + "c03e9e6b 40136fc6 3f655bb7 3e7a6900 c0033d1b 400e901c -> 3f85e95a", + "c06d2fc3 3f1626f8 3fa64533 3c6a4000 bebb86b0 bfc4f666 -> bc895477", + "400fca88 c034b1dd ff800000 7f800000 3e1b8dd0 3f763ae0 -> 3f0bd978", + "407c94a4 401ad33a bef86e10 7f800000 3f8a308c 401adac0 -> 412f5c0a", + "c03f7b9c bebc9828 3ffe5c68 befd807c bf8113ec 3ebca1e0 -> bf2e8422", + "4020cfe0 3fbfa9ec ff800000 bec74ee2 bf5da2c8 bec67c50 -> be3e93de", + "c00d2dee 3f50efe0 3fd8618f bfb19656 3f356f28 c025afbf -> 409266e9", + "bf44b364 c07447c3 bfd29b93 7f800000 bf8acc84 3fce3ab8 -> bf91d691", + "be9a1c78 befe9960 3f277228 7f800000 3e805e20 bfae4fde -> bf87017e", + "3f9e00fc bf88e102 ff800000 3eda1fc8 bff76283 3fee13f4 -> 3f76cb83", + "bf8ef440 bed711a0 ff800000 7f800000 3f32f3d4 be428170 -> 3f8b3ffe", + "be067930 bf9e417e bf853207 7f800000 bf648afc bf88e6d6 -> be43d727", + "bf7f9700 3fef7b1c 3f26c61f bfe0524f bf6d1eec c015e85e -> 401ed364", + "c03b0aad 3f01d8f0 bfb759c7 7f800000 403ced4e c01ba186 -> 40f6c684", + "bfe92a4c 00000000 3fe0c054 bff1c0f4 401ca664 3e9bbb10 -> 40818ca7", + "40158400 c07868c5 3fbaa390 3e7b5c90 3f0f2138 4035fb98 -> 3f0f8d26", + "c0252b55 402bdf3c ff800000 bf6bc7ac beec5098 bfd86ca4 -> 40d73441", + "c0439078 c02ab53c ff800000 3e5e0f60 3e709e60 c018c75b -> 401204ed", + "bff046aa c059b87e bf99fa11 7f800000 3f86a3f8 c030ae4e -> c0d2a314", + "40563928 bf14dc14 3ec4c038 7f800000 bfe97076 bed2e7d0 -> 40b49698", + "c014aa4e bdfe0340 3f3605dc bf8dcf77 bf297690 3e2aaf90 -> bf6b05c9", + "4006e3d2 3f612710 3f35e2c0 be124f38 c0008056 4023799c -> bf26dee6", + "bfce98fc 3cb4e000 ff800000 7f800000 c024ad6d c01767f0 -> beb1277d", + "401c9254 400d1d40 ff800000 7f800000 3fb53620 3edcd940 -> c08d3488", + "c0443ea5 40732a4e bfaecf9c 7f800000 c015d256 3f9feb58 -> c06ba7f1", + "3ff551d4 00000000 3f77cbec bef1ad68 bfb84901 3f451778 -> bf8580ed", + "3f38d8f0 00000000 3fd8c54d bfe14821 bf1befd8 3fb0f7f4 -> c06058a8", + "c033affa bf7d4dc8 ff800000 7f800000 40196ef2 3fa0ee0c -> 40a86678", + "bf2cc260 bf934a3e ff800000 bf714bc3 c01d504b 3ead2a20 -> c0291728", + "be5790e0 40534106 3f0bd833 bed0b664 befbb698 bf08982c -> 3c8c11c8", + "bfa83d6c 00000000 3fa24e71 be275838 bee3f830 bff605bb -> 3e77224e", + "4078f8f2 403b7202 bfe30e3b 7f800000 bdeac340 bf99fb7f -> c00891ab", + "400729d0 40616b48 ff800000 bf461e6f be2caae0 3ff7ba54 -> bfd06ba6", + "bf4c31ec 3ff690f8 ff800000 bedca326 c03b59a8 40086c3c -> c138ac1b", + "bda07260 3f5f9400 3f44ceb8 bf7d7ac4 3e564320 bf1a9360 -> 3f4e178c", + "bf878b18 c05db95e ff800000 3ff43ee2 3f871b38 bf1df154 -> 4042a94e" + ] + }, + { + "glsl": "float cultmath_integral_clamp_linear(float, float, float, float, float, float)", + "tolerance": "ulp-bounded, zero-length spans and flat infinite clamps", + "cases": [ + "80000000 80000000 3ff65b76 7f800000 40101a44 40101a44 -> 00000000", + "00000000 bffab8a4 7f800000 7f800000 3f12b010 3f12b010 -> 00000000", + "00000000 80000000 bf34a3ac 7f800000 3fd46400 3fd46400 -> 00000000", + "80000000 c00a0d51 7f800000 7f800000 bf9849a6 bf9849a6 -> 00000000", + "00000000 00000000 ff800000 7f800000 3fe4a0ec 3fe4a0ec -> 00000000", + "80000000 beb441d8 be195228 3fb78208 c015d454 c015d454 -> 00000000", + "80000000 00000000 3efc18a0 7f800000 c0157e7c c0157e7c -> 00000000", + "00000000 80000000 3fcb9924 3f93c620 bdbbe200 bdbbe200 -> 00000000", + "80000000 3f16a5d0 3ff1278c bfaa73fc 403c47f4 403c47f4 -> 00000000", + "bfeded14 80000000 ff800000 ff800000 400aa252 400aa252 -> 00000000", + "4042a300 3ec80180 ff800000 7f800000 c0369fbd c0369fbd -> 00000000", + "bf834bb8 3ff1675c 3fccd224 3eea4e38 bfe4046f bfe4046f -> 00000000", + "80000000 80000000 7f800000 bfb8ed52 c015deb4 c03bb881 -> 3f5abd11", + "00000000 c004ae4d 7f800000 7f800000 4038c578 4038c578 -> 00000000", + "00000000 c04d2c62 ff800000 bd754b20 3fcc4a60 3fcc4a60 -> 00000000", + "c0363eaa 80000000 be8c9ea8 ff800000 3fba9ac0 3fba9ac0 -> 00000000", + "80000000 80000000 7f800000 bf4c4cd0 c003ca24 c003ca24 -> 00000000", + "3f92b974 80000000 ff800000 ff800000 c008d69e c008d69e -> 00000000", + "00000000 40441954 ff800000 7f800000 40217b3a 40217b3a -> 00000000", + "80000000 00000000 ff800000 7f800000 3f8c7c70 3f8c7c70 -> 00000000", + "3ea22a30 00000000 3f5708fc 7f800000 c03b518b c03b518b -> 00000000", + "00000000 00000000 ff800000 ff800000 c00c95a0 bf5a3618 -> ff800000", + "80000000 80000000 ff800000 7f800000 3fb3af0c 400bf204 -> 80000000", + "4060b036 00000000 3fa51834 ff800000 c023687e bff93c2a -> ff800000", + "00000000 80000000 7f800000 ff800000 bfe50f6f bfe50f6f -> 00000000", + "80000000 80000000 bfbcb16e bf944da4 3f89a670 3f89a670 -> 00000000", + "00000000 00000000 beaae9fc ff800000 3f9468b8 3f9468b8 -> 00000000", + "00000000 bf50c0d8 3f7f9d38 7f800000 3ed30c90 3ed30c90 -> 00000000", + "405610da 80000000 3fe8ba90 3e9602a0 3f1e7c70 3f1e7c70 -> 00000000", + "00000000 00000000 3f84dca4 3ed1edd8 bfb5cbe0 bfb5cbe0 -> 00000000", + "00000000 00000000 ff800000 7f800000 401d4be0 401d4be0 -> 00000000", + "bea92ba8 00000000 ff800000 bfbc161a bfce0954 bfce0954 -> 00000000", + "00000000 00000000 3ffabe24 ff800000 be94c538 be94c538 -> 00000000", + "3fdb9278 80000000 7f800000 ff800000 bfb80c20 3fad3c54 -> ff800000", + "00000000 80000000 7f800000 ff800000 bfa61865 bfa61865 -> 00000000", + "00000000 80000000 ff800000 bf84d6bd 3f403a2c 3f403a2c -> 00000000", + "405d8f82 be715bf0 7f800000 bff060d1 40252552 3fabcf50 -> 4014cf8e", + "80000000 bf8e4188 7f800000 7f800000 3f6acd98 3f6acd98 -> 00000000", + "bf2996a4 3feeb9fc 3ef574c0 ff800000 403f2da4 403f2da4 -> 00000000", + "404b1a90 80000000 7f800000 ff800000 40358f58 40358f58 -> 00000000", + "406964ea 3f1b1f88 ff800000 7f800000 3eace890 3fe2f1b0 -> 40c4dd90", + "00000000 406a2510 ff800000 bf7d18c8 3f024fac 3f024fac -> 00000000", + "00000000 00000000 3fc5fa3a 3fa07496 bf8fb485 bf8fb485 -> 00000000", + "80000000 80000000 bfbb7b60 ff800000 3e2af490 3e2af490 -> 00000000", + "80000000 3e5aa380 7f800000 bfd8835b 3fdb8594 3fdb8594 -> 00000000", + "3f74a1e0 4011863a bf0a69ae bff30675 3eb98b78 3eb98b78 -> 00000000", + "bfa52d40 00000000 ff800000 bfc83f64 c0094bc0 c0094bc0 -> 00000000", + "3fdf6ba8 3fa96b98 be04bc30 7f800000 c00c72cf c00c72cf -> 00000000", + "80000000 80000000 3faeecdc bebf17e8 3f5565c8 3f5565c8 -> 00000000", + "00000000 c07111a4 7f800000 ff800000 3fe4c6ec 3fe4c6ec -> 00000000", + "00000000 3d8861c0 ff800000 7f800000 bfb30a60 befc8658 -> bd69a824", + "00000000 00000000 ff800000 ff800000 3eaec780 3f4e3528 -> ff800000", + "80000000 00000000 3fa5cf60 7f800000 3f46b9c4 3f46b9c4 -> 00000000", + "80000000 00000000 3c95e600 ff800000 3f847fa4 3f847fa4 -> 00000000", + "00000000 00000000 3fd73136 ff800000 bf7d6bc8 40305c10 -> ff800000", + "80000000 bfe74ff0 ff800000 3e0ee410 3ecea760 3ecea760 -> 00000000", + "80000000 00000000 3f082ac0 ff800000 40151e54 40151e54 -> 00000000", + "80000000 80000000 ff800000 7f800000 bf63ab8c bf63ab8c -> 00000000", + "00000000 c0571bc6 7f800000 ff800000 bffa05e1 bf93391b -> ff800000", + "00000000 80000000 bded5bc0 7f800000 3f8f48f0 3e98cc20 -> 80000000", + "00000000 80000000 ff800000 bcf42a40 3f0ec9a8 3f0ec9a8 -> 00000000", + "80000000 00000000 ff800000 ff800000 beb280f8 beb280f8 -> 00000000", + "80000000 00000000 3f9f75f8 7f800000 bfe89986 bfe89986 -> 00000000", + "00000000 404345ca 7f800000 7f800000 bf8f67d5 3f9b0790 -> 7f800000", + "c02d14e5 c018b2de 7f800000 beabd00c bf628f58 bf628f58 -> 00000000", + "80000000 3fa81b84 7f800000 7f800000 bf7fcb58 402c99e8 -> 7f800000", + "405a6490 c015edd8 ff800000 7f800000 402b25a8 402b25a8 -> 00000000", + "00000000 80000000 ff800000 3faf5518 3e8458b0 bf17a4b0 -> 80000000", + "3ddcc380 c01b944b ff800000 3e1a4ed0 c02f5e1e c02f5e1e -> 00000000", + "80000000 80000000 7f800000 7f800000 bfae87ca bfae87ca -> 00000000", + "80000000 bf365dc8 7f800000 7f800000 3cb87a00 3cb87a00 -> 00000000", + "3c9b6800 3fd96c50 ff800000 7f800000 bfa0d940 bfa0d940 -> 00000000", + "80000000 3fe3b634 ff800000 bfb50e83 be879260 be879260 -> 00000000", + "40400d36 80000000 7f800000 7f800000 3fcbf0b0 3fcbf0b0 -> 00000000", + "00000000 c073a1d2 3e22f910 be8c4dd8 402c5370 402c5370 -> 00000000", + "c00c4bae bf7f88c8 7f800000 ff800000 3f50b1bc 3fc21d00 -> ff800000", + "00000000 beb0d330 bf6b718a 7f800000 bf2bffdc 4015e728 -> bf5e9010", + "80000000 407ee62e bfc8b0ac ff800000 bfe242a3 bfe242a3 -> 00000000", + "c00a085f 4000655e 7f800000 ff800000 402f7d2a 402f7d2a -> 00000000", + "80000000 80000000 7f800000 ff800000 3ec08940 3ec08940 -> 00000000", + "00000000 80000000 bfb3cb9a 3fae284c bf73e094 bf73e094 -> 00000000", + "80000000 80000000 7f800000 ff800000 402b9396 3fe25440 -> 7f800000", + "00000000 00000000 bfa90b1a 3e615980 bf113528 bf113528 -> 00000000", + "00000000 80000000 ba34e800 ff800000 401b67ce 401b67ce -> 00000000", + "c066c90e 00000000 bda3fa30 7f800000 40336df4 40336df4 -> 00000000", + "00000000 80000000 ff800000 ff800000 3f965088 bf746b10 -> 7f800000", + "80000000 00000000 bfbad0bc ff800000 beba6078 beba6078 -> 00000000", + "401d2f02 00000000 3fea9790 3d3f6080 bfafe5e0 bfafe5e0 -> 00000000", + "00000000 3f51ea80 7f800000 7f800000 3fcab42c 3fbfaad4 -> ff800000", + "00000000 00000000 ff800000 7f800000 3fe9ee70 3fe9ee70 -> 00000000", + "80000000 c034a2be 7f800000 ff800000 3df9d3c0 3df9d3c0 -> 00000000", + "3f1f29e8 80000000 ff800000 ff800000 3dfe57a0 3dfe57a0 -> 00000000", + "80000000 c058626d 7f800000 bff33e4d 401c9e9a 401c9e9a -> 00000000", + "00000000 4075df8a bfd1267c 3fff39cc bfbb7f91 bfbb7f91 -> 00000000", + "00000000 00000000 ff800000 7f800000 c0105faf c0105faf -> 00000000", + "00000000 80000000 3e8b28a0 7f800000 bfe3598a bfd7dee1 -> 3cc7ad83", + "00000000 00000000 ff800000 ff800000 bec241f0 bec241f0 -> 00000000", + "40322ac4 3f612ac8 ff800000 7f800000 bf3990c4 bf3990c4 -> 00000000", + "80000000 80000000 3fc155d6 ff800000 bfdcf5b0 c0339ef0 -> 7f800000", + "00000000 00000000 7f800000 3fb27a36 c027718a be8432c0 -> 40526f29", + "80000000 00000000 3e4910d0 3bf3c600 3ff7f7b0 3ff7f7b0 -> 00000000", + "80000000 00000000 7f800000 ff800000 bf853af2 bf853af2 -> 00000000", + "80000000 80000000 7f800000 7f800000 c03fcb21 c03fcb21 -> 00000000", + "3f1c4320 404b71ae ff800000 ff800000 c03b7ec3 3ef955b0 -> ff800000", + "80000000 00000000 7f800000 bf3589e8 c004199e 3f4c3114 -> c001e073", + "405882aa 3f58fe18 bf3c8892 ff800000 3f5ba17c 3f5ba17c -> 00000000", + "3ed60fd0 c0787bf7 7f800000 ff800000 c027a669 c027a669 -> 00000000", + "40415c10 00000000 ff800000 7f800000 3ed104e0 3ed104e0 -> 00000000", + "bfd396d0 00000000 ff800000 be9a61e8 c03e89c2 c03e89c2 -> 00000000", + "00000000 3f504908 7f800000 ff800000 3fa5c6a4 3fa5c6a4 -> 00000000", + "00000000 bffb884a ff800000 7f800000 be7903f0 3ecc3d80 -> bdc934ef", + "00000000 3f2bd2f8 ff800000 7f800000 bfaa3d52 bfaa3d52 -> 00000000", + "80000000 00000000 7f800000 7f800000 3ff3f280 3ff3f280 -> 00000000", + "00000000 3fe24c9c 7f800000 bff12655 3f102eb0 40087702 -> c03d302a", + "406358a8 00000000 3ef01bf8 7f800000 3ffedf74 3ffedf74 -> 00000000", + "00000000 00000000 7f800000 3fd89804 bfaebcbc bfaebcbc -> 00000000", + "80000000 80000000 ff800000 7f800000 bf843386 bf843386 -> 00000000", + "00000000 404be2c4 ff800000 3fc25892 3e4d0290 3e4d0290 -> 00000000", + "bf0e67fc 80000000 7f800000 ff800000 bffe5952 bffe5952 -> 00000000", + "00000000 3fa4f594 7f800000 3fe5d4dc bfafd589 bfafd589 -> 00000000", + "00000000 bf77c8f0 beefd1e4 ff800000 bf79e4d0 bf79e4d0 -> 00000000", + "00000000 3f2f9d20 7f800000 ff800000 c017a5d0 c017a5d0 -> 00000000", + "80000000 80000000 ff800000 bf4b5558 3f19e55c bf993596 -> 3fb6cea8", + "c04aecf2 bffdc53c 7f800000 7f800000 3ff42e04 3ff42e04 -> 00000000", + "00000000 bf8733ce bf30ee0c 7f800000 bfdf4843 bedbf9d0 -> 3fc139ba", + "80000000 80000000 7f800000 7f800000 3f65e7e8 3f65e7e8 -> 00000000", + "80000000 c01677e0 ff800000 ff800000 bf007e88 4034553c -> ff800000", + "c023ebaf 80000000 3e888168 3fd868b0 3fd88290 bee63c60 -> bf1223eb", + "00000000 00000000 7f800000 beb09d98 bfac21b0 bfa0ead4 -> bcf793d2", + "00000000 80000000 3fe85996 3f3ed978 c0316f65 c0316f65 -> 00000000", + "00000000 00000000 7f800000 7f800000 3fdab088 c005b692 -> ff800000", + "00000000 80000000 7f800000 3fc6a1dc 3f5b0e68 3f5b0e68 -> 00000000", + "80000000 80000000 7f800000 beae44f8 bf31265c bf31265c -> 00000000", + "80000000 80000000 3fd86ce6 bf6d3f26 3ff3c428 3feb4ba0 -> 3d7b359a", + "80000000 3f9d2860 ff800000 7f800000 3f665fd8 3f665fd8 -> 00000000", + "80000000 80000000 ff800000 ff800000 bf3d4470 401a79f4 -> ff800000", + "80000000 00000000 7f800000 3fb11030 bfeb92e0 bfeb92e0 -> 00000000", + "bfea040a 80000000 3f4a5d74 bea5d2c4 3f741340 40247da0 -> bf060c1d", + "80000000 401a5258 ff800000 bf87f253 bf009dc4 40089a4c -> c033879b", + "00000000 bfe60b72 7f800000 ff800000 40386c38 40386c38 -> 00000000", + "80000000 80000000 bfcf6e6c 7f800000 403515f0 403515f0 -> 00000000", + "00000000 3f90bd0c ff800000 ff800000 3fa3921c 3fa3921c -> 00000000", + "00000000 3f494080 ff800000 be21e630 bebcd298 bebcd298 -> 00000000", + "406a06a6 00000000 7f800000 3fecf07a 3f8a6220 3f8a6220 -> 00000000", + "bf720b18 00000000 7f800000 ff800000 c031ef1a c031ef1a -> 00000000", + "bfa18170 80000000 3c31b200 ff800000 bffa78f4 bffb00c0 -> 7f800000", + "80000000 c010f43c 3f35544c 7f800000 bd35a040 bd35a040 -> 00000000", + "400eec30 80000000 3fb9fe58 3ff68576 402f8fb0 402f8fb0 -> 00000000", + "00000000 3f727ac8 ff800000 7f800000 40196800 40196800 -> 00000000", + "00000000 80000000 beabf8b8 7f800000 40275b20 40275b20 -> 00000000", + "80000000 c06216f0 ff800000 ff800000 3fbcd710 3fbcd710 -> 00000000", + "3e2b8e20 be09a8c0 7f800000 ff800000 bf2dc6fc c01fc7d8 -> 7f800000", + "80000000 80000000 ff800000 bfdd501a bf26753c bf26753c -> 00000000", + "407b0f90 00000000 ff800000 bff2b216 3f233758 c02aa030 -> 40c87116", + "c01eb335 00000000 ff800000 3f3265c0 401b2e1e 3feb10c0 -> 3fbab530", + "80000000 00000000 3f838946 3f0a5c88 3f0a68f0 3f0a68f0 -> 00000000", + "80000000 80000000 ff800000 7f800000 c02c2d43 c02c2d43 -> 00000000", + "00000000 00000000 ff800000 ff800000 c0275de0 c0275de0 -> 00000000", + "4015a6ce 40201064 7f800000 7f800000 c023744b c023744b -> 00000000", + "00000000 00000000 bdbabbb0 bfd7fffc 3f7393f8 40147bb2 -> c013ce4d", + "00000000 00000000 7f800000 7f800000 402fac7a 402fac7a -> 00000000", + "00000000 00000000 7f800000 3f2cba98 4022c284 4022c284 -> 00000000", + "bf7649a8 3fd2428c 3eef5f18 7f800000 401ff64e 401ff64e -> 00000000", + "80000000 80000000 ff800000 bf58f31e 3f568188 3f568188 -> 00000000", + "80000000 4039be2c ff800000 ff800000 402c7c98 3f8ee1f0 -> 7f800000", + "c0762d7d 80000000 7f800000 3f97fcf0 3f0b0374 3f0b0374 -> 00000000", + "80000000 00000000 7f800000 ff800000 3f52ccd8 bfc0fc02 -> 7f800000", + "00000000 4070b1da 3f5b1dc4 bf13e846 bff47ea1 3f35263c -> bfc19734", + "80000000 00000000 3f9c9f20 3f65673c 3fb0e250 c004e4f9 -> c046574d", + "401ed582 00000000 bfb557e0 7f800000 3fe43a6c 402222b6 -> 3fee5b90", + "80000000 bfc22d2e bdcfeaa0 3fa464a0 bfbda1fd bfbda1fd -> 00000000", + "00000000 80000000 3b208000 bfaca132 c030f6ab c030f6ab -> 00000000", + "3f9d2ae4 80000000 7f800000 7f800000 400dc94c bdccfa00 -> ff800000", + "00000000 80000000 bfcfc459 bf3e3016 40200df4 40200df4 -> 00000000", + "80000000 80000000 7f800000 ff800000 3ec4ecd8 3ec4ecd8 -> 00000000", + "00000000 3f892e30 7f800000 7f800000 3f9fcfc8 3f9fcfc8 -> 00000000", + "80000000 c042469e 7f800000 3f89d6f0 c008d2e1 c008d2e1 -> 00000000", + "80000000 00000000 bfa9f914 7f800000 bfe56d87 bfe56d87 -> 00000000", + "00000000 00000000 7f800000 3e7e1130 bfe16440 bfade4e4 -> 3dcc6f4a", + "3fed278c c0175c36 7f800000 7f800000 4006c8da 4006c8da -> 00000000", + "00000000 80000000 ff800000 be0c0b68 3f5397e8 3f5397e8 -> 00000000", + "3eb88820 c0494d42 bef4f1f4 7f800000 c00a6ab0 bf5aa13c -> 40d5c9c0", + "4058173a 80000000 ff800000 bfb537a2 c021c9a4 c021c9a4 -> 00000000", + "bef693e8 c039c856 7f800000 3fe18074 3f047ec0 3eefba60 -> bdb20561", + "c005b8ad 00000000 be9992cc be1fb720 bfc55229 bfc55229 -> 00000000", + "00000000 00000000 bff6c59b 7f800000 3f121cd0 3f121cd0 -> 00000000", + "80000000 bf7e2114 bec9b580 ff800000 c0122268 c0122268 -> 00000000", + "c01aa58a 80000000 3ff5ddde ff800000 c0001a48 c0001a48 -> 00000000", + "00000000 00000000 7f800000 bfd66c9d c0177ec3 c0177ec3 -> 00000000", + "3f322310 80000000 7f800000 bf947393 3feaf4e8 3feaf4e8 -> 00000000", + "00000000 80000000 bfa02d07 ff800000 bfde7439 bfde7439 -> 00000000", + "00000000 80000000 ff800000 7f800000 c02b599c bfee1d64 -> 00000000", + "80000000 00000000 3fddd822 7f800000 3fb54f5c 3fb54f5c -> 00000000", + "00000000 4007f0d0 7f800000 ff800000 3f31f590 3f31f590 -> 00000000", + "405cb2f2 00000000 7f800000 3f8d6758 bfe7a6d8 bfe7a6d8 -> 00000000", + "00000000 40567352 ff800000 3fd0fb4e 4030ba98 4030ba98 -> 00000000", + "80000000 00000000 ff800000 7f800000 402e382c 402e382c -> 00000000", + "80000000 00000000 7f800000 ff800000 bd8df4c0 4000ba00 -> ff800000", + "400e00d0 00000000 7f800000 bf0354a0 3db22980 3db22980 -> 00000000", + "00000000 00000000 7f800000 bf1c13f0 c03fb2ac c03fb2ac -> 00000000", + "00000000 80000000 bff31dff ff800000 402821c6 402821c6 -> 00000000", + "80000000 c0689249 7f800000 7f800000 bf9fa480 4013c62c -> 7f800000", + "bfd95cea 00000000 7f800000 ff800000 c0173b18 c0173b18 -> 00000000", + "80000000 3e952d00 7f800000 bf8097d4 40302390 40302390 -> 00000000", + "80000000 80000000 ff800000 bfeffee4 c01eb36c be619270 -> c0879000", + "404b75a4 80000000 7f800000 ff800000 3e95b1b8 3e95b1b8 -> 00000000", + "c00e25f0 bfbdd6ae ff800000 ff800000 4026857e 4026857e -> 00000000", + "3fea06f4 00000000 bff0ce8a 7f800000 3fd74714 c025bd4f -> c0f9e9f2", + "80000000 00000000 7f800000 bfb96dd4 bd946640 3fe4f32c -> c02c8dcd", + "40261dbe 00000000 7f800000 3f3f6830 3fb95060 bfc2eb22 -> c00e2593", + "80000000 00000000 3fde5382 3f1fb348 3f5e8390 3f5e8390 -> 00000000", + "80000000 00000000 bf8a5d94 ff800000 40363990 3fbfa8c4 -> 7f800000", + "80000000 00000000 ff800000 ff800000 4005e244 4005e244 -> 00000000", + "4024309a 80000000 7f800000 7f800000 3fdecd88 3fdecd88 -> 00000000", + "00000000 00000000 7f800000 3f91238c 40079ca8 40079ca8 -> 00000000", + "00000000 00000000 7f800000 7f800000 3ff7960c bcc05800 -> ff800000", + "80000000 00000000 7f800000 7f800000 40065cc0 3fe91e88 -> ff800000", + "00000000 3fe389a0 bed1d474 bd357fc0 c01f4cc3 c01f4cc3 -> 00000000", + "00000000 00000000 bf0a3aa4 7f800000 c032ed34 c032ed34 -> 00000000", + "80000000 80000000 ff800000 7f800000 3d3a0e40 3d3a0e40 -> 00000000", + "80000000 80000000 ff800000 bf6eae48 bf888f74 bf888f74 -> 00000000", + "405c38fa 80000000 3e25bd20 bf0077c0 3f8d28cc 3f8d28cc -> 00000000", + "80000000 c05d2c4a ff800000 befb6b5c c00cb828 c00cb828 -> 00000000", + "403e43e6 80000000 bfb8ab82 ff800000 3faf7fb0 3faf7fb0 -> 00000000", + "00000000 bfd6fecc ff800000 3ed7c3c0 3f225020 beac0090 -> 3e7ee0dc", + "80000000 80000000 bfda0e7f 3f5b65d0 3f74fed0 3f74fed0 -> 00000000", + "80000000 c037e6c4 7f800000 7f800000 3fa63768 3fa63768 -> 00000000", + "80000000 00000000 ff800000 7f800000 402a7c9a 402a7c9a -> 00000000", + "3ff192cc bf9b4ede ff800000 ff800000 c01f8d5b c01f8d5b -> 00000000", + "00000000 bf801ad0 3f59ed4c ff800000 c02ff429 c02ff429 -> 00000000", + "403bdb5a 00000000 3fd09b08 7f800000 bfdc1271 bfdc1271 -> 00000000", + "4063f4fa bfd6c9cc be9b4e08 7f800000 bfde6f62 3cda9100 -> 410d15b2", + "80000000 80000000 ff800000 3ea3f328 be05b570 be05b570 -> 00000000", + "00000000 80000000 ff800000 3edcd728 3f53d948 3f53d948 -> 00000000", + "c03d9d24 80000000 ff800000 ff800000 bfd2feb9 bfaea521 -> ff800000", + "80000000 00000000 7f800000 ff800000 bfaa4ef0 bfaa4ef0 -> 00000000", + "3fbe989c 00000000 ff800000 bf955032 3f043390 3f043390 -> 00000000", + "00000000 00000000 ff800000 bfb66c20 3f5be7f0 c03ae854 -> 40ac5d0f", + "bfbd1070 00000000 7f800000 ff800000 3f097bf4 bfb0083c -> 7f800000", + "407eb752 80000000 7f800000 ff800000 4017e772 4017e772 -> 00000000", + "00000000 00000000 be3e4598 3fd93476 3f1bbd64 3f1bbd64 -> 00000000", + "80000000 3e011e80 bf8a1de8 3e70f440 c020ae44 c020ae44 -> 00000000", + "00000000 80000000 7f800000 ff800000 403ce988 403ce988 -> 00000000", + "80000000 00000000 7f800000 ff800000 bfec7761 bfec7761 -> 00000000", + "c01aacb0 80000000 7f800000 bffce4f3 be0040d0 be0040d0 -> 00000000", + "00000000 00000000 ff800000 ff800000 bf2f0254 bf2f0254 -> 00000000", + "80000000 3fc97bcc bf3468ec 7f800000 401fb7c2 c002f127 -> c06786d0", + "80000000 80000000 ff800000 ff800000 bf0a598c bf0a598c -> 00000000", + "00000000 00000000 beb9c524 ff800000 3f949a78 3f949a78 -> 00000000", + "00000000 80000000 7f800000 bfd73167 3fa7e328 403d13e6 -> c030c039", + "80000000 00000000 bfc58738 ff800000 3f5f8ea8 3f5f8ea8 -> 00000000", + "3fb9b348 4007bb94 7f800000 ff800000 3fe45408 3fe45408 -> 00000000", + "3fde732c 00000000 ff800000 bfd1e56f bffd7643 bffd7643 -> 00000000", + "00000000 00000000 3f29de30 ff800000 bece6e60 bece6e60 -> 00000000", + "80000000 00000000 bfaab168 ff800000 bf0391a0 bf0391a0 -> 00000000", + "00000000 00000000 3f54e0a8 ff800000 bfc96f89 bfc96f89 -> 00000000" + ] + }, + { + "glsl": "SpanNoise cultmath_span_fbm(SpanPath, SpanStage, int, float, float)", + "tolerance": "ulp-bounded", + "cases": [ + "c1e8c486 408859a0 4209df1c bfd405da bfae7f45 bf4bf283 40173577 3f3df92e 3dcccccd 3daf634f 40773997 bee454c8 3f4650cf 3f117634 c0fa9f30 40328fb0 c084111c 40cdfff8 c07fcc48 c038ffca bfe64a6f bf5f5c56 3f9985f8 00000003 bfce4600 bf721003 -> beabe5a4 3e0539c6 bec85e69 3f290ddc 4052379f 00000000", + "c189c6d0 c1a1a95d 421a9dca bb872588 3e566bda bf6d4d57 3f7a38ae 3fad719d 3ca3d70a 3ebd46b1 421ba090 3fee0ccb bf94d83b be4eaf17 c0ab18ec c04962ae 40fc3e12 c0cf5763 c03a6dfc c0b8d06c c0333f47 c03ee6a2 c03de320 00000002 3f4d9338 be75267e -> be3bfd40 bee86efe 3ed91f5c 3e81cb36 4084a651 00000000", + "4171abd0 4100b594 41e79c38 be3ab428 3e2e3b8c 3eef71ee 3f391044 3ec58756 3f000000 3eca832b 40052ea3 be9184d6 bf096b92 3e9cac7c c0b8afb0 c0333dc6 c0b5cd4a c09daa9c 40d488ba bd778080 be8986c5 bfa7b7ac c039408d 00000004 3fcc6857 3eedc0c3 -> 3f1c45fe bf4ac425 3e59bc0e bd0437f6 4236b592 00000000", + "413fd3c4 c1a2b8dc c0912300 bfe8af9f 3f1b8111 3e81713d 400477b9 3faec659 3dcccccd bd984a9a 3e99c3d4 3f8ea27a 3f81ec15 3f8b2e04 c012a21a 40f315bc be0985a0 c019d566 c0bc628e c09185dc 3f84d85e 3f13f3e2 3e584f9a 00000005 3f36a156 3f8b8bfb -> 3e0e7221 3ed5cfce 3eab833a bfb45f1d 40b42f1f 00000000", + "40cc6a60 4133d034 41fba404 4019b988 3f139385 bdf9342c 402025a7 3fba886c 3ca3d70a bc858213 3e57ffb3 be856941 3ec0c011 c01f90c2 4089161c c0398198 40b8e87e c0882fa9 c0b1f685 c0819f02 402dd736 3f69e33e 3ea75944 00000005 bf53e0b0 bf2cc0e8 -> 3b852f1f bcbbec36 3d84e78b beaff143 3e6d6e6a 00000000", + "4231d2d0 c1aba9ae c2443623 3fab6f26 3f949615 bfaa0f42 4024a6c8 3f371a21 3f000000 3fd49533 3c766fa8 bd74c1ac 3f46c890 bf22300b 40e8094c 3fb8b970 c0de5299 c04379f4 40ef200a c048773a 4011b36b bfa17c11 bfea954b 00000004 40268527 bf27efb8 -> 4192a37c c39cd16a 438302f6 3e4b2811 4c91e2f6 00000000", + "c1d5f064 c194386a 41126b84 3f14e7f8 bf8c110b c02d2ab0 406c4b7c 3fb75103 3f000000 bb1e41ea 3e8cb0c3 400e1b01 bf966cf6 3fb3d0d9 400a57b8 c08c3d74 c0ffb8de 40bcd070 c0b325de bfc386ac 3fff3828 3ff71900 3f9aaaed 00000005 bff19635 bf845b60 -> bd324040 3c409948 bd7ead2c bf4ae73a 3f8949ed 00000000", + "3f802d20 c1067134 403266e0 bef9b9e5 3fbf0341 400ab782 402b653c 3db224a6 3ca3d70a bf0a21be 40077d71 3f09df29 c00b90e7 3eb0cc37 c0d9333b c0bab75f 3fd6ec60 40517608 c0487e0e c0d3c302 401c3cff c0359246 4024ee5a 00000001 c0285ac4 3f0860c1 -> 3effe827 be8c1b9f bf47ac03 3ea47b0c 413d149f 00000000", + "c0836d58 c2303c12 40913c88 3f02a951 3e1f7eb6 3ed1d98a 3f32319f 3f947d04 3ca3d70a bcb992d2 3c348184 3c81fbb2 3fa2796b bf490d24 3f9b5978 c0b9b5f2 c0748930 3fd20130 c00b19d6 409533f4 3ed6e69c 4030b114 3e2fb80c 00000005 3f3ac510 bf575170 -> 3c9899c7 3ee5208d be555174 bf2747fe 4389f73a 00000000", + "41a3f724 41bf57d8 c13ed9ac beb5332d 3f9d9383 3f746b6b 3fcc7e55 3fcb0906 00000000 3f9961ef 3c829ece bf112939 3f0a57af be552572 bfa9bc30 3ffe5758 40d50412 40cee2b6 c00b50ca 3ff7abc8 bea63a7d 3f7418a4 bfd66bc1 00000001 3febb7c6 3f8c99db -> c2a5b4e1 4299db1e c202831a bed784d7 4814f9ee 00000000", + "41466c9c c1fa6241 c1f201bf 40135b9c 3db49ce0 3fe964c4 404b110d 3ef0334e 3f000000 be55c93d 3e6684ec 3efca50f 3edf817e bd858552 c089c9da 40e68558 c0c5c125 c09327a4 4091b76c c0ce1070 be672ed8 3e103ee6 3fe589bd 00000003 3f5eb2fe be83c4b9 -> 401d2baa 40155d06 be090d41 bdecadcc 426bf920 00000000", + "41ac8848 c1c8d5d4 419ee46c bf8c9a83 402d8df3 be472928 403e36fb 3ffee66a 3ca3d70a 3c0d46fd 3c9bf162 be45aca4 3e744a01 beffecc5 bdfa11c0 40e8be3e c05e040c 407fbba8 40c4bafc 40515ef8 bfdadd3c 40084c64 3fde692e 00000006 3fae2c08 bf96b088 -> 3fbcc2f2 c010e5d0 40891e28 3fb3f9b5 447c6731 00000000", + "419b84c0 c15badb0 c214e7f1 bb8b29e7 3daa323b 3f925209 3f9ead5f 3e3f838b 3f000000 bd8e1057 3e268108 bd159118 3f7093f8 4016ad5e 4084b7f4 bfb191ec c085323e c0dd4ed7 40b8b6ea c0aad83f 3f9b3a24 bfbea860 bfa670af 00000007 40396b71 3f10163d -> 3ebada3d 4055ed07 beae3ae8 bdfa7d0a 480b76e5 00000000", + "c143b798 bfb00160 4223b0d8 becb8c2b bfc907e3 c0163973 403861ba 3fb8ee45 3ca3d70a 3e148220 3e216a44 be96defc 3fa0263b 3e72019d 4054ad64 408aa782 c0503aaa bf3e6958 c06b28a6 3fc7e108 3de9226d bfb45af6 bf8380ea 00000005 bf0b7977 3f868b0a -> beabf28f 402103cc 3ea1bc39 3f395fb0 42751101 00000000", + "422a9ee6 c2326299 41f372d4 3fec7421 bf9372e2 3f2fc27c 4012182c 3f2e6e91 00000000 bbfe1a2f 3e296a80 bf0b2f4a 3f5a96a5 bf6c5440 c0847ef4 40dc54be 40c57ee4 40f1ed84 40b3d6a4 3e9905e0 400ce8a4 c021a8db be7632d0 00000001 bf0e10b9 3e183bcc -> 3b849332 bc153149 3cd46fda 3d19da97 3d3162ff 00000000", + "4227afd4 40ce3ae0 42073478 bff05385 bf94e2c8 bfb6d1dc 402855c6 3f4a0183 00000000 3d700ca9 3cf17e9b bf354721 c0104a71 3e96ed08 c0099b4a c0273c84 3e9904e0 bfef5e4c bfdb0ae8 c05e8768 bf72a154 3ffcb970 40045238 00000005 403015c7 3f497076 -> c23f9b32 c30a23fc 4190ca7e 3f7a647d 480bab76 00000000", + "41cd4e28 4123e7e8 419d3464 3d855d67 be8d5546 bbaa051a 3e913ca1 3f753fb6 00000000 bd6960a2 3f133dcd 3f01e8d0 bff5a312 c00aec5c 40d4420a 40f130d8 c0f8accf 3fb379f0 3feb0910 c07b13a8 bf6076c5 3e1b28ba bfaf18e8 00000007 bfdd6d30 bf26cf77 -> 3ded1a42 bf9eb62b bf0098b8 be2800c0 419aaf14 00000000", + "c0ce3178 c14c8710 422d6d32 3fbaec37 3f78eaf5 c01a82de 403f3ec3 3e439da5 3ca3d70a bdf42534 3f83d100 bd18b614 3d502dcc bf018def 3f023660 c0b0a519 4013d810 c0cedc18 c0e6f551 c094e390 bf8b784a c01bb2f7 c030fa23 00000002 bfd5b3fc bf21b657 -> 3e29ca4c bd2d2070 3e557ed5 3efe5d6d 3fa3f121 00000000", + "407c8a80 41976410 c1e42d6d 3f3e9daf 3f028cb3 bfa84f50 3fcc2381 3fd7b1da 00000000 c09b3fd5 41013bb7 bf2eacc7 3fbf8072 3eff3d88 c0b977dc 40df71c2 c0650a84 40fb91da c0c74236 40802768 400d565b 4036d0e7 c039a62d 00000003 c0104194 3f420f50 -> 40b6c1c0 41915625 c1f443d2 bdfc5598 463d0826 00000000", + "c101faa0 c2246379 419e1838 c009e8c5 becee55d bf7fcdae 401b62ed 3f6d5d9b 3ca3d70a bda14697 405922db 401a0b3b 3ec73f7a 3fcdcf23 401ff42c c0f26032 401cde2c c05766ba c003a452 402b5ad4 3f951e18 4026d012 3f74c1a3 00000004 4038df8e 3e8bae39 -> 3e05ec4a bd530b80 be5d051a 3e0e2536 40c738a6 00000000", + "c13346b0 41f6276c 420e4546 40178adc 3d688fb8 3f90314d 4027e8db 3ad4cb50 3f000000 c0b826b7 3d0395ab bf4c86f9 3eedefd0 3f208905 3ea5cc20 c0feaa41 40f98f92 c07ead52 c0b3dc38 40ffa62e 3ffdc042 3ec711bf bf69aa23 00000008 3ff4295f 3e4fcef2 -> c377f82c 4317cbf4 4357025f bee87499 4abde401 00000000", + "c2474822 c1897a80 41c5fa74 bfc081f2 be8f0bb5 3f30bf8f 3fe57cf7 3f92ac00 3dcccccd c0aed12c 3e5ce478 bde110f5 3f7dc9b0 be623f80 40423ec0 409cfa26 3fb06088 c0d4ed0c 40c858e4 c03b1ff0 c02515ed 3e2a6b6e be706064 00000008 400f667e 3f8c96aa -> 45e681de c680d80e 44b7fa2f bf7222fa 4fc96248 00000000", + "42372ab8 c0123920 4225584e 3e46a4a2 3f38d0c9 bfbe3847 3fdfdb66 3f5a88a6 3dcccccd bddbec60 41c5aab5 3e062c2f bf9aa084 bf989938 c0b0ce54 40a33b98 3ed8fe40 3ff775f0 40c03150 bee58900 c01eec70 3e095f6b bfa10dce 00000008 3f8ac558 3f513c21 -> 3c7c6a40 3ecbdd24 bee2f328 3e845d37 4035459e 00000000", + "408af178 41a23fa8 418c8748 c000fb6e bf820795 bfc09004 402fad5c 3fd0bde9 3ca3d70a 40860c6d 40827671 bc4a5e83 bef90d91 be202b0e 4098db26 402fe65c 4079706c 401b5a20 4045b8d4 407f17f4 401a1d80 3de32362 bf9e4e89 00000006 c0332eb7 be62299c -> c1188c68 c0127f11 40c75df0 bea077a4 47073620 00000000", + "41d25c84 c23c0cdf c20f60c1 3f8d74e3 3ff473f5 bdec94f5 4013ae54 3f7ae2a2 3dcccccd 410e0e65 420ae7ae bf185aa3 be33ad5a 3efe88a7 3ff42b10 403612ec c0fc7bfa c09fa6fe 40cf909e c04b20a6 bee9a968 bfee04e9 bfccb1d8 00000004 403a3287 3f80ce75 -> c3982d36 41ae3ea8 c3b0ac48 bf2e824c 4a3b65e2 00000000", + "c00eed60 c1f9c353 c1743468 be872098 3e9191d8 3f36174c 3f4f6966 3f533a57 00000000 be0116b2 3ed38680 bf8a2323 3ec91869 bf80b05d 40cbad9e be41b320 403de714 c0ee90b1 c0f14314 c00dbc3e 3fc8fa5b c01450e8 3e25ee7c 00000003 c002c1cb bf74062e -> 3f1683cd bb33fd00 3efa9cfc bea2e09e 422f39f5 00000000", + "c22ebbe2 bfbcbe00 c22d8ece bf82a489 3e172181 3e1d9f3d 3f8576bf 3e4fe027 00000000 3bdf54db 3e859a53 40187857 bf088117 bf10aa56 40e9dd06 406a649c c0ff713c 408f580a 40f7545a c01cd20a be0709c8 4007a19c bf6d7d6a 00000005 403d378e 3e631b0d -> 3b144e5d bb8aa09e bca31034 3dbcd6fd 3ec6ccc9 00000000", + "c22ef0d5 c226860f 40940d30 be5895d2 3ebd7d13 3ec06e4e 3f483a4b 3edaf920 3f000000 c0abe0a3 415e681d bfd20629 4006573e bf62800f 4071e64c 40b5e4fc bfc2d310 c09e99ba 409f7b52 40b652c0 4019908d 3febfe1f 403da491 00000004 3fd500b8 3ea02514 -> 4148d96f 4191920d 41827b8a 3ea70d30 45a3d818 00000000", + "c22ab0a6 c1efed8a c1640d50 3d9fca6a bd106bd7 3f13aed1 3f16996a 3e81dec4 3ca3d70a 3c6476c4 428f10ed 3f4db2b6 bf56e31f bfd534e0 3fa52e48 c0b55d62 409371a6 c0f16f5d c0598a22 40adb1e4 bf26a117 3fd32243 bffa4352 00000001 400b9c55 bf3e0c90 -> bc754ac8 bc1b4f65 bb250a07 bd0923d3 3b1e516a 00000000", + "40a078c0 c0d7f3c0 41a1e938 bd491ae4 bcdeb047 3f396332 3f39f17f 3f8ff087 00000000 bc7043bf 3c46f52c 3f6a46d5 bf2adf15 bf93bbd4 c0985fb4 c0360fe0 c04b17d0 3fadf058 c081cb7e 40aadfd8 3f288b0d 3fb367a9 c02c1802 00000003 3f8efdcf 3eb0e626 -> 3fc03631 bf8d1a48 bfedaf3f bed4d5e6 42531e27 00000000", + "c17b4514 41f29130 4239f0f2 3f66df55 bf0bb35d 3f2d47f0 3fa93fca 3f320b23 3dcccccd 404fd5ed 3fbc4642 bfe53418 3fa6f122 3fbba932 c0a46ade c0d6c057 3e3efe80 bf1f6658 bf3b1750 408c68ca c0123148 4039d763 c0216e01 00000006 40066bc4 be93f058 -> c0919b2b 3ff84acf 40ef8ea0 3ea0f90d 45db941c 00000000", + "c1e4c8de 41ba4210 41f58304 3fe0355b bf17bd14 bfd1b808 401e1eaf 3e2e0e8b 00000000 c0b58068 4140629c 3fbf9144 bf037b40 bfa5f2f9 3f72a940 bf9dbf00 4060d6d4 c0cea58c bdee2180 3e8c8d60 3fca08ba bfe5f40b bf44e87c 00000008 c03c5ddf 3edec443 -> 4218ab66 42b82b40 41e54382 beba9708 4bc3a406 00000000", + "c23b8ec2 c0f67120 bfb7ea00 3ea23f8e bd1489e8 be295efe 3eb81125 3c2cdef8 3ca3d70a bb23ab4d 3dd9ea35 bf2b45e5 be3d11d8 3f98b414 40950a00 40799824 c0b5a5d8 40452128 c05dd8d8 bb7d3000 bdf10c64 bfb40370 3f175799 00000007 bfd7c2f4 bee82815 -> bc60f16e 3c000a2c 3c48dc08 3e9cc376 3e617d62 00000000", + "c1ffb6ae 40d16fe8 c22cb319 3fa6d768 3f04eb30 3e987b4e 3fc095f9 3f33dfaf 3dcccccd 4111ef02 3e537bea bf28fb62 3f8f9cec 3e1391ff c04dc29c c0062e84 4003c4ac bfa336b4 409d1ad4 40dbb1d2 bf7c5fa8 bf18b4ab c0373f54 00000001 4032abc0 3e09e1d9 -> c2804c8f 4306791d 3f1d2285 bebaaa4b 47d3f531 00000000", + "c16393fc 40498970 418ac5ac 3cdecc73 3eb869ec 3eae8151 3efe45b3 3fe94915 00000000 3b465572 3cb84973 bed59b57 bf05e65f bf7860fe c0d0820b 403cbdc4 408f244a bfea3be8 c018d5b4 40845102 3f44f44e 3fbcec2d bf51f3fa 00000004 40293138 3e04c2c2 -> 3c20814b 3b0055d2 3c93ddf8 3f165b4c 3fb9d308 00000000", + "c20aefe4 42215ed0 c22c52b6 be905088 bef9e826 3e38e733 3f1783e3 3e286dc0 00000000 bbd33a6c 3d847d7c 3fb8294f 3f4ef38d 3daae83e 40983458 40a30048 c0945b14 bfffa0f0 c0cf7349 40c2d8ea 3feac2c6 3ff5a60e 3fdabe8f 00000005 bf8e354f bf8ad0dd -> 3b89aa80 3c7cdbc0 bd88b2db 3dcafe80 40082b55 00000000", + "415f15a4 c1083a08 bfab6ae0 3f3cca0b c02193d4 bf828f21 403dd42f 3fb9b05a 3dcccccd 3f1024d9 3d813dc8 3fba2622 3f82d30c 3f93d455 40dccd36 bfdc5f94 40d1ea54 c0a68a5e 408063e2 c0b073b1 c007d2bc 3ff18ff1 c00b1c3a 00000001 bfb90b0a bf276008 -> 411c24cb 40ff3ad8 40eefa13 3f4dee18 458a6d8e 00000000", + "c1dc0226 409882d0 40d35ff8 bfd2b98d 3e110a75 bf4489fe 3fee0283 3ebfae1b 3dcccccd 3de8bab1 3c906ea5 be987339 4034f869 3e08b5d4 40e7e21a 3fd059c8 c0e35bcd bfd884b4 bed42740 c0f4d7e6 bf83deb9 3f963d16 bf912118 00000001 4037422a bf16cfe5 -> 3fb1c646 c13a594c bebfe100 3ee903cb 45107853 00000000", + "c1c0eeb4 c1001f50 c24268b7 bd6375b0 bf1083b8 3ee0d78b 3f48d88e 3f2c0586 3dcccccd 3d584989 40072769 3f8b5bef be0d90e0 3efb19c9 3ff7db08 bfd805fc 3fe62820 bf1d1598 c057497e bfdf69c4 3f9c6557 3f9845f5 bf5f475b 00000007 bfe2eef0 bec6a711 -> bd023ce4 bc81719e 3985b870 3e82d970 3fd8d2dc 00000000", + "41fe142c 4129e8c8 420722a2 3fb719a3 3f07aec7 3ea19020 3fcd0f34 3ee27565 3dcccccd 3bd63e83 3ec92df7 401e93bf bf080be8 bfbb02e3 4095043c c0f4f28a be930370 c0f67ad4 c00bc180 40b8c902 3fee22ab bf8a91a3 bf61e0d4 00000005 3f99ab82 bee0349e -> 3d3ceb30 bc446e5b bce1c991 3eb49e6a 3c743699 00000000", + "41c572a4 422ec68c c1f32288 3fae2a74 bf3eb647 bfd111ec 40102ad0 3fa1aa0d 00000000 3d18a6b0 3d0300e0 3ed211fd 3f994298 c00638a2 3f718700 4094f6d2 bf8dfcbc 40c5d076 bf272e50 c00b6948 c03750d3 bf7b4183 bfa2e751 00000006 3ffc6326 bebc2c1a -> 3ebca933 3f63b3e6 bfc73e20 be48025e 440aa932 00000000", + "41c56454 41b4e2f4 42198eb4 3e80372a 400ec263 bfa44ab5 4025ff63 3ecd5750 3ca3d70a bcb58cc2 3dbcf515 beca9e70 be9efd37 3e82ffaa beec75a0 bfe12304 c062b746 c00359dc 40e5788c 4054deec 3fda2ca6 bfd453dd 3f1f991d 00000004 403b5f56 bf3dfad3 -> bfbc94b3 c00c4c14 3fd87bc6 bf994f8c 4472e934 00000000", + "c1968389 419c5290 41574878 3e8b71ee bf1638e3 bd5997cf 3f262bd8 3faa8f72 00000000 40bc546b 403bf13a bfcce6f8 bf9f7101 bf8bdb46 3fc392a0 40f77fe4 c0a56074 c0b626e2 40efa8be c04d2c92 3fe6c8be 3f66379d c0227eb6 00000005 c03ad71c 3f245067 -> 4226c89a 43521ddf c30fb345 bf3ce258 49df6efc 00000000", + "421bd68a 411fdd98 423cbf8e be548efe bf6338f9 bfa53c2a 3fca46fa 3fa6de72 00000000 406ebc6d 41f8ff42 be3ca009 3e3b8fa7 3eeb10e4 c0d58352 40f07626 c02f4e7e c0d59f52 3f619880 bf742230 c0359918 3fe22855 4032de16 00000001 c00c4f9a bf874eac -> bf96a4ae c101f10b 402567b6 3df261c9 438c40e2 00000000", + "41e52f20 c07fa660 c2125a47 bfb0a809 3eaee9b7 c01f78c2 4037bf99 3de3ff52 3ca3d70a 3bbddb34 41d9ae3e 3dcf8a72 becacbeb 3f1f5cbd c0c0c100 4057584c bf88d944 3f8adea0 409e9c82 40cf49c2 bfc7bdae bf72da19 c0152ff4 00000004 c0370912 3e06e652 -> bcb5b4f3 bc49d780 bb831683 bee32ef3 3b906ea9 00000000", + "c20d4088 c05ff650 c115b880 bf6affcf 3e4a16b5 3e857026 3f947bb7 3fed8cc3 3dcccccd bde039a2 3e3fef7e 4005c75e 3ee3191e 3ff8cdd5 c0c707b2 40d29c46 c0017c5e c0831230 bdb07400 c00a977a 4009f3be 3fb8f755 bea76d8b 00000003 c01d34eb bf862c5c -> c066d8c1 be715548 c09cbad0 3e4d8508 4460432b 00000000", + "419ab084 422b18a0 c224c589 3ed10fdb 3df3cccc 3e03d732 3ee38635 3f1934a8 00000000 40a28ea6 3f91d12a bf632bce 3f6c874e bfa3d715 40ca143a 408e86b2 c0a980d0 40d0505e 4079a004 be84c250 4001f40d 3e51007a 3f704b1b 00000008 3fcbaa7c bf601596 -> 4180b5e2 42057e34 41a5ea06 bf2036f1 48bd05d3 00000000", + "422cbd06 422a39b8 c1bae1a6 bfa99e27 3e1f7832 bf0a02bd 3fb832f3 3f42a27f 00000000 bb060bc9 3df1378b bff8e65c beecdc30 3f317d70 bfde1830 40960284 c0afbce1 c0b7298a 403446a8 c0421dd6 3fb22f59 bf2f7f36 401b3611 00000007 bfd58e15 bf3f4c6c -> 3e0ec8b3 3d41a222 bd37b7d0 3f0dc343 3f923c60 00000000", + "41c16600 c227f41b 42164b02 3e91f581 3f181509 3ec51185 3f435b26 3f9c159f 00000000 3d4e4a31 3f855e77 3d2c150f 3ea5edb0 befcab4c c0aaa9ae 401f8c98 c0b7778e 3ee1a280 4086e1e0 3ff8aff0 400eae78 c008adae beb2f91f 00000008 3f764865 3ea55a29 -> bd050a99 3c9e8e5b bdab7c49 3d01541f 3dbb79cd 00000000", + "41793ac0 c21b90c0 c24495cb 3df01ec4 bfaa85b9 3f90337a 3fdfd2de 3ea4a7a4 00000000 3c5a32d5 3fb87386 bf57545c 3f98a3f8 bf02afbb 40309794 bacbd000 40a73724 c049af8a 40eb94ee 3e032440 3faf67f9 3e0826c9 bfdb8c09 00000008 3fed06b5 3f773189 -> bf003889 be754bba bf6481ed c00ec382 42003844 00000000", + "4143e7a4 c067f020 c03a20e0 3ebdfdc4 bf0271f8 bf0baf33 3f758f84 3fa0a8bd 3dcccccd bdc025c0 3e394828 bee6e00c 3d880df4 bedccc9a 40a68ac2 c09f66b3 bee7f380 4022e6bc 402785e4 40a67ce0 3f1de3c6 3fce9d3d 3e97e9e0 00000004 bfe3bafb bf5664ad -> bfda0186 3e02adf4 c004277d 3f51c105 43da7a98 00000000", + "c2018b5b 414ce190 424261b8 c0155a97 bf08d91e bf7b1a69 402a7479 3f42dd79 3dcccccd 3a9b29b4 4196096f bf234a68 bebbd73b 3f24b59a c026670e 40cd86aa 40aafb56 c0ee3ca0 4004f840 3fb2a5c8 bf9056a3 401b0863 bfa6c9b3 00000002 c002183a bf30ad28 -> 3a417bd2 b8014a30 3a8553dd be124e4b 38d31734 00000000", + "bf277000 c201e667 bfc54760 beeea1ff 3e13e117 3f1a3ce1 3f483b8e 3eafba17 3ca3d70a 40c70c84 3da8b07a 3fd3ea87 3f8372ca bde015a8 bfc9adfc 3f6bf200 c0ccc83a 40365568 40bc71fc 4087ed02 c028cf87 3fddc284 3fce2112 00000006 40072c76 3f8bc4a2 -> 466aa96c 4613c14e c440b022 bef3290f 4e828636 00000000", + "c22f76a6 40bbdb50 409d6fd8 bd99697f be006249 be83c4b2 3f35829e 3f53835d 3f000000 bcd2b064 42ad700d 3ee70e84 bedf6418 3e932622 c0d7595c 400799f8 40deeece bfb8ce70 40f65dec bfc201c8 40309555 402a80cd bdd1465f 00000007 bfca1d28 bebf92ba -> bd735611 bd1003e1 3d2573fb be58623f 3e2cfcb2 00000000", + "41412ad0 c19c89ce 420bf800 bf4f1f57 3a8d57d5 bfeb1743 400071f7 3e894229 00000000 3a8a5569 3d948b2c bf79ff44 3f80c270 bf68becc 40146b98 40f7b15a 4075c1c0 c07ca6f0 c009d330 bedd2500 40226efb 3f91e81f bfced978 00000005 bf862e03 bf050c7e -> bbdf40a4 3bc686b1 bc0b0ec5 3f0efa17 3c0bdc50 00000000", + "41dd4500 c214cee9 c1996f6e beeede01 3ff1fffe bf8f302c 4010e151 3f6615c7 3ca3d70a 3be11e39 413d25a9 3eb785f6 3e1ada51 bf2318f3 be3dea80 3faedf60 40fddc08 c09c1185 40b3dcc8 3ee2c380 3f805874 be6c7114 3fca7239 00000007 3fd524a2 bf95c3ba -> 3f149a8f 3d33ba70 3ec5e2b6 c0644e78 40057d69 00000000", + "423db370 c22f3b46 4238826c bf92af59 be4d049e 40163097 4029c5cb 3eab4c59 3dcccccd 3f0b7aca 3e7ec0a4 bfb54f6c be229752 bf83053e be573d20 40694388 40ed4946 40b36d24 be8e4c90 c0dabb68 3fb40c04 403a8be0 befd22cb 00000002 401944c3 3eda83a1 -> 4011c48e bd84d96c 40350927 bed28986 4466069c 00000000", + "40c1a468 41678558 c13ca070 bf1ee3c4 3f6370cc bfb99cce 3ff76088 3f9c890e 3dcccccd bb771cd6 3d7441f8 bd281043 3edc4d63 3ee876be 40e4e86c 3fa55438 c0e84dbf c0d3ebf2 c0cb454a 40bd2836 bee346e1 c038877f 3e51a89a 00000001 3f406b20 3f946247 -> bb8f742d 3d5eba5a 3d53ca4e bc52783a 3e14fd49 00000000", + "4219e07c 422893ca 41db3958 3fb96177 3d4f921d bf0a9997 40162c22 3fcca806 3f000000 bd84fa3f 428903b3 3d6245d0 c000bb60 400706fc bf7ab710 4036a6dc 40ec6ed4 c0f3ab55 40d25b8c c0e67bcd 3eab56eb bfa38661 c03a4b78 00000001 bf8fb148 3e43505c -> 3c5da552 be6120d2 be5af53b 3e6de0f3 3e703713 00000000", + "4183cee4 421792a6 c1ff9628 3e2bddc1 3f38ab1d 3fc596f3 3fdb279e 3f83a55a 00000000 4047a6a1 3eaaeace bf2ccd03 bdf52839 3c98cec0 c0c00f8a bf75f628 4027a7b8 c0dbe5a2 3ebe7d60 40ae3968 400f0bb3 bff15000 400a2812 00000008 bf991a8b bead8475 -> bf569345 3fb688b4 40497ffc 3f136092 458b9880 00000000", + "c0f53c70 424147c6 c1684a8c bfffe56f 3f4a34bc 3f9f59b9 401f17f8 3db76e5b 3ca3d70a 3d895acb 41ad3a24 bf7fa4b7 bf1574da 3f72a54d 40de1440 c0dfa418 c01a23dc 40c9d96a 3fd707b0 408ad7d8 be212d0e bffb2917 3f333241 00000008 c0181047 bf8aa59a -> c0f97648 c3020c9c c28f1d1a 3fcba7d5 47baccec 00000000", + "424440fe c242d229 c1a21949 3e9d4867 c0127d2b 3f3859fd 401cdbeb 3fcbc712 3ca3d70a 3a9c120b 3cb0bc8b 3d9d13b1 bf79816f 3f07f7ac c093b382 bfdf9c74 bfae1a20 3fa1a630 c0f691e3 bf181aa0 c00edd46 c02c8ae2 bfe6e7ca 00000002 3f9ec80a 3eed332b -> bb6b48c3 3cb91fca bc257000 3f8ef87a 3e2cc81e 00000000", + "420d252a 3f1ef800 c0e0e3e0 3d3699bf bec8e55b 3ee8515d 3f452ef2 3fd7f779 3dcccccd bdb36116 410f8916 bedbdbdb c02127b3 3f8e79a1 3fc30b88 c062b7ce c0cb7371 c0e5a5e9 4079c1d4 bf811d44 c0130396 3fe08c3a 40275f1f 00000006 bf43c50f bf08d1c7 -> 3db11a11 3d3adb04 3e175b8a 3d041bfe 3eaa9013 00000000", + "41f99554 c0e978f8 c1a5566e bfc1d938 3f1585c9 c00fe2bf 403b7c51 3fc85c9c 3dcccccd bfca77a7 408f906d 3f193971 3f15a6d6 be17aa10 402f102c c09b3db1 40431dec c012636e 3fe8b270 c085fdb8 bfcd85e4 c0231cd2 4021ff47 00000002 3fcb3138 be1843a9 -> bfe9adc2 401ec9b6 bfa4935c be00f741 433f622b 00000000", + "3fd7b640 4245d3d8 bf8e5180 3feca912 bf34a12a bebbd25e 4023c3a3 3f8bcf74 3f000000 3c975c69 4235cc82 bf0c41d2 3ff61c42 3fc6f307 c09322ac 40084e1c 4059edb8 bffa9c00 bfafdba4 408d0172 bedf9348 c03d3a45 bfce93a1 00000001 40105a8c 3f881791 -> bc09e4d9 3b317d0b bbfbbf34 be549567 3c49833e 00000000", + "c2477acc c214cb3a 42282bc6 3fb65244 bfc3c184 3fe971bc 4031ca9b 3e56bb17 3ca3d70a bfeef7e0 3f03e904 3e956e75 3f8f101a bfb97a3c c0396f6c c0c405bc c0f08f4c c021f8a4 40ee6c78 c0edce19 bf6dbea8 4007f5c5 be66e1fe 00000001 bf7c545a 3f656811 -> bf928252 c0b01891 3faab05d 3e71b3c6 44369c78 00000000", + "40f320d0 420f2e18 420ae070 bf0864c1 bdb98fad 3e414e53 3fc8cdd2 3fff0f0a 3f000000 3bb65731 4201591a bc3cfd70 be89faf3 3ff952ef c024e734 c0f913ba 40ffb1a4 c099b85a c022c826 409b6c42 40118cef 3fa631bf be0ae912 00000005 402295bb bdf8a462 -> 3a308d2a bc7f529d baff4006 3f152f5d 3b8130d8 00000000", + "c2478100 c1ce6ae1 c154b250 bda51001 3e47eb88 bd32e22a 3e5cdc4e 3ff9c4ef 00000000 bc3f6b96 40037df2 bd4a95c4 400b361e 3f8b5169 40995294 40baf25a bf0b5c30 3fef7270 c0af5a50 40f5366c 3ed2fc71 400bcd2c bf86b4b8 00000004 3f0b01be 3f906982 -> 3c1a2e01 bc9249f7 3d19825b 3ea4c9b4 3bcdc99c 00000000", + "c157ab94 c167ed68 c2252a4e bfaec272 bdb64b67 bd78a1fc 3faf4d86 3f84a192 00000000 3bd006f0 41d92909 3f55e7d7 3fca9bd2 3fc863d5 4050f63c c0aac42e 409bf5e0 bf78e178 c03990b0 403f6f1c bf34598c 3ee5195a 400b5918 00000004 c023ceb9 3f4451db -> bd855545 3d917e22 bd0c6035 be8ef69f 3e4fb84a 00000000", + "c1db574c 3f594b40 422ae0ec bd615e31 3fb37a70 3f8504cd 3ff02116 3e84f74e 3f000000 bda33ae9 3df9df39 3f43211d 3fdee1ff 3e385923 c0d69a4c 40ed3eaa c0b73da6 40c3b098 402c0cd8 c001fa6a 3fdd7c50 3e98b4a6 3fa2e5a5 00000006 3f7705f8 beefa8cc -> 3e58a321 3eb79ea8 3ef26f55 bef98fe9 42273317 00000000", + "c20a8978 4205685c 417bf410 bf61a0db c00ee0e8 bf0439b9 401d2067 3ffd9df8 00000000 3d947e9c 418c3582 3d933996 be45b86a bfa6d9aa 3fc75310 407fac18 bf5800d0 bfa2f5b4 40e2c4c2 c0c061d0 c0281213 be91ef15 3eb2553f 00000006 bf0d2d29 3e2a2af1 -> bde71b4e 3e21c749 bd08dc4c 3ed587b1 3dd06a41 00000000", + "41ab007c 417d22a0 4156eb5c 3e1f3265 bdff4f7d bfe62d7a 3feade1a 3fa40c5e 3ca3d70a beda0990 41b48aed bda7fbaa bf103e93 3f4fb8d0 4037ab34 bff94fe0 bdf1c8c0 bf028420 c05d9d84 c083b42c c002d3f4 c0283631 3f2450a0 00000003 c0275336 bf1c0689 -> c0690514 4028b7a7 3fcf909f 3d3a6f83 43585e74 00000000", + "c0b44d48 4204f0f0 c1b650c9 bda3325f 3f769193 ba98aa64 3f9855b5 3ee50887 3f000000 bb313e35 3f39ba1e 3f5a7ea2 bee862b9 3f358fc3 4095fde8 3ff2c8f8 c07151f0 3ef56ca0 bfb22e08 409782fa 3f941f53 3ef3c426 bee0c01b 00000001 c015fe08 bf969217 -> 3b266d90 3a03fc72 3a065e30 3d129e66 39d697d0 00000000", + "41ca8930 c21b281c 3f20e700 3d726198 3f413a12 3b921f20 3f41d2b8 3f6e9a0d 00000000 3b3f955c 41c9570b bf402abc 3ec8b462 3d859281 402a10c8 c0406bf6 bf582c48 3eaac7c0 bfd74818 408c5902 c02f9e76 3fcea2f4 402b5d4e 00000001 c0165082 bf5bfc6a -> b9f1d0e1 bae5c874 3b991f17 be1e2685 38c80158 00000000", + "c02a9df0 4214aafa 423f371a 3e5b2aea 3e9663ec bf2d6447 3f59c4b9 3f51ea58 3dcccccd 3cbab03b 40e02258 3eb314b3 3fc9e004 bf2b624b bf47e428 40d16a7a c080b778 403a6a28 4093f8de c0c0d70b c039cead 40379675 bfef73d5 00000001 40368ca2 3f4bfb22 -> 3d026a32 3c7b95f9 3d122b58 3ec27786 3c20dd80 00000000", + "c131ca20 c215af01 415938b0 3dc732ce c00bb1cc 3c5874e3 400d5e50 3f994504 3ca3d70a 409819d1 3f8e0320 3fe5fea3 bec13260 bc60b3ec 3faac290 c0fcb67d 40373774 c0e75f4b 400a3724 40da64aa bfae5775 3fa08d9a 3ff9fb95 00000004 bfc525db 3f8fc6c1 -> c11e654e c1a19a91 c22b8617 3bc62030 4711c51e 00000000", + "c1607b64 41bc6314 c1de9344 be0e3bb0 3d95ab1e 3ec44392 3f9bcc61 3fcd8ec2 3f000000 baee3526 41e5a14b bffa1e59 bf418314 be4888fb c02765e0 c0c79b58 bfbb1adc 40e52ddc bfbd0214 4073a48c 3e775b14 3fa4e1a7 bf85a3e6 00000004 c0219fc9 3f51593d -> bc0ed100 3ca53395 3b5b7ef4 3f052be0 3c5d106c 00000000", + "c19a40d8 c236a3e2 c1454224 3fcbb0e1 401ec8f2 3eabbfab 403f3acb 3f892ac3 3ca3d70a 3a8e769b 3dc88277 bf9282d9 bd44f16d 3f15dd9d c07cf312 bf6d31d8 c0615196 40f7c944 c09dea16 3da8d080 3f640724 3f9844d2 3e83dfe4 00000004 402cc07a 3f14ea8a -> 3d8f6e5c 3c3c1e1d bd016fa7 3dd2b9b5 3eb1bb90 00000000", + "42228352 413da808 c1d62cff 3ecde4dc 3f6bf3b0 3f2e5852 3fef9f72 3fa854e4 3f000000 3bb035b4 423ddf19 bf3010e8 3ffb2f5f 3da2dc21 bffe7b54 3f33db50 405dd408 3ef82860 40dc470e c09c11a7 3ffbe340 3ed392a1 bfd5f962 00000006 3f5d90e4 3f03ea63 -> 3ab0594e 3a73af29 bb7c18c2 bf023296 3ad7ca4e 00000000", + "4147a208 c1dd0c6f 42236f7a 3f316555 bf91082f 3fbce821 40002582 3f574315 3ca3d70a bbae6b27 4066c805 3fa60ac4 bf55edd0 bef93874 bf266a98 c051acda 408b887e 40e17232 400788d4 c0dddb05 beb342d8 3e0f7e44 402ae8ca 00000005 400afeab 3f42424b -> bce95519 3cf9e170 3cb78152 bf349c12 3e5e4b0a 00000000", + "bf85a280 c1efbf95 c22b1c60 3ee3a0b3 3fde78eb bfa28fb4 400eb535 3fcb38a4 3ca3d70a 3bcc1631 3f197ab1 4009c84f bf87bf43 bf3fe060 c02269f6 40a48412 c08d8ecc 3f500120 40bfe67e 40661fd0 4038be27 402f0b5b 3feabcd4 00000005 4005cd93 be141c7e -> bc1d66fa 3c3ee2d2 3c8d6522 3e91c396 3ba7f7b4 00000000", + "c0d38930 c2395499 c20ad788 3ff07976 bfdfc4db 3f2c5886 4029cc72 3eb862bd 00000000 bfad59a8 3dcc3958 bf6911f5 bf8f407e 3f983dbc 40aba542 3e786a40 c0d46209 bf782370 bf9dc934 4083a84e be37ed5c 3e9b41bd 3fe34ebd 00000008 c02a3d1b 3ecacbe2 -> c0c7ad0c 410b6b81 40e25c0c 3cc2150e 4b9df232 00000000", + "41784a20 4195f57c 421c0e08 3da92aad be88ef1e bedcb0cd 3f2da822 3fd26d98 3dcccccd c0519ff3 4038f2d8 3fd39364 3fa4b5e5 3f369a92 40f95c70 c0151496 408f4d12 c00f9f4a 409f1124 406db770 400a50d7 3efcb5f1 c012951d 00000002 4006534c becf3c53 -> 407a39fa 4048fe0a c06b2519 3e7f799d 44cfa8de 00000000", + "4196f400 c202c6e9 c23a5f66 3ea1ce65 bfbc8d35 bf5d9ca7 3fecedf9 3f91335d 3dcccccd 409977e2 409d8f26 be6abec3 bdb5ce9b 3f1994ea c0b7373a c0c22490 40169f78 3fc14030 c0d2ed7a 40205dfc bfb9b45e 3ef4436b c03fd6d5 00000001 3fea6092 bde68c9a -> c08af436 40a024db 3eb9c3d6 3d5736b5 449ff984 00000000", + "41fa9c64 c240f078 c1bb1f89 bf085976 bf0e1a65 bf8d0709 3fc1d440 3fda440d 3dcccccd bf235a34 3c4c8be3 bf93338a 3fed90bb bfe1a8d6 c0a12834 c0b7fd70 c0fc0747 c0dfe301 402ef414 4099f948 4036afef 402ae4b8 3fda4e38 00000003 4014e6cc 3f128f0c -> c2959a32 42e43ed2 c2de1df4 3f638330 49f32056 00000000", + "40f71830 c2317a68 c18ebd70 3f7014de 3ee1695e 3e1a4532 3f87715e 3f0fcd0b 3ca3d70a bd517449 3ce29702 4009f12e bf9d99c9 bf9b15ee 40d69a0e c0590b12 c09936ee 40814958 408dc3a6 c0972414 403980b3 40296153 c0193d67 00000001 bff2b67e 3f0a886f -> 40467197 bfe9555f bfd77fc6 bef7d08c 433e2daa 00000000", + "c19ba0d0 40fb5420 40ea88f8 3f4fab49 bf468452 bf250bbe 3fb112de 3f643c27 3dcccccd c002f858 3c868164 3fe3e33b bf6615e1 3f97d6bc c0bac123 40724a90 40c51e78 c0ae1058 40a3df4a 40b27612 3fea9e1f 3f5f31db bf3353d4 00000004 bf45ed53 3ef587e4 -> 435c8c2a c2e8ef1e 4310c56d 3f08b37b 4992e9a1 00000000", + "422524a6 c10b82c4 c1f24915 bf5c79a5 3ecff885 c01389b9 402be820 3ff58f3e 3dcccccd 40050a80 3d3494cd 3ebadeff 3f32680b bfddeef4 40c25922 40b18bbc c0de018b 40ba37f4 3f6661f0 c0d3fd0d c0229493 be674012 3fac3c7b 00000005 3f9d11b4 bf8a8f33 -> c18b3483 c2a487d1 4303220f bfd3f220 49fbd527 00000000", + "be2c2800 c1bab7b2 41728ed0 bf3cdfe5 3fac8a5c 3fba3f64 400890d2 3f60fd0a 3ca3d70a c113054d 3f39ee05 bf1c5867 bdf04867 3f2e9ea8 c07d18ae c09b6eb0 409dacde bcf78500 c0b95baa 40381c30 bfc27550 3e102250 3fef5569 00000003 3f80212f bf59c6a7 -> c2104dc4 c1423776 c1bb7a9a 3dbf07a8 46b37394 00000000", + "41cd97ac c0a107d0 41bf5968 3fb97991 3f2615d8 3e55555b 3fd622c8 3f378a65 3dcccccd 3f170beb 4047123a bf858f1c 3f02c935 bf67cc5c bf1f4ff0 c09b3371 40cbd39a 3f8c61c8 40e5f30a c0a5f8be 402e2812 40275880 bf5e3db9 00000008 3f062074 3e8b6b9b -> 3ccc2fb6 3f6e4085 3e3e6883 be2f956d 41a73e5b 00000000", + "40666d70 c1553f00 c1f255cd 402d4b95 3f11b02a 3f05d7f1 403611d5 3fb97824 3ca3d70a bd119ed7 3e3ea5ee be99692d 3ee1228c c02b5480 bf6b6748 bfa4d980 403fd544 40ae8c46 c087fc0f c08d6931 40302ff4 3f62754a bfb753ba 00000006 c02624ad bf4ad807 -> 40b4e106 be6e2aea 401189ee be53b0e1 462946ff 00000000", + "41720bd0 c1a857bf c20da637 c009ccea bfc2b27b bc65ca46 402af96f 3fe16954 3ca3d70a 3b5026c4 3d3d5ba9 bf9534ea bf6ad7cd 3e2b8ae2 3ffe0248 c092b425 40bd1930 be96c170 bedd2080 c015c192 c009290f 3fc0d81a 3fcb9380 00000003 c0179741 bebd6f9b -> bde701d6 bdbdfe1c 3c61af7f 3eb8e265 3f905500 00000000", + "c09932f0 c1a08c32 c24284dd 3e9888bf 3f7c44c5 bf1a1bcf 3ffb8d13 3fc5ce66 3f000000 bc10fd33 3db74b78 bf2e2f31 3f2735bf 3fbac4c5 c0e43b33 40c4cefc c0c8f02e beb2fcb0 c02b0d98 c0a1d605 3fd852f2 bfe44619 4016060b 00000008 3f09a585 bf00e469 -> bd7fc259 3d7620fd 3e1a8567 3d7576b9 3f21ad80 00000000", + "4139de08 40bb59d0 40defd98 3e9b6afc bf9da589 3ec3525d 3fa98c4d 3faba29b 00000000 40592c1c 42af514a 3f0e99f0 be07a3d4 3f24214f 404746b4 c08e17ef 401ffff0 c0e722b9 bf12f5e8 c0bfacde 3f21bca3 c025314c bfe55442 00000007 3fc93482 3e140e89 -> bf0093f5 c0caef56 bf4411e3 3f0052ee 43d189db 00000000", + "c20eae22 bfb74d40 416229b0 3ef6ffeb bf897403 3f0408db 3fa4dbb5 3cdc9725 3dcccccd 3b04ffa7 40fc2093 3f1e6cf3 3fd5f9cd 3f24d58a c0f49898 402b35c8 408ad612 3fcfe350 bc611200 c0f73c75 4005e85d c0289cb5 401e284c 00000005 3f0f75ac bf12c2ac -> bb4604a1 3aabf60a ba9b7909 3ee6bd19 390f4da7 00000000", + "c1466ef8 42373018 423e2bf2 bf436d0d bf5721ef bfbec504 3ff23a28 3e40f355 3dcccccd bcbc0e25 400ab5e5 bf9df5fe bfe198ae 3faf5210 40dab95e 40e82ee2 4014c034 c0352792 bf561370 c0daea06 bf9a32b6 3fe79dbf 3fcde64b 00000005 40135d0d bec2361a -> 3d7103a4 3bdccd56 3ca113ce bd506ff5 3f4d2f62 00000000", + "41c0c8ec c11337f0 c1b85268 c010e57c beb34448 bf19e6b6 401a0dcd 3ff71332 3ca3d70a bffb1dad 3f9992f2 3faf5b70 bdc7849e 3c1c5999 4091dbfa bf5fffa8 40b5292a 3fe5dd58 4015a614 40fdb306 3ff94e30 c0266ef6 c025e157 00000005 c0093386 be404792 -> 40a7a07d 40bb279a bfb69dc8 be5e1f0e 444bb5b3 00000000", + "c1bbfa91 c1df299a c2428555 3df7ee3a 3f573b2b bf245c83 3f899019 3efea0fa 3ca3d70a bb34a430 3eee3e4f bdb9bbb1 3d771ef4 3f46ec89 c0b7409e 3fa4dbc8 bf825e28 409bb574 3f9946f8 c0ce23a7 c03705fb 3f23b93d bf006945 00000004 c019d9a8 3f2ab435 -> bc89c025 3cc7267e 3d48f612 be400e98 3d8d9ba2 00000000", + "c18362b2 41e6d504 41ac1f90 bfa10315 3eb76aec c00ada8b 402f9488 3ed72650 3f000000 bead7b20 3c6d50f7 3f6f0b39 bf73271e bd81742f bf906ca0 40d30aa6 bf8039fc 40d7afba c0a69f68 c086e656 bf4b30b9 3fab6183 bf9effd4 00000003 c00dd84f 3e7703ee -> 419cacb6 c1adc753 bfb0275a bda96644 47d56ddb 00000000", + "c155ecf0 c24504a5 c11ae52c bc07b885 be591e90 3eb31ce3 3ed17c8c 3fd6604e 00000000 c01df9ab 3e30160a 3f2d8ed4 bfc448df bf8e9158 40ffee12 401d274c 409e5880 c0ad0144 be727aa0 40f93678 3ff21fe5 3eb37090 bfc4c97d 00000002 bf5aabac bf62c118 -> c043b429 410165ba 41972486 3eaf6939 45f1f6ef 00000000", + "c207047c c207b1bf bece8800 beab00cd 3f1db9ef bfb93065 3fcf95a6 3f354857 3ca3d70a be5de13b 3c2aacfa 3d450d92 bf0c7fa9 bf218a1b 40cfd02e c0e76b93 40a4c222 3f8bb820 3f4a01a0 bea6cf50 bf81e3c7 bff24bbe 3ee63b9b 00000003 bf4d94af bf172b4a -> c02c050e 41ee19cd 420b1614 3d14301d 470f7bb4 00000000", + "419b26e4 c10def30 42213ffc bf7b7cf2 bf054b1c bf14d5b4 3fabc8b4 3f5fbf28 3dcccccd bd5e346e 4185b599 bea4d384 3f8cda8e 3f0cb8c8 40989d30 bf2b78e8 40d8dbf2 3eb3fd00 4099e148 4047bae0 be9e4549 3f00f3b9 c00dd826 00000001 3f1bbc2e 3e8aabcf -> 3def4edf 3ce2151a 3c2874bb 3ee546ae 3d89455f 00000000", + "c104d29c c23e34bb 423b8ab6 bf8c7de0 3fc8ca6b 3e0c7d9e 3ffa324c 3fe15be5 3ca3d70a 3fcc9674 3f2b4de3 3dc2d553 3fc0ee06 3f806820 409fe11e bf557af8 c058d084 403e749c 40077eb4 bf842d68 bfa0bb3f c02cf0de be2526db 00000006 bf1129d3 bf725f59 -> 3f77ae73 407861f9 3fd3061f 3fbd388a 43d56def 00000000", + "c22ddbd9 421b8aca 41cc961c bf4ba6ff 3e7259b2 3f083bb4 3f804972 3e270ac0 3dcccccd bd043a51 3d860b5a bebd5e4c 3f72e7ae bebb8dc8 3e475bc0 40902578 4086d3ea 3fcfa398 c0137f06 3c43d800 c022efd6 be8060b4 bec6b921 00000007 bfef68ae 3f2b6304 -> c00e1deb 40aebde0 c0261164 bd7b0cb8 4572cb65 00000000", + "4232eb54 41ddd130 41eaecbc bf3257db bfc92548 bf2b9e9b 3ff3c76b 3f1841b0 3dcccccd bc7dfce9 3ec050b4 3ef997ee 3d686c4c bfa25e08 40834b72 404cc590 407b0404 40805d7c 40b4dac8 beff6830 bedd4ab3 4008fd08 bfc5a176 00000004 c007bbe6 bf8c0e38 -> 3c82ca58 bf46f5ae 3f48b354 3e34f148 41578a7c 00000000", + "c129c2e8 421fefba c1934862 3ea99261 3dc52940 3f422c19 3f5ef81d 3ff19cc7 3ca3d70a bb1ea48e 417491a1 3e097475 3fd06d3a 3fc371e5 40c0a50c c0378dba 3f88c360 40ecf160 bfc77ba0 c003278a 3fd406b3 c029f863 bebe004b 00000003 bfc2333e bf04ff14 -> 3a96dcc2 bad8a71b 3acf1f7c beb5f833 3a04aa60 00000000", + "c102191c 41f6d880 c2240e80 bf9e6fd2 bff83793 3e89bfc0 40143d53 3fc7d63c 00000000 3d94bab6 3c9d4196 bd131bf2 3fc9bfea 3e647854 c0381d68 c0dfdddc 3e1cb8c0 c0935cec 4075f394 3f8b2660 3f874554 bf468926 bfacd13c 00000005 bfb89fbd bf120307 -> bd027786 40c6716e 3f47f2d6 bdb6800a 45044bd2 00000000", + "c109e37c 423ab412 c244c0bc 3fc283cf bf3e08c5 3fc94911 40254ba7 3f0bf588 3f000000 3e2f22ea 3fe8e89e be12e0f1 3f02ff99 bfd58896 402dd1a8 c097acd3 404b65a0 bf080d18 401e22f0 c03d7f56 bfcda515 3fb22062 bf3c7d53 00000003 bfecab04 3f8a29e1 -> be766de8 3fa2405c 3efe3198 bf27bb8e 41ee615f 00000000", + "c23309a9 423d3838 40c51858 bf280455 bf01bdfd 3f57b813 3f975348 3e8734be 00000000 bedf1887 3f4574bc 3fe1de97 3eab61fc 3e21c5bc bfd3c5ec 3f922128 408f12ba c0a03550 3f150f70 402d5d44 3fd24022 3fe1c175 bfa10c53 00000007 c033ab35 bf53ea0d -> 434c6e50 c344ae24 4081c8d8 bf6c05d4 499d8208 00000000", + "c205d1cc 41fad414 422af688 bfdc0842 3fd1d9d7 3fde48bb 404419e2 3f9b9127 3dcccccd bb34bed0 400d1a9b 3f5b03da 3fcaea29 bf96d831 406345d8 c0e63a75 3f483400 c0e35a7c bf4fe960 40e35740 c02cec87 bfde617e 3f5e0205 00000004 3fdea91c bf12b896 -> ba4b7088 3b874475 3b7322d4 bd2a2d11 3ad7201c 00000000", + "c16547ac c20ac8ce 42393d0a bfdd8d29 bfa8d46b bffce35d 4043c60f 3f9947ed 3dcccccd bba456bb 40133979 3fcc3812 3fad163a be86cdd4 40d5e77a c0f7a8b0 c0ccdb8c c0f977c5 c08f5642 4076cb80 be97373f befd3541 bfd03ade 00000006 bf2e9988 3edab5ee -> 3bd95994 3ab3ebe1 b9aef29c bf12071b 3a3087a7 00000000", + "c150e8a8 41a9d334 c237aab9 3e727d92 be8a2bc1 3de911e6 3fa12a80 3fe1e9a9 3f000000 baa89509 3e12ac41 bea7e14c 3f682bc4 be0e0c5c 40b9dc7a bfa98944 c0b51aea 3f23adc0 40d495d0 40f2fd1c c017e65e c015a822 c0204897 00000002 c03c844c bf0db83d -> bb9ddb44 baf22e6c 39a40fbe bec0cd62 3c814430 00000000", + "c1779330 41d3fcf8 41a600f4 bf84b3a8 3f886b05 3f6bf58f 3fe1c18e 3f37e791 3ca3d70a 3b9885d7 3c87bbb8 bf5f9d59 bf1c4315 bfb921c3 c0ec7d8e c0de5c2a beb8d4b0 40a4f472 3f0e52e0 c0bbe219 40301a24 c03fdb6d bf85a596 00000004 c01fc2ac bf384bef -> 3f5bb52a 3f1aa69c 3fb8daee bf00e1c5 43fa9db0 00000000", + "41be9900 c20a88c7 c2161cd6 3f8502bb 3efecf2b bec8ce39 3f9bc9d0 3fc9e830 00000000 ba93f6a6 429703bc bf8107d4 be9d8665 bf870835 40637668 402544ec 3fd46c70 bf569c68 c051be06 3db81080 bec5059e 401245bf 40098772 00000001 c01e1288 3f19ec9f -> ba5506b5 bb3ab429 3ad6cdbc 3e932cf7 3826fefe 00000000", + "420a161c c1dda6b7 4237410a 3fb1e4a7 bfd8263b 3f5303f5 4048ee38 3fcd6581 3f000000 bfc29bcd 4286b4a4 3f3e19d9 bf4dd026 c0219f30 40be10fc 4025cae4 c09462a9 40d91872 c00fc7cc be488d80 3fadb356 4005ca45 40019c6e 00000003 c0355962 3f9391e9 -> c03d4ca8 41cd7902 c1197b8c be144e02 4640d16c 00000000", + "c1a8178d 422c4494 c234d038 4020866a 3e7cea86 3f9be1f1 40335976 3d02b70c 3dcccccd 3d854124 3cab2025 bf0a2708 bdcc563f be922813 c0c409e8 c0d14a2a c0ed4a0b 40cba5fe c04d0bb4 404613b0 3f9b0eac c0043bc0 4025effc 00000006 c011dd6f bf6b971b -> c326e3be c22f33f6 c2b00a79 3e7123c2 49cfff22 00000000", + "419dba70 41d42290 c0c32cc0 bec3ea43 3e71f52d 3ff70490 400169f5 3ecfa6c6 3dcccccd bb81d7e5 409c17b9 3fa5aa94 3fde6df0 3f84086d 40fb4124 40b3f9a8 3f3bacc0 c0fdc0e9 c05ca8c2 40f38292 400e859e 3f80be0b 3fff24df 00000007 3f022513 3e344908 -> 3af6d349 bb694ecb 3a0664e8 3e41f6b4 39ba538f 00000000", + "420330c6 c2147000 41f5b308 3e551f4b 3e82597d 3fdb56b0 3fff75be 3f007a1a 3f000000 3ab12b42 401c116f bf87dfda bf87cb7c 4025464b 3f53dc00 40184c8c 4084e006 40e1710e 40995c6e 40f12b96 3f0d4980 bf5a92f6 4004250c 00000008 4010abf0 3f39443f -> 3c38460e bc9a163c 3a005e80 bec76465 3fa7c88a 00000000", + "c12385f8 c1e65dc0 c13c6bb8 bf910edf 3fc77619 bfc21f0e 4057153a 3fe89b3a 3f000000 bf97cc4d 3d9e94ae 3ffc5c42 3f9f0fdc beb2e76a c0db84b4 400251b0 3f97e4e0 405a79dc 3ecd9220 c0031f4a c03ae620 bf7eab86 c0115900 00000008 3fea95ba 3f24ce60 -> 42881fbf 4254e4b2 c1d00a1b bf12c739 4aa36bf5 00000000", + "c212ea52 41aed330 4139f090 bdd3402a 3ce1cc55 be557cde 3e6fdada 3dfaa7f6 00000000 3d61738f 42b01287 3dbf3187 3f13b43a 3f2adba3 c0cb92ef c0bbdc5e 3f8a8a60 4085fc8c 3f078c90 c02aa800 c008591e 3f9ccae4 3fd57ae6 00000002 403cebc1 3ee17800 -> bdee49fe 3d8956fb bd22a158 3f30c03c 3f0770eb 00000000", + "c0b9ebe8 41244100 41da3e60 bfae2d05 3f06c2cd 400023cb 402c6947 3eddba09 3f000000 bd84edcc 3f695644 c002ab23 3fc37a7a 3f8ed19b c047faf2 3f669f80 c0fff0ed 3f1e5080 c0eae7ee 3f8641a8 c005b3a9 4000dc92 c00d2f2d 00000007 401c3e30 3f43a4a3 -> 3f9d03e0 40996bd8 c1095e7a bf2a3718 4549bfd9 00000000", + "c20f41ae 41c9c26c c1fb25cc 3f883789 3fbc2152 3db4cc01 40017e1f 3ed38c4f 3f000000 3f24eb6d 3de1a947 3e9b06a8 3ebd7b94 402e28d2 c0a300f8 3fffc270 409625de bf8ce9f4 4087074a c0a7478e bf8e2a6d bf050d7e 4017eeaf 00000008 3f907b51 3f2f6238 -> bf873e65 c012649d c08efce6 bea67ed8 461b000b 00000000", + "c1f7d32f 41777d00 403ba2b0 bf76f9a4 3e9aae5a 3ea0d637 3f8899b1 3edb422f 3ca3d70a bad28065 42c373c2 be8c1970 3e509465 3fac2dce c0ca7f6e c0a874be 3eca41e0 c0874562 c0fb60e3 c018da5a 3fb68143 c017554d 40204b2b 00000008 3fc5da88 3f503532 -> bd19fa7f bca4a5df bb8fe62e 3e9587b4 3c3b4e2c 00000000", + "c240e292 c240d529 4221edf8 bd47d0ff bf341d00 befde147 3f657041 3fda58e0 3ca3d70a 4006ebac 3faea17b bfe89837 bfd258d2 3f03767d c02dd0f2 c0d5a872 c06af5f0 40e112e2 c0a9d016 406ebcd0 bf5e1cb8 402bc514 bf3b88aa 00000003 c00746e8 bf3b91ff -> c0b2af14 bfee5cce 41588d94 3e9c6286 4561ca00 00000000", + "c1d82cee 41ed0be0 41f8e9c4 bc031b68 bf75dde5 bf1dd613 3fa9e6a7 3fee20fb 3dcccccd bd154d01 3c5310cc bea13e4b 3f8a6c77 3e5d7174 c066e396 c0f9fe52 c0155474 4067fa40 408bf19c 40253a00 3fa7cd2b 400207c5 3f62cdd6 00000005 bff8989f be7bde8c -> 3fa466ae c0871dbc bf50d1bf be08e4dc 44f1004e 00000000", + "c2276238 420f0032 414a4be4 bf8c17d8 3f891faf 40069be0 402d4c5a 3f87b0ed 3dcccccd c0dbba32 3d05af0d bfbda55d 3fbc5a8a bfac9df7 c055c364 c082c504 c08357ed bf846728 bfea11c0 bf1a3d28 bdd5924e be4b7419 403be88e 00000007 bfd89c5b 3f13f683 -> c478219e 4482bba6 c4727164 bf33b67c 4cecde19 00000000", + "409f2940 41274e90 41b331e8 3ea8a0e0 bb96152a be703e62 3f626510 3f75bcf4 3f000000 3cb8e36d 3fb6f844 bf7c6c7e 3f69b02c be8de9cc 40db8062 3effb120 c0de2b4b bf3c6aa8 c0112c8c c06c1932 3fa89715 3f8dc080 c01852fd 00000002 bf906eb4 bf54cf9a -> 3c87e0ff bcef9918 3cdb0b18 3e0ba2e3 3cd3ab47 00000000", + "c220971a 4130d710 c21e3817 3fa7f44b bf93a64b bec3dd93 3fe831bf 3fa374f9 3ca3d70a 40a81448 3d959b27 bf428a9d 3f6e1cd6 bf67b1cd 3fc19a30 40a60378 c07201da c0f08919 40b8c2e0 407a1798 3f319590 be4be73e 3f659b84 00000008 3f48df07 3e554531 -> c250a5f3 4281b5a9 c27938c7 3ddbcfeb 48a11fd0 00000000", + "4174d1f0 4212c4a2 41f4822c 3ece5bc4 3e4f88e2 3edaa021 3f1f04f3 3fd510b4 00000000 c11072f3 3d6de2e2 3d2bfbef bef5b059 3e9dcb0a be124ea0 bfb3386c 401f6f4c c0ed874a 40f473b8 3f5f2d40 3fe0d2a1 40193791 3f237e9c 00000006 c0146259 bf0d4944 -> c0b9ddfc 434b4fee 41dbe69c bf988074 4dd4d0d6 00000000", + "422a9e6c c1c2c234 42315d34 bf993595 bda1eb53 3d9ea913 3faf4b56 3fd64ff1 3dcccccd 3c3257e5 406d8b0c 3f79a39c c0083f2e bf8f92ed 402bbffc 40ac62ee c0274126 c04adfc6 c0bbedb2 c0dfbcda c003f9aa bfb8e389 bfd43de8 00000003 bfef4c66 beb0da1e -> 3c228125 3c0fa1fb bab46fa0 3e954d31 3c14ceaf 00000000", + "c1dd7640 c21f703d c1c30514 bf5e4411 3eb4b4ef 3f3d55c8 3fac637a 3fc3b419 3dcccccd be59e869 42c7252f bf999b0d 3f4d0542 3dd4eee5 4050f204 c070f898 c0a786ba 40db4fb8 3f800a40 40970a2c 40125e01 bfde13a5 3fc406f6 00000007 402131fd bf5bb42d -> 418f4ce4 42094389 c2769237 3ca10ce0 479cfc89 00000000", + "c0b239b8 c0e96df8 c24018a3 c010a41e 3f1be7c4 bf80ed98 402bd6e7 3faf2525 3dcccccd 3e722e67 3e50cc35 3ea600ef beab5394 3f542568 40ae720c 40c747bc c09b6cd4 40db5484 c09da9b7 4094236c bf0e302a 3fb65a97 3f7304df 00000004 3fe01131 3e799c0a -> beba292d 3e306ec2 bef67cb7 bf4a53aa 434bbcb3 00000000", + "4226e8b8 4213fa96 42038b2a 3f6e8ee2 3d900443 3e53dc4f 3f7e07fb 3fe10bf3 3ca3d70a bbbe215d 3c9124ba 3fe5b845 3fc11bd4 beda750d 40a45424 c097028e c0ab1726 40cd7ae8 40574748 402ec740 3e8e16e8 40324175 3fab05fd 00000003 3ff87758 bf2eb31f -> 3f916fd0 3f6ceccf be879528 bcdc056c 420f5c8e 00000000", + "c15670bc c21d8367 c11f904c 3e2c16ce 3fa9b0fd bf191e3e 3fbb66af 3f4c9e97 00000000 3b2d1b67 41af6db8 becd8112 beda948e c013b797 bf0c01c0 c0644aa8 c070affc 408fc918 400b1d30 bf210740 3fc89aa7 bfa1136d be879fcc 00000006 bfddf7df 3f987caf -> bd8d2e81 be4148be 3d220862 c0350084 3e09369e 00000000", + "c18772a2 c2371f0d c1a7afd0 bf0bc957 3db499b6 3fb597b7 3fc5192e 3f5b3bba 3ca3d70a 3caf5e8c 3cca29e8 be3ef34d bf9e2a1d bf05707f bf780f58 c0c751ec 3fbf3118 c086207c c0de9222 c0f4a4fb 3f8f8de8 beb1e874 40266574 00000003 bfd22b41 bed320ae -> 3f31e972 40844ff7 3fe0c15c 3e842b9c 42dd8374 00000000", + "41a6a53c 42449a04 41eaa73c bf51b9ae bfeb56cd c0061dea 4058c8b9 3f76902a 3f000000 bafcf6ad 406b2b66 bf23ba2c bf8f39b0 3f14f7f7 c001b5d6 408c4796 c0f8cfb5 40e6b4f4 40db76a4 c04ca2d4 40281a77 3f4d10b6 40239d1d 00000007 40150f3e 3f03a75c -> 3afb2380 3c13366f 3ae81b6c bec20b9d 3e196f89 00000000", + "420e6872 c135c9bc 41cbc884 bf39cf61 bee0666d 3eab001a 3f694a67 3f9af79c 00000000 3b8a7c93 40c6f8ca 3fe0a7fa bf09b4fc 3fc886c5 c0913e1a c0d35560 40c3b020 400335b8 40544e64 4011e92c bf94bc6b bf97ae27 bf72b05d 00000006 401b14f0 3f814e35 -> 3e33ee4c bedbe538 bf0798b8 3f3e1a95 40458310 00000000", + "41319260 420756f6 405889a0 bda5f498 bdcd517f 3f01266a 3f056da9 3cce38bd 3ca3d70a c0285fb0 3caf4e1c 3edd165a bec37323 4028aa6e c0ad441a c0bdb9fb c0594c4e 40d313d0 c0cfa1f8 40532f80 c0179ab4 3fa38168 bf813371 00000007 bf0d8e98 bf3dc8a5 -> 4180ceb8 c12a0786 427e9042 bdb94f3f 4984bca6 00000000", + "414c68c8 c219422e 4205cc84 bf1cdda4 3e929055 bf3d35d0 3f803c59 3f70a91c 00000000 3b024a22 41a16752 bec863e7 3fb7b5a2 c01f1941 40e33472 c0551434 c0e72be6 40a55ef0 c01d8554 40e27ece bfc706ab bfcb2f2a 402205f4 00000004 3fecb06e 3f1157f4 -> 3c0a6f76 bb5b635e bb796dd2 bf10ac62 3af4a9f8 00000000", + "41c4f42c c0706e70 423032fe c013394b be36d098 be633622 40145907 3fa6d69c 00000000 be59267d 401f901f be1a6a3e 3fe1b1f4 3e572fec 40dbd160 c0d2817e c065537c bf2a50e0 c0874fc1 402e3e78 c02f45c7 3fc438dc 3e5790e4 00000001 bfd79716 bf1a3ba8 -> 3ea4a5de 3d4d45c9 3e73f1a5 3cea5a01 3f86b28f 00000000", + "423fbc36 c00462c0 41b7c170 bec40393 3f411b61 befcd259 3feb9804 3fdc7022 3f000000 3e87db87 3fba3a3a bec87ea6 be819efc 3ed65350 3ed52aa0 c0bdc1e2 c0c6e24b c00825bc c0fd9355 c03b2d1c 3fc11cdf 3eed7ef7 3fc85f19 00000004 40273e2e 3f3ac5e1 -> 402d56de bed67f6c bfc5adb2 bf5f6818 441748e6 00000000", + "4225e108 423003ec c05033b0 bfde4520 3f291fbd bf0721eb 3ff73971 3fa25b97 00000000 bd315b91 41a05390 3ed35fb4 3fdb8126 bfaba2ee bfaa0080 c0b8b54e c03d0848 40fde030 4049f830 c09d762a 3f905946 3fbc7d43 bff4d609 00000006 c03edf46 bf4a2432 -> bfc37e82 c0806ea2 c0a29720 bebdf3f8 446925d6 00000000", + "4244102a 41c4a340 408c4ba0 bef118c4 3e732b13 3e229845 3f3f7324 3ffc44b6 3dcccccd 3aedfb39 41ec7b9b 3f469d87 3fcd5448 3f5f64e3 40af6194 4045914c 3f870678 40af23de c0fcfa8b c06b60fc c02a0eff 4022346b 402a07c7 00000002 401d3eb8 3f27b2f2 -> 3b8fd80d bac32a20 ba893048 3e6ba3f0 3a14a1b3 00000000", + "bf242980 c21834ae 420c5ec4 3f0b671e 40312f2b bf71886f 403e6849 3faf5abb 00000000 c0483609 3c89b385 be980867 bd550287 3f28ca36 c0d24959 3f2ce9a0 40722dac 40d90416 c0d4887d 407f362c bf8d3728 40304bff bfe2cc30 00000003 401133c2 3e9f0110 -> c2849d93 c1781302 42f29542 3f18a76c 4aca5902 00000000", + "40fc6958 c2227c8c c106e18c 3fcd5cdf 3f87a06e bf92e7e2 4014d333 3e3083bd 3f000000 3aa26845 4219ae10 3e4ce9b1 3f056793 3e63e5ff 400e4c78 bf8e820c c0e418da 407e1c1c c09d9dd6 40980024 3e338efa bf8e4d42 c02d3e0e 00000003 c03e37c2 3eb9d7cf -> 3b57b221 3ad958d6 bbdda6ce be7480a4 3a28e571 00000000", + "c0501dc0 40b15f30 4145cc04 3f8a138d bf471bca 401523bd 402f0b14 3f050f16 3dcccccd 410aea46 3ca22c46 bf1c2810 c00b988a bd7fcfa5 c0bc250c c0cdd310 40e4a7ee c08db050 c01f82e4 c0d01bbc 3fac1bfe 400d883f bffd61d0 00000002 c00377dd 3e87ec87 -> 423705bc 42966b78 41727cf4 3e83017c 4bad1ff3 00000000", + "415c30b8 c1ea9e83 c22e46c8 bf26abf6 bf3ff199 bd59f7c5 3f8cc9d6 3e5807ba 3f000000 bd29b4a7 3e424bcf bff86100 3f3ebdd0 bf614c26 3dfaaf00 c0a29ae2 bf8f86d4 c0c3cbd8 40b33634 bf587068 3f31d2cb be5d7e62 bf9b9141 00000007 3f0f95c0 bf28e87d -> 3e0c261f 3d5052ec 3df3bbdf bec983fe 4051b150 00000000", + "c23c0f63 423e359e 41cd1ac4 3e99384b 3e24d60a 3ef141b2 3f14b906 3fb99056 00000000 bf68f0c0 3e9efa50 3fa665d1 3b89dbff be34a0ed bfdd23e0 c0ab593c 40cbcf4e 40b74250 c051b9d6 40b26c0c bff8d13a c01c2b0c 4009a921 00000004 3f313bfb bee7b5a6 -> 40af743e bd366847 c0608dee be8b2e9a 4340d80b 00000000", + "c1eeb71e c23bae34 40e3bec8 3f4905bf bf06ad4f bf80bc8a 3fb0a9e8 3fb94990 00000000 ba9379e6 3d32fffd bd817e7f c016a65b 3f9cead6 c083486c 3e871660 c050ce4c 40e243d2 40a4c16e 40d3c970 4012781b bfb71cfe 400239b5 00000008 4024160a 3e35f5c4 -> ba5e69ba bc8c462c 3c457983 bea31c49 3e703bbe 00000000", + "41a25b50 c11f87c4 c1ce8fdb 3ecbfe45 be855bd6 be9534d0 3f80d119 3f658125 3f000000 3b61fb97 3fb7c212 3f71711e beb760cd c01f4138 404449f8 c0e48740 402847e0 c03ee876 c0a85cb0 c0e84b77 c00dbd5c bf88ff7d bf57b6b6 00000004 c03d7061 bed6f0bc -> bbdf02f6 bccf45e6 bc91a0be be6c75fd 3d0b2bad 00000000", + "42130ba4 42275bb6 4182df7c 3ee2e334 bffd9891 be20c947 4005ac46 3dd6a604 3f000000 bbfdeb1d 42a20864 3fa11c83 be8e3a6a 3dceb8fe 3e202f80 be87efe0 c026a466 3fea27a0 bf58d9e0 3fe94168 c027cd0a bfe5f88e bf614cc3 00000005 400b40d5 bf184a0d -> bbffcb38 bcc9e4d3 3c8ec63d bf1968e4 3ea17334 00000000", + "419d601c c2278634 422bb378 bf34ffb8 3fe14fc3 3fb3bfa7 40170cee 3f4d1ca0 00000000 3e944811 423a0aa6 3fc55e53 bef7bcd1 3f083286 c0cc3292 408915c6 40e5056a c0eba824 c06457d0 407e8918 3e8c53dd 3e074a46 4001d383 00000007 3fc9eba9 bf43eec5 -> bda7f7b0 c06d09b0 c048aefd 3e3660c9 434cf4d4 00000000", + "c21d94ab 41efe03c c1f6a08b 3e670966 be118faa bf4e0120 3f735f4d 3f83c169 3dcccccd 3cc5e35f 427f40c8 3da75526 bf981beb 3eb123f7 bf9af9f0 bfb006d8 c05d1f6a c0023aba 40410e78 3feba428 3f3a7714 bfc1292a bf43a438 00000006 4015f471 bf34d909 -> 3ea39a1f bd93aec4 be72f8f1 3de52719 4216f664 00000000", + "421fc076 41930c00 422e64d4 bd811497 be3aaf22 3dcc12b0 3e74b108 3f8bcc79 3ca3d70a bddf2326 3d26ab81 3fb11195 bfa4c8b5 3f72030a c0f1c603 beeeafa0 40ae2f0e 405e35cc c0bd58f1 c0dbaa14 c004a6ef be471be9 c007a9aa 00000006 bf903613 bf3a2460 -> c0ad1b19 407e1e2a c040ac14 bf8f4497 449cac67 00000000", + "c0abad38 c22e0eb9 c18c74fa 3dea8398 3e164646 be5ba44f 3ee91346 3fdb2af3 3dcccccd 40a0b352 419e9b9d 3ef6207d beb3d973 3f0981e9 40ab3aba c0e2671e bfc40d1c 3f09af50 3f0edac0 c09a434a 3e43b9bc bfe6e686 3ff701f1 00000001 c01ad778 be4dce25 -> c0c72681 c174b505 c09a0983 3de3e08d 44af6853 00000000", + "be12d700 c0facf18 422b2a90 bf32db7f 3e8292c7 3fdffba9 3ff6f93e 3fb3eb94 3ca3d70a 4096bba1 41a20eeb 3faf50bf 3f8baa42 3fa8d7c1 bf9980f0 c0e30c1d 40e3b386 3fa5d8d0 c0931795 c09bf954 4021821a be86302f bf2f5455 00000007 3fa109c6 bedf6f33 -> c1108cd9 bfeecac8 412e4bf4 bdbba447 4566c98e 00000000", + "c0e20860 41d66d48 42358806 bf415802 3d7d1062 3ec3d5bb 3f59e875 3df2bdd9 3ca3d70a 3e2bca92 420a97bc 3ee500c4 bed0e5bc 3e54526d 3f610c60 bf9afed8 408a9a92 c0acd086 3f3aca30 4097af1a 3f8d742d c0397f85 3f5a769e 00000004 bfb131f4 3dfbdd17 -> 3e25d9eb 3e05902d bd3e0b06 be131fc8 3f55ee0c 00000000", + "c036df60 41ba9804 c189ad98 bed53e71 3f53fe78 bff90baf 400da30d 3ded1a47 3f000000 4044c4a1 42305376 3fa47144 be6d119d bfbe386c be8ab6e0 400023c4 4080af0a c0431e86 3ff52e40 c0a82c36 403b8c08 3efbe643 3f1ac0e6 00000005 bfb4d15a 3ee6ef4e -> c0c14e9f c158e15e c057941b bf16f247 4506a35b 00000000", + "40d73338 c1948fb2 419369b4 be1e0b2b 3fb499cd 3fa2ed30 3ff626d2 3f53e472 3ca3d70a bd879871 4029cc37 beae60e1 3e416f70 bf1e10cc c0a026a4 3f9ab2b8 c016205e 40369be4 40ebd0c2 c09b810e bf0ade3b bfe59531 bfd77a77 00000004 3f085fd0 bf49cc16 -> 3d827e46 bdd58719 bdd708dc bedce6f6 3e1e779a 00000000", + "c1cffe0a 410ae74c 40bb1c30 3da5c729 3f095f9d 3f749369 3fc68abb 3f679bc3 3f000000 bc135d81 407062f6 bd862c17 3f1ae843 3f101bce 40cf859c 4095deb4 c0eaea23 4022edbc c0b2092c c027279e bee5342b 3f5daeda c030461a 00000004 c00d642e 3f273a46 -> bdf2fa3d bda120a7 3dbb3e42 3ed1fb6b 3e3948ea 00000000", + "c114e4b0 40bd4bf8 40270ed0 bca0e0de bf080b06 3e69e7c3 3f9bea63 3fa3aae0 3f000000 bcab1ec4 413aa88a be81ac5b bf35000b 3fe9296f 40b7f148 3f05c1f0 3f378730 c01cd4d6 c0237a1e c0b8bdb8 3f8f0901 beb270d0 3f92e77c 00000002 3f0b535b 3f09885a -> 3a34b770 bd44e4da bca58112 3e89fd3a 3c734d55 00000000", + "c18578da 421d54f2 c232105f 3f1c7027 4028cc85 3f93774e 403c4e5c 3e7261a2 00000000 bf84fcec 3e251741 3e80276a bfd56fcd 3e42b20a c03d5930 c0a335ef 3d7ffe00 bfc4d3a4 40da09e6 c0363c28 c0056b4c bf4576ba 40116bc7 00000008 40361db0 3f261a4f -> c18e2d54 43b4d0c5 433f820f 3db71df8 4d96086b 00000000", + "c2145cd9 422f5978 c19fa3ef 3fd40fe6 3f4cc13a 3de56f89 3fef0634 3e788850 3dcccccd bcd34211 3e190175 3feb2b6f 3e5be914 3f8bd5d3 bf9bdc24 c09b4500 bf2dc2c8 37d00000 400286f8 40d71126 bfd0942d 3f157837 bdf7a952 00000004 3f210456 3e6bc520 -> 3f36cd27 3debfc18 3eab3724 3dc2e56e 3fa37cb6 00000000", + "c229d888 41ab1364 4218ef08 bd1cff46 c00ad13e 3ef2abfc 400e65f4 3e634cf8 3ca3d70a 40ccece9 3e0002dc c002accf bd130fb4 3d8df545 40b63ec2 bffecfac 405a5d14 4034e280 bf89bb64 3fb34860 bfdd75d4 bff1ad79 3f952cfd 00000004 3f606361 be84aafd -> c2a251d1 40867d30 c1727c5f beb9a6b4 4831c1a1 00000000", + "c2321c2e c2178788 40e5ca60 beb629e7 bd933622 bf375565 3f55d6b8 3fcf873d 3ca3d70a 3f09647d 3f86cea2 3e9cb2a9 3dae5ec0 3ec779ad 40513520 c08fa698 c037da8a c0aea9e9 c0c03f43 3f775e30 c035b731 3fc15f99 bfae1aab 00000008 3f94ab86 bf400553 -> 3f047524 40238a3c 405dda22 bf1c8576 429a54c9 00000000", + "c1bf3bb0 c1ca170e bfe1b300 bfb15c0e 3ff72717 3fbd857c 4034ad4d 3f931728 3ca3d70a 40566381 3ce9a815 3fcfc014 3fcd44aa 3fd1518b 40f25ff0 40982eb8 4082005e c0a11ce9 c05a0a06 3fc7eb70 bf812599 bf7a1cc1 403ed847 00000004 c0129bf0 3e7ba45c -> c217a220 c22198c0 c23a9954 397b8670 4a8ece48 00000000", + "41a8aadc 41c666d8 c18df3a4 3f5dd420 bfc1bc12 3e9ea267 40055cac 3f1ff6aa 3f000000 3b8ec885 3d9d92f5 400ae5db 3e8fb09f 3d215081 be131ce0 c08dff9a be8d2ff0 c08d1412 c0067926 400c3ef0 bf3dba2f 3e51e234 3f4eb834 00000003 c0059863 bee46123 -> 3dbd3f74 3cd1e30b bad97876 bc24b50c 3f98d264 00000000", + "c14cd2fc c0200460 c232429d bd0e42e3 3ebc185c bdb846b5 3ec3430c 3d9e7d28 3ca3d70a 3cb371e2 41174ea6 3f1d8d61 bf024021 3e051c6e c08c0e34 40f4848e c047ba74 40587c6c 3fbb76f0 bf5fa8c0 c0388025 be52221c c01a077c 00000002 4031a30b 3f529e8d -> bcd3cd14 bc6fdfa6 bb09c7e0 be907924 3d831e76 00000000", + "4079a960 423cd28c 3ec36e80 bfa2c1e5 bf772a54 bfd8f62c 402f7f8f 3f53c742 3f000000 3bccc239 3c9f0b0d 3fe140e4 3fc006a3 3fd7fc8c 3f0d40c0 4001fc38 3f593870 bfa00a6c c09e2e02 40cdcfb0 40107533 401b9e56 bf191062 00000002 c0363889 bf86f36c -> 3cd99238 bcc32a98 3d173050 3e9d0fe2 425c1e1e 00000000", + "c1551f00 4082b550 c171221c 3f00e9f6 3ec68d36 400931c3 401b2afa 3ff1674b 3dcccccd bb335276 3c6b130f bedb5ac6 3e80c784 bef7e4fc 400e0d64 409cdb4c c00c4c6a c0a266a8 c0ef4aa3 3f4e2a30 403003a7 3ff44d2b bfe9eff1 00000006 bf4063ce 3e08f1f2 -> bdf2ca97 3d93f44a be0759c4 bd6800e1 3fe76da7 00000000", + "4240fefc c21041b3 c23abc15 bf85ab3e 3edffd61 401a331f 40479663 3f69af06 3f000000 bd429213 3da733ab bf9d4b04 bdb71732 3f8ceef5 c0a8b9dd 40cb2b9a 3f161110 c0579f54 40ba0cb6 c0392384 bfbaa1c8 c00f3486 400809a5 00000006 c00b76fc 3f6a5cd4 -> 4147188f bf41e4c2 c1a1709e bd96b257 47145ce3 00000000", + "41bde074 c1c7c246 4131dbbc bfc1098a beef27bb 3eaba05c 40220c6c 3feb0410 3f000000 bff794bd 3cade4c1 3f66f99e c00bbfb8 3e15ae1d c05cde54 bdebf9c0 40cdb8ca c00cc8de 3f0d1840 c02f215c bfe57800 3f842cfc 3fb0471f 00000006 40350f98 3f3ddfea -> 451b9ec3 c5bb28dd 437f4bfe becb536c 4f741e02 00000000", + "41457c78 c1bcd52e 41e4f868 bd418989 3e9a0ab9 be2f31cc 3eb74959 3db172a3 3dcccccd c0079d97 3eb40f82 3f078adc bf2a492d 401578d5 3fee3eb0 bfe182d8 c0b72bae 40ccb500 c0046aba bff226b0 bf1e9bc4 3e2c0b75 40196c71 00000006 bff7ecfc bf97cba9 -> 4319e8a7 4367cfbc c42fb02a 3e4b15f0 4a8a9b98 00000000", + "c1757f00 41a17a04 c23fdfdc c01c6053 3f8fe40c bf0c315d 40353347 3e312466 3f000000 3ca66f17 3e430e2a 3f4d712d bf0084bf 3f068bac 40423344 c0bb3c56 403eed60 c0eb1b2f c070ea68 c030823c bf5c4f47 bf89243f 401d107b 00000008 bfffb15e 3f8629a8 -> 4272e22a c2107abc 41ca1786 3e19fcb8 469184ac 00000000", + "c234e03f c1632bf0 c07400a0 bef2b93a be883733 3eb041ac 3fb92b5d 3fcd9d6c 3f000000 402a23c0 3cc7a3ef 3fd0ea41 bfc92990 3f9bf2f7 3fa93500 40ff9ad8 bf29a310 408fe600 bf502600 3f3667d0 3f7fab39 3fa939c3 bfcc2df5 00000004 3f085b28 3f1e65ce -> c2db71e2 42db5c4a c2ac5759 bedf4cce 4943e203 00000000", + "41ec6ab8 c1b12374 c1f5283c be2f4023 be15d2d9 3e1b3af0 3e908790 3f0adc83 3ca3d70a bda3da6b 3cb6587c 3e411c8d 3f51caea 3f24005d 3fb01ed0 40114594 beed7520 c0c22d94 c09b9ffe c0eb9f73 bf860b6f 3f9c2431 4008d9a6 00000002 3f6dfdaa 3f1c8e29 -> 3ebff82e 3fc47529 3fa63dd4 3d6b6cc9 44251ef5 00000000", + "41fd1eac bf2abb80 c2424913 c01388a0 bf88c21b 3fb3f38e 4039d7bd 3e81b4f5 00000000 c1167494 3f16186b 3ece1776 3f2c5953 bf89601c c085b0f2 c00cd874 c0d21cba 3fadbd78 bfed8718 40a2ef02 4037c102 40181743 c028c1e8 00000006 c0366ceb 3f081ca4 -> 422c8510 4375acba c36f7a90 3eb22029 4bcf8834 00000000", + "41f33204 41f5cfb0 41ce0340 3faca5c5 bfaf6f83 bf9f0246 4036bf67 3f90ec73 3f000000 4093c969 40b21a0b 40075a0b bd9b554d 3facd20f bfae19e4 c0b068ac c0c286c5 4070d164 c0edae18 bfbff6f4 3e7807f2 3e9d2ec8 c0187e56 00000003 3f47d1b8 bf5e3e11 -> c0351956 4099c66f 40b86bbc 3e33179a 450627fd 00000000", + "41a12aac 41ceb578 41f12678 bfc8610b 40169af5 be086fa0 403526be 3d4cdee0 3ca3d70a 3ff210a4 41182aae be4ab6d7 3f8251f3 40339edc 402140d0 3fb6f9b8 40dc6d6c c021562c c0cb6fea c0f45ac8 3e246849 3f9c7e45 3fb662c7 00000001 bfc02c44 3f11db69 -> 4080745e bf128dda 3e745bb8 3f447271 4346ff65 00000000", + "4140a864 c2268b16 41f98564 be0445f6 bf3937d0 3f3492e8 3f875a5a 3ec68526 3dcccccd 3be9cd13 3f9ae9d9 bf5296fb 3eace979 3fd85113 c0d6314b 40f1b9f2 3f8d41f0 be061920 bf70bb90 be847ee0 3f8c58d0 c020e01c c00a473d 00000004 3f8d574f bf855472 -> 3b95e588 3d0fc0b3 bd39b1d4 be01353c 3c008e74 00000000", + "41de9a84 c2175ce9 c1aac4bc bf82d997 be2aaa3a 3f278b33 3fa02ca5 3fa7658a 3ca3d70a 3b11205e 408d65d6 3f07e431 3eba2c9c 3f5e6d6b 3f07f5c0 40b3515c c02e6eb6 c0b903b2 40ca5ad6 bffc9110 402e3d95 4026b800 bfdd2c14 00000002 c0390587 3e0f6d5c -> b92cb670 3a7987b6 b9fc0278 be97d6e9 3973fc8b 00000000", + "419728cc c1f1daa3 c243c99b 40346323 3e84f2ff 3b18f53f 403526b6 3cc57fe0 00000000 3e9fcadf 4164cd34 bf8d0806 bd88d2cb bf922384 c0577f2c c090470e c0225090 3f2316b0 406c9480 40d688fa bf0799e4 3f9b94e9 3fb58630 00000003 3f052057 3ebe1d92 -> bef78a52 3e8446b6 3f05b866 beea114d 40423349 00000000", + "c1b20019 c1d738b1 41ed480c 3fd77f47 bf194879 3ed981ea 3fec1ab2 3da16f9d 3dcccccd c08f60b4 40fe3a9b bfba7d15 4015d297 bda1f07c 40e3dd92 c0c597c2 bf222ad0 3efb5620 3f4a3a90 40b67674 bfc93990 c026d353 3f6369a6 00000005 c01d849c be2b3791 -> 40cbb598 401302dd c167af02 bd482e71 45b31524 00000000", + "423e51c2 c1b68684 c239aeae c00a8cf1 3f62ffe9 3fc28210 403315f2 3edbd3d8 3ca3d70a c082cf6d 3f98e9fd 3ec3440b bf3ba579 3ea2e028 3d91a200 c0a52088 4038dbe4 40e55b72 402ba504 c0d7ca18 3e8f1a6e bf83b55b 40336a9a 00000004 bf839596 3e86ce53 -> c0e1f751 417b2fd3 bef5f23a 3e971dec 44a010ae 00000000", + "41cd58b8 417c9890 41f81400 bec03ea0 3f38cf7d c035acc9 403cfded 3f491405 00000000 bd0bce23 3e6fe1ff bf568c16 3d5bf929 be84646e 40a35ab8 40983a0a 3fd08030 c0c9e3a4 c0447f3e 3f0fab40 3f0b4d95 3e010a27 c00337db 00000004 c03a0cbc bedb41b8 -> beb579e5 3dc51014 3dccd7f7 3e13c7be 42116d32 00000000", + "c217afc7 c23e3f2a c065d0b0 3e242a0b bf324568 bf1852cb 3f918e43 3ed43fca 3f000000 c0035bdb 3ec3c74c 3f1a6d5d 3fb2a504 bf6134bc c000b23e 3fe2a220 402dfdac bfe4ed30 c0484a12 c0d192e6 bfe08796 3f3778a0 c01286e8 00000003 3fc86c30 bf4eb72a -> 40aaeab0 416dc1d2 bfcc8894 3f1603c8 462cd0ce 00000000", + "41d6c248 c0872e58 c23a6dfe 3fca60ee bf3c6dd6 bfb8449a 4015f3c2 3f50d991 3dcccccd 3e04d448 3d6c0c47 bea8996e 3e9945bc 3ed228d3 3f2fd7c0 40f04880 40acfbc2 bfc36400 403cab7c 407cb814 4024e660 bfbe5443 3fa28977 00000003 c01335fd beff35e3 -> 403e5e0c c0631cef c081432d bde0ae49 453aa0e7 00000000", + "c233efef c046c240 c243c531 3ed05c8d 3f95a261 40109848 4024dfe4 3f6ea295 00000000 bb5cc36c 3f7e45c8 bf59ca9b 40114463 3f00543b c0bc2130 c0c2f814 40a88cde c0c89c5a c05d96fe 40a78796 3fa7a152 3de9112c 3ffd3df3 00000001 c0368227 bf28ef89 -> 3b3e1e5e b89d93b5 bb7fe645 3de6a534 3960ee2a 00000000", + "c221ea7a 41bc0248 c2285699 bef83fad bf219847 bef8e5a7 3f852e40 3e5ccbe8 3f000000 3cb92fc3 3e21e2fc 3e0146ce 3df22b98 3fa5be4b bfef52bc 3f71e610 c0a08598 4080d320 3ef105a0 3f6ff950 bfab8c85 3e52c34e 401a1dbe 00000007 c00aa2d3 3e9b7c36 -> bdf2a570 bdc92d6d be96adb2 3ec8a143 41ea18eb 00000000", + "c077cf80 c1ef37d6 410e0090 bf68c42d becf8b46 bd6e6419 3f7f4923 3ec299d8 00000000 3f10a861 42b83b56 bfd43cb9 3f29b551 3f544695 c048b734 bf47b1c8 c0879f11 407cd748 c022a7a4 c07c1c14 3f2092a3 c0163523 3f643ea9 00000001 bffabb1a 3f667eda -> bf43df1f bfb8e2a6 bebccba5 be69518e 40f37198 00000000", + "c243e1c9 c1e7a97f 42275eba 3fac0669 bd52275e be247c31 3fb0fc56 3fb497e6 3ca3d70a 40edc9f2 4150e55f 3ea87f86 c012bfba bfe8ccc0 c0b3442c 40598668 40d615f2 c0c57be8 409879fa 400297e4 c020b287 3f9c09eb bf7257bd 00000008 bf80521e 3f996ddb -> c157ff5a c2ad34c6 42c8d0f4 c0931926 47497752 00000000", + "421bd65a c2429dd3 c1897688 c0132ae6 bd958d3f be57c37e 40164d7b 3d9c7532 3f000000 3ef6737a 408fd924 3f75b47d bf1627b2 3f9d065e c0cbedd0 4028f6c8 3f4b19a0 c0ec712b c06bfc1a 408906ac 3f456cd3 c03b4f59 3f8f0489 00000003 c03bff73 be899542 -> 3cd39338 3f32dac0 3e5c8a08 3e0b1c6b 42e0317f 00000000", + "c225bc62 c0ba7df0 4183dc18 3fa90bed bdcc56ea 3f8638eb 3fe06ac7 3f23bd15 3dcccccd 3f1b847a 3e35e7db bf77e96c 3e11c4e1 bf83c422 c0bcb07d c096880e 40f6683e 3faa1570 408a43bc bf436928 c015f798 3f3eccc4 bf13bec1 00000003 3ffba4c2 3f734a07 -> c1ad8b84 4085d535 c1c915f0 be7fd650 4638b08d 00000000", + "c23abe2f c1e9b97c 41b39f68 3f4d3247 3eb56b83 bee35c8a 3f80d6d9 3f9a37c4 3ca3d70a 3e817bc7 41184c4c 3e1cb070 bf93c1d5 3d1d2bc5 c09ee33e 3fb69160 4017b64c 40a288be 40ca5514 402fea08 c0030753 4010c7ac becdbaa8 00000004 402dca65 be652192 -> 3e1dc914 3e8fd065 3e8f316c bdae3dc8 41e6ebf1 00000000", + "c21250e7 42470272 c22cdc30 be95249c bfb7d0ac 3e6052f0 3fd0ff8e 3fc18d45 3dcccccd bff94f5f 40617026 3fcedaa0 bf23d16f 3fa99e28 40a51d76 40b7870e 40923b40 c0305c24 c0f4a133 4005a944 403b46e7 3e899939 c03d898b 00000002 3fff5617 3f73f0a7 -> c018dab7 40ec622f 408296dc 3dbbc03b 447cdfc8 00000000", + "413426a8 4239a924 c1cb2840 bfeab6ad bcc2d4cc bf9f50da 400dd89a 3f406a40 00000000 babdc858 3c6a3005 bf948733 400e75ad bf7ebd08 3f43d140 40867dae 402c29e0 c0a9e77c c0c0f0ee 40f153ea c001cacb 401c5b42 bf00b7de 00000006 3f3a49fc 3ebff777 -> bd078e8b 3d734f29 bccec968 3e9798cf 3e9ab56d 00000000", + "420ef392 c11b236c 417460a8 4024ef63 be43d82d bfafe9b0 403cbaa6 3f8ca75b 3ca3d70a bd28c79e 3d8306c8 3f28ae75 bfd1e4a7 400a182e 3fc644b0 be938ad0 bfd9acbc 406f2d58 409c2792 beadcbd0 3ef20ce5 be07ff64 c018991d 00000001 3f73f96a 3f4c261c -> 3f2f2721 bfe058c1 4020b985 beb89921 4187c214 00000000", + "41160078 4223edd6 c083a360 3fcc892b 3ff59c7d bfb48051 403788fb 3fc5cd09 00000000 3a9a2334 40bcd416 bf841040 3f047dfb 3fa06793 406a68c8 c0f59b92 40346208 40a7240e 40e3eee2 c0fae6b7 bf2e5027 bfcb1260 40337bb9 00000003 bfcc294d bf53bc8c -> 3aa60884 3be9948a baa3bf06 3f4bc15c 3990593c 00000000", + "c1155f94 41eff288 4146e130 bf338a5d be60f18f beaafd03 3f578f74 3fde9ed7 3ca3d70a c0951dbb 3f13b8e8 3f3fa5b7 bebfd31c c034be56 bec2eb40 3eae0a00 40dd3a16 c06ba51e c0fe81f1 40a602a8 be299fa9 c0251069 3e6b3f32 00000008 3f1d8c65 bf7b8563 -> c050c90c 40f5d9e5 4037235e beae8d28 45b1a952 00000000", + "4013a220 c23c0512 415a482c 3fdd8cf2 beda3546 3f1ad94f 3ff38a37 3f81ae32 3ca3d70a 3ae87cca 3ec3ac9f 3d862d4f bff5d114 bdda11fc bff4bbe4 40cffed6 c0a4b628 40b42d14 401b45e4 3fe4d7d8 3f5b7fc5 3fe88b7d bdce8319 00000004 40161cb2 3f695a5c -> bbb02bf8 bdd6cfaf 3c98e563 3f5ed210 3d6f8747 00000000", + "40fa00f8 41f9bbb8 421b2aba 3e82de96 bdfc0034 400a9ca1 400bcbb9 3fc4e1fa 00000000 c03abd53 40aa50cd bf834c77 3f4e98b8 3f4fba72 c0547508 c0728404 3f0dcf50 40fb1128 40e66790 3f233c90 40108013 3e8c8d54 c01e846a 00000007 c00c1e68 3f8f3871 -> 42c7b89c c47dd036 44c2c3af be615cc8 4b2e29ab 00000000", + "c211c674 423d90a8 40f67168 c021c6f3 3f45dc59 bedcfaab 402b6927 3f9fa334 00000000 3d646bab 3d4e49c7 3f8c8158 bfc31555 c002f1a8 40d127fa 3f144970 403b0ba0 bea7bdd0 40e5b0d4 40e65b92 c028d326 3ff3faaf bea794d2 00000005 bfe780f5 3e2447af -> 3f6b5ff2 bfa6f088 bfe12b41 3f4d2091 42a1964c 00000000", + "4150283c c1dea5fc 410a93b4 3f4465e5 3f879ff9 bf92c866 3fdeabc3 3f83e698 00000000 3b3079e6 3e3a673f 3eb9b071 bd824f8b 3f0d06b0 40a0ab0a c0cc914e c0ce42c6 bf9f4e40 bf3d0fa8 40aa3a0a bff1b9b7 401e4adb 3ef08ce3 00000003 bf9b2cfa 3e11df04 -> bcc3ea83 bb952143 bd002c7c be12d7c7 3ba9440b 00000000", + "4220b2a0 c0b83d60 c22e8c8a be2a39cb bf930b1a 3f7f5613 3fc8bef7 3ff2b316 3ca3d70a 3f85bc03 40a9c97a be7be55f bd397fcc bfd43697 c057e4f4 c0eb3984 c09ffe5a bf9f6a18 4012734c 3f9ae9a8 bfc5e2a9 bf82ddd0 c01f62ae 00000004 3f9f8aa9 3eef32b5 -> 3e4e7e65 4024dd0e 3e985b6c 3eaee0e0 42dfd94f 00000000", + "c1db91cb c222fcc1 41c0f0b8 3ea44155 bca849dc be78d9f4 3ece5488 3f1603e9 00000000 3ead15f9 3d6103ba bfc09b86 bf89603b 3f168c38 4095edda 40d04e02 c0d354dc c091395d bc07fa00 bf81a7f8 c0180a70 3fa356a1 bfca8fcc 00000002 c01e6b00 3ee628e7 -> c1955ce7 c158e7ea 40e18e48 3f3af70b 45494fc2 00000000", + "4226de18 4188d520 c0c41c40 be9196a7 bf49b202 3e43b6a4 3f5bf0df 3fc24762 00000000 411bd50d 40b07a5a 3f31463e 3fdda684 400ef37a c0e10f3a c062c30a c068ee9c c0ca4204 40a6661c c0e66900 3eb5683f bfdbc4a1 3fc6566a 00000008 3f9ef6be be9949b1 -> 40ddcdf4 c1535b41 4137d7f7 bea4ebbb 4542b7f7 00000000", + "c2470b25 c11ce8c8 414d66b8 beba1a2d bfbe79ed bfe0e24e 401debd0 3faed2c6 3dcccccd 40a6b04e 3ff2836d 3f119caa bdc94849 beb7f3b5 c09883d8 40d8bd06 40d4414e 40d29a40 c0cf0d96 bfe4198c 400a379d 3fb1390d 401c13a9 00000005 bfa07d41 bf8b120c -> 421303e3 c20ebab4 428468ee 3f8f10e2 46e18511 00000000", + "c1cbab11 4234693c c05b7910 3f590f0c bff18a92 bf4da1be 401ddc00 3efd3e6a 3f000000 bcf01b0d 3fab9a58 bd881f5d 3e503383 bf1dca94 bfffd600 c0b1b336 be617340 40736cc4 40869f2c 40d495fc bfefbb29 3f13f286 3f9b8964 00000007 bf972bd0 3f3b2a88 -> bd38880c bdec4062 bddbf7ae bcb419a2 3ea361fa 00000000", + "4223bfc4 42223f9e 41f378a4 bf01c3b7 c0194e74 bebce385 401e785a 3fa47e75 00000000 bd6270b3 3f2132b6 3df2eabb 3ec4304d bf416570 40e3ef28 c0aa3b32 c0b95978 c085b6bb 40c289d6 c0d72f98 3ed9e67f be804f01 beeb1008 00000008 3f9d40ba bf6fce9b -> 3f718bc1 be013637 bed0895b 3fcff256 40c3418f 00000000", + "c19de57f 41b9cec0 42319390 be8cdabd c039af92 3f2a4e79 40496074 3fc934b7 3dcccccd 3fed9767 40738aed bf5f8243 bf4191a8 c010e358 40616e68 c0fca245 c0db9cbc bfe97cdc c0badc6a c0474aa6 be66c8ec 3fea8746 4013c5d9 00000008 bf7d01aa bf128958 -> c022ce99 3e52e8be 3eafec17 bf5e1c67 43d754fa 00000000", + "c1a17c07 c20a154e 41860d70 bfb796fd 3eaf0589 3fcba996 400ad530 3f47facc 00000000 be798fcc 3c834210 3f5dc42e 3ee415fa bf1e992b 40bd004a 40dab4c4 c0d83e8c c0cff820 40fc3614 c0d4d234 401676c5 3de4934c 40035909 00000007 3fae52df 3e9bfb8f -> 3f82ad5c 3f75cf97 bef8c87c beb54db8 46472c02 00000000", + "4243337e 41ca1f5c c247d7d7 4019614a be2bfea3 3fac8f01 40398d35 3fb8d67d 3dcccccd ba877902 3de9aeba bf4848fe be35c7fc 401e6ef8 40bcf81c bfd9b3c8 c0d7da63 3fc0ee10 beafa810 406e2f5c 3fc83362 3f705ede 3fac0a0e 00000008 c0275ec0 bf4e8df1 -> 3e65dbdb 3e50ec54 bffee14c bf333500 44592514 00000000", + "40b8ac60 c1450a60 40007f50 bea0f4e0 3fc50ea0 bf14e0a8 3fd952dc 3f8f54a8 3ca3d70a 3eb3cc4f 42be0a70 3fc3a9c3 3f6d056c bfb66703 bec704b0 40abb18e c0846528 c0e80ff9 bd0ef680 c09c98d5 bfc60597 bfd2371f 401e0d14 00000007 400b77e6 3f40d39f -> 4139e5d8 41be69da 41df3f23 3f26b6bf 469bba13 00000000", + "c2198e4a 4185c2e0 bf393040 3f35d92c bfc642cc 3dd1b88d 3fdd102e 3f7f5ecc 3ca3d70a 3c647684 3c881d65 3f6169aa bf264d9e bf0016b6 c09a7ae7 40e9ff5e c0e729f3 40ea7f8a c0a79a7c c0d399b6 bfe9e95a bfb3424c c0282ba6 00000006 400730b2 3f935a39 -> c172a35d 4169b16e 41177399 3e8b1b42 4829ebc0 00000000", + "c0859470 c129a44c 41ebf95c bf08df57 401a8974 bf9f81c3 405adc83 3fa67ec7 3f000000 c00be392 3fa74bb8 3fb401f8 bf4bf01e bee8a384 3fd0a718 c07245be 4067dcf4 40927400 40a509b4 3f6cab20 400310b2 c01cecb8 c02e614b 00000003 c01cbf82 3e3b46d4 -> 40620bb7 400a045e 3fb059a8 bd54b809 445b5a16 00000000", + "413f49ec c23b9605 41f54df4 befc8a34 3ec2d2cc 4011456f 401d6efd 3e594c9c 3f000000 3ddacbee 41bfa9a4 bf33ea94 3fedd5ea 3f21ee53 c0bb0c62 c0166284 40c33c0c 4074428c c054a9a8 c0bb5a40 bfff8873 bea66a0a 3ff05bb2 00000007 3fedca4f bf64cf9d -> 3fca1c6a 3f3e73be bfa8e472 3f7e6a7e 444a4df2 00000000", + "c1225df8 c1b04148 41749fb0 bdb110a0 bfa70fc1 bf408adc 3fd13ab4 3fa0f7c3 3dcccccd bd722fb4 42374e33 bfb6316b 3ef3adfc bfd3794e c0ca021d 404db38c 40e5e046 c0a5fc49 c0f90548 3fd2bcb0 3f869193 3fbb2602 3f9bf36f 00000008 3fa9b9d0 bf0017c8 -> 3bd3e9ec 3d3d2ecf 3c4873ea bf2c6f42 3f2648e0 00000000", + "c2399250 421ef7a8 41f48b60 bf079a2c 3f6ce8bd bf12e744 4000ff2e 3fcdfefd 3f000000 bfa3001c 40e8c470 3eb544f2 bfa0aac9 3f5276a7 407e2ac8 4000ad14 c0da1d75 3d307200 c0d996a6 c0ddefaf bf12d2b9 3fa628ad be9e40ee 00000002 bf106dfa 3f8f71c8 -> 3f737900 4040f1dc 405ddf6e 3ee19a13 42f4d621 00000000", + "42006530 41e49e2c 415450a8 3ff57128 3eda626e bfd57cd4 4058689e 3fcdf078 3f000000 3ad735f3 3fd92494 3dae8e24 bfa4ec8a be8076a2 40726f2c bfbd0f5c c0a7b760 c0a09efe 3f837ab8 402882ac be9fd356 4020e40e 4028fb2d 00000008 c0114192 beada74e -> bb87ddf1 bb3555e9 38c33f68 3f30365b 3c873869 00000000", + "41252d10 c0539d90 4160ec50 3fee0079 3f83859e bf34f436 400fe9a6 3ef8c519 3ca3d70a bf4acbec 3ca8f4d5 3f6d5e06 c005b776 3f3913ca 40b316e8 c02ac87a c0b7bf2c 406d11f0 c03c56fc c0ecbe74 4032da72 bf95dd6a 40255872 00000003 c02a26cb beda9840 -> 41ed0ec8 c279a35a 419a9502 3f490653 49838348 00000000", + "c127fdb8 c1f062ea c115d160 3eca2e1b bbadc78a 3d497ed2 3ed4598e 3f56b87f 3ca3d70a 3fcd8492 3f41a800 3e3fe71d 3f3d58d2 bf0d0dad 400300f0 40fc2b52 40e9f630 c02e79b0 c084bbec 40682fc0 3fc2349f 3e8e36a5 403b8e87 00000002 3f39c783 be26b1ca -> 3fb4d9ca 3f4f54c3 c0b0222c bf597d6f 435b765a 00000000", + "c1ded520 4225ed86 424527e2 3c6441be bf75e8db 4013d319 402bc462 3fe951ce 3dcccccd bd9328ec 419ce808 3f97d38d c01cf12f bd8c6440 3fcf9318 4060bc34 c08c758d c06b49c8 c0e2f632 c0aacb7c 3ff87de3 3fbd789e 3fd2e1cd 00000005 c0241186 beaffce8 -> bd9dc4bc bd1cf4d0 bc380b00 3eb12af7 4150f647 00000000", + "421bf0f0 4140c508 423d72a6 3de02520 bcdc2b8d be883d7e 3e93f497 3fb15ae1 00000000 3bad140d 3e835589 3e89c9d2 be6783e5 3ed85e8b 3dd3ce00 3f17dec0 c08dab00 c0ab73ae bfa1a3fc be5235a0 3f9ed7fc bf17726b bf5f0ec0 00000005 40214a47 bf460df7 -> 3e43ce14 bd8f2753 be2994a8 be930cad 40be929e 00000000", + "c23e11dd c1311ff0 42441c0a be99289f 3f94d118 3f11ccc2 3fd73523 3f348373 3f000000 bbc6b346 41b274c0 be670569 bea2bb70 c00188a1 c077e324 c0d61966 c07d9d1c c0a8ea32 c00e827c 3f657650 3fb2dd89 c0031edb bfd409bc 00000005 3fde3d1e be1a7d97 -> baee079f bc55996e 3aae5136 beb0e367 3b11ccdd 00000000", + "41b31508 c21522e2 4188b5d8 bfaac1d0 bff82d23 bf5090b9 4020ccdb 3f8cbc13 3ca3d70a bdc62875 3fb6b81e 3ffebd46 3e511819 bff5112f 40459044 40b17470 40a65aa8 c0cf3bf1 40c645ce 401baa38 3f968ce9 becc1378 bfe84788 00000006 3f253762 bec9f094 -> 3d00910f 3e0e3707 bdc032bd be14a513 3e5d3c31 00000000", + "c19fc1c6 4238f19c 4201281a 3f2e2fdf bfb3fa7d bdcb13ef 3fdaf040 3fb9ec0d 3dcccccd 4023ba54 3fbdaf43 3ee023ce 3f06c306 3f496837 3d45f000 402b7fc8 c074792e c00af9c0 409e65f0 c051f52e bf85f9c3 40174246 bf75fa9c 00000008 bfe9173b 3ef8bbf0 -> c0a5c6ad c1480551 40959cd6 3f09dd63 46ce98ac 00000000", + "4241b6e6 418a11a0 4192bb70 bf02e578 bea73eca bd30286e 3f1bb795 3f977536 00000000 baacc669 3c6a2262 3f56bfce bfaac53e bf302076 40d48e36 3fd97ec8 c0bea9d8 c0c94c92 3e3d3780 c07b113c c004a1c3 3fdff019 bf8a0cc7 00000004 bf4362c4 3e023402 -> bd860aa2 3dd94cad 3d59a42d bf4602c2 3e5d60de 00000000", + "41d738c4 c1f6b6a4 41b820f8 3eb0f29a bf8a2f9a bff45ca0 400e4364 3e05e27f 3ca3d70a bd607f71 410a63c0 bf631210 bf71f0c7 bf4e0491 c08e0700 3e4d52c0 40ddefea c02c1da4 c05bfdfc 40948772 3fca372a c00f53f4 bf84e38c 00000003 3feb33c5 beedbe16 -> bdfa56f0 bdeae2d8 3e833b11 3f25bde1 3edd7a21 00000000", + "bf417d80 c0a917b8 c14e6520 bfab4cdc bf20cced bf2d42a1 3fd01dfe 3d5d9f2d 00000000 3da14cc5 411978ac bf643651 bf9996ac 4024a4bf bfa4e8f8 c0bccd30 4020427c 40f72334 409b9ffe c02461d2 3fe4e98d c01cc9a8 bf2d83d8 00000003 bf8f070b 3e331d70 -> bd4cb137 be1e1e0e bdca36f2 bd67e1ca 3e066bd8 00000000", + "411e6650 c2429db4 c10485b4 3fc0a78d bfa96ae9 3f2600f2 4008161f 3d21e78b 3f000000 3d118969 3f5b3fac bfba9353 bd4e17e3 3f884464 bfedcecc 40dcdab2 c0800905 40943f8e c02ac822 c0631b40 bf175153 bf986799 3f9e77c3 00000005 c0042875 3e5577d4 -> 3cbfb0e4 3d561b59 bd5cb122 be861679 3ebb44cc 00000000", + "41cc071c c10ca168 41120cf0 bd76260b bff05e86 3f20a3f8 4009c860 3fdc261f 3dcccccd be1cdb33 3cf3863a 3f613d9c 40180f9c bfbf2bff 40634b8c c0c42eaf bfdc0f58 40c16b14 c0ade6bd 40088ae4 3f7271d3 be6f1ffa be14932d 00000008 c031dad2 bf260033 -> c24f91de c2d6ce35 42d7791a bf3cf130 4d0b09aa 00000000", + "c1c28014 4222e312 4206754e bf6be48a bfc27d60 bfe291e0 4021d277 3f81d6a0 3ca3d70a bcac126e 40aefdec bf2b7ba8 c0124ab9 3faee6d3 4066085c 3ea88c80 c09acd74 409e8c78 406e4018 40f6ae9a c03a48b3 3e55b500 3f330d0f 00000001 40294e21 bf13baaa -> bd360946 3d0a21b9 3d43fa28 3e9575c2 3c3cd727 00000000", + "420ccbb2 422ab13a c1a59913 4027eab9 3e140650 bf69a2b2 403a126d 3fa0eb34 3dcccccd 3e80ce2d 3ca60ef8 3ed4852d 3f0b2074 3f90318c bfda9ad4 c0c76b65 40c0ba3a c0aa709c c00b43b0 40fb7e66 402b4e07 bf2e5385 bf374b00 00000006 3fa3b17c bf20cf17 -> c1905afd c1ae254c c239914d be1b285e 475719c6 00000000", + "424726d4 c22c5421 c231672d 3e3733e7 bf14e384 be406c19 3f2ae157 3e9cf2c6 3dcccccd be2aa840 3f64fdc4 3ff04c2d bf2b558d 3eafcd74 c0c3c06b c0df93fc 40e17742 c0383262 bf070bb0 40ee5f16 bf0c1aee 40193634 4021a414 00000002 403fae4e 3e57544c -> 3f327d88 3de687da 3e675446 3ece0026 4098bff1 00000000", + "c21d96a9 41f7a458 c18d61de bff10754 bfbd6de7 3fdc2057 403d1ae9 3ea139d9 3ca3d70a 40067508 3c8e5e5f bf8b9a6c bfb572ed bf1ba4bd c09383f3 c080d659 c0eb031e 4045d950 406d1f70 3e62fb40 3ff4a77e bfc8500a bf45c78a 00000007 bfe96e66 be2b5038 -> 430dded5 433754f1 42a76b85 3f09e4ff 49dc1118 00000000", + "c1db0a28 423b436e 4232fbd0 be62f212 3eedd217 be9f7e12 3f1a9b11 3def7fd9 3ca3d70a 3f7a716b 3ffc5074 3e7fdc14 3f8727cb 3f3df688 40a46b6c c06e37b8 c0a7a1d0 c01e47a4 3efa2160 c053ab92 c003558a 3e6ae3e8 bf808caf 00000008 bf3a2fe9 3efc34e1 -> bf6bfe26 3f405ace c0263fd4 3dda6cbd 420abee5 00000000", + "4230e04a 41244120 421c75e8 3e65d8cc be9cc663 3e48e111 3f140941 3fc12d9d 3dcccccd bb2545ef 400a46a3 3f0e92d9 bf8716cf bf2740f9 40cb75d8 406b69f4 c06f60e6 c0cb2efa 40b80658 c0f3e00c 3fc871cc bf918f98 3f34aed5 00000006 c01dedd0 bf4a4ab7 -> be6e997d 3c38a5c4 be29e9d3 3f001728 3f0fc813 00000000", + "c206db58 4230fe9c c147ea7c 3d9e83ec 3ea1cc56 be9fb19b 3fa0a366 3fcde53c 3f000000 bc1baf93 419bf09b 3ff1a0c1 bf846ebf 3ea56a9e 40ba6ede 405061b8 400f2994 bf190478 40a18472 c0d95ed8 c00a26e4 bfd01807 3eaa6867 00000007 bfa64b26 bddad6e5 -> 3aa210ec bbcd1db8 bca451a2 3dd3fe7d 3b5e92aa 00000000", + "c20188ee 4021e890 41d891d4 3f5b3ab9 be80c4ff c00a3080 401df5eb 3e86e353 3f000000 3baa1c80 3f8357dc c004c770 bf000bdb bf694cab be3300a0 40cc6666 402aabb4 40aab6e2 3f4bb2d0 409e89e8 bfb09b0a 403506e4 c031d9f4 00000001 bf8d5036 bf6e2d27 -> bb5e01ca 3a91196e 3c7e4345 3e15a6e3 3a4b8f3c 00000000", + "c01c24a0 c23557c3 41ee01f4 bec67a37 3c7d2033 bf60e04b 3f8ad670 3f9f37c2 3dcccccd be8cdd22 40d295ce 3f22e6b9 be9f8d69 bf86f190 3f39fe40 4046cd94 402762c0 3f6b61b0 bff4cfb8 c09834bb 3ef8d0b0 40179f12 bf877696 00000006 3f82d165 bf63e410 -> bcd5ca00 3ff2e6d7 bf0441f8 be722a68 414d28f4 00000000", + "c2017612 422c7a44 c2383cf6 bf0896b6 bf8bc3d0 3e80853d 3fa1f528 3f9bcbe3 3ca3d70a bdb0fb30 41a8ee37 bf78d1c2 3e3c2f4d bf06074c 40e1cf82 3cecee00 40b900d2 c0f7be60 c0849b62 c0fe2459 3ea298ff c013cf1a bf462bce 00000001 402d754d 3f6c3558 -> be1a2d66 3c5692fa 3e947620 bef7d182 3e44b3c1 00000000", + "422715aa c12fc940 c1c063b1 3ec1290d 3f0afcf0 bd6fd728 3f33f3b5 3da0a38c 3f000000 bb63fdc5 3fc1712f 3b9474c3 bf888fec 3f4b1057 409060e6 3fe13868 3faa9c28 c0b38f18 bfe549a0 c08df9e0 c02a4209 c01d550a 3ed9a376 00000008 c02eea9d 3e5893e6 -> 39d41ca3 3a7d29cd bb68df88 3e470eb1 3d295fe6 00000000", + "c1a5f096 416c2840 423c863c 3ef24e9f 3fb32858 bfd51180 400e9d0a 3e04ca36 3ca3d70a bcad76c6 42735fa0 400df247 3f7d2a96 3f1250ea 402f57e4 3fc71fe8 40a34470 4008200c 401b3efc c0f863f6 bfc80d9f 4020d3be c025a79f 00000005 bff6a40a 3ea8ff42 -> 3caef1c8 bd9b9a3c 3d4a2acd bf5b91a0 3dd0b656 00000000", + "c1d1c8dd 4158dca0 c2064484 bf2ee5de be676350 bf5da67f 3f901ac2 3df94c1f 00000000 bb483a17 3c951328 3f974a92 3fe6389d bf8c34de c0489638 4084c2be 4000c054 407d2074 40d0ab64 bf8bea14 3ff473f3 bf092f3f bfd26c55 00000004 3ffb30c0 bf68728b -> be71d9ad bee96ccd 3e63c06b bfb931ee 4244497a 00000000", + "417ee070 41091450 42443cd8 3f9704ab 3eababf0 3ea94b08 3fa464d6 3f331852 3ca3d70a 40365692 41338f8d 3fdb9f7f bfeb687b beb47642 3f38b190 3fc9a038 bf2fc920 c04137f4 402c1870 407deab8 3fb8d31f 3fbefd0a 3f4268c6 00000005 bffe61d0 3f430551 -> c12c8067 c1ab3cc9 428fe6f9 be0084e0 4757b032 00000000", + "4220e808 414798b4 421dc16c 3e6fc9cc c029f82a be94058d 402ce499 3f7cc5e2 3ca3d70a bf032be1 3c7624f9 bf648a30 3ea9fa8b 3ea38972 40a71f1a 3e9de060 c065f4d0 40e33ade 408b8654 c08a1d16 3fdaa11b bfba5588 3f3d86a5 00000006 bf956162 bf75b4a0 -> c24aaea2 41af7bbd 4195e2c2 3f1ed6fd 494d3542 00000000", + "41be24a0 41970304 401fbad0 3ee33723 c013de2c beeac1bb 4023a459 3fccc439 3dcccccd 3e6fa682 3e271c00 beedf1d6 3ebdbc31 3e8e1665 c0b94058 4093ad88 c0853f2b c0c5e48f c03e90ec c08a64f4 c037af3c 3f7ae526 3fb9df26 00000003 c0159625 3f900748 -> bf16d254 c056be46 bf6a28c9 3bf7f880 45a0aede 00000000", + "c01af050 41db21d8 c22bf580 3f94768a bf894411 3f3344d7 3ff606c0 3ff8885c 3dcccccd 3ed4b948 40d931b4 3f97fec0 bf6565d2 bed6f2ad bea7bb10 40db38ba c0f833dd c07d7e6e c063bcb8 c0dff399 bf412c4b bf977ddd c03de0a4 00000001 402fa481 bf8874fa -> 3eb4aec5 3ee58436 3dcc1059 3e6c08e9 4119adf2 00000000", + "c1e4c6c2 c0a34c88 41fb7804 bc1faf5a bef3e264 3fe491a7 3fef379d 3f84a204 3ca3d70a beddaa08 41d540c4 bf6e6e9d 402265d6 bd5c8b78 c0b2c23e 40b20c56 40fdfb06 c01fa06e 409ecd14 40b245ea 3f2f1c5c c0169d16 c035a8b2 00000004 40392c81 bf9616c7 -> bd01f7d0 4012a540 c1da2b06 3eb198c8 462b40b6 00000000", + "420b99ba c1c059ee c1383740 3f80fe1a be40fb79 bf2a0774 3fb676c8 3ed0d4d8 3f000000 bbba4c30 421555ba bf318b30 bf215d9c 3f3f3a2e bf3188c8 40b60ffa c0f4ccea 40970dc8 c090fdb1 c0f87562 bfd07dde c018845b bfb1d3d7 00000006 3f99b680 3ef0bb37 -> bbfc933e 3b882317 bb41aca6 be6582d9 3b1c5f28 00000000", + "c22b488c 4172a558 c2192820 3e9e5b46 bde3ddc5 becd08c9 3f04a0d8 3f3dcf44 00000000 3f187832 4155a11e 3ff0ebdb 3fc28533 3f0d263a 40646c80 c060986e c00d0cb2 bf873a70 40420ff8 c0f10580 3e04fbb2 3f945e7d c03fd90c 00000004 bff44771 beef6109 -> 3e8937ea bf6c17b0 3fbe2d2d 3ece5189 42ab09cf 00000000", + "420bae06 c194bead c1bf7341 be4163c3 beaa5f1f 3fb752e1 3fc2c95c 3ec959c7 3dcccccd bbf3d19a 3eea1865 3eaf88b4 bf34eabc bfbfee95 c0e1bf67 c09f58e0 c0098f76 bf117520 401d9e68 40a2d844 c02f7a5f c02c7c77 c005a4d6 00000006 3fb0c05e 3f62341f -> 3da72af0 3d207109 bb83130c bf14f05d 3e8950a9 00000000", + "c1b9eb26 c0f53968 c1715270 3cce1b86 bf4af9b3 c017737b 402c2472 3ff82a98 3dcccccd bc608c0d 3cdd2581 3e459e7a bf0ca21b bf57b208 c0e6f949 40acbf84 40cb7046 40bcc794 c07ba9de 40d77566 402c0c61 bffb36de 3f8ca7d9 00000007 c03ebe33 bf33b8d8 -> c125c082 4204e4c4 424d3128 3e6d1a34 49753577 00000000", + "4209c1ac c1c77876 402fce90 bf3cdacd 3edfaa1a be190e47 3fc1d006 3fa4d513 3f000000 bfd95c8a 3ce92ee2 bde6b284 3d84a928 3fbd9834 c0524566 bfda917c 4067f400 40ced79e 40b2c300 40929648 c00843ef bfa65712 3f381e62 00000004 bf014172 bf5dc0a7 -> 40700268 40249778 c1c78617 bf6cb143 48779bca 00000000", + "c102bb74 40962118 c208ca3b bed8d9ac bee9ee73 bfe9b091 3ffb70ac 3fe1f113 3ca3d70a bab24464 3f024a0e bf35c605 3f39c770 bf1092cc bf54cd28 408aa6ac 3fd982f0 c0868062 be33b1e0 c08c8bd2 3f9708a7 403d3eca bf957b9d 00000007 403952d1 bf71a3d5 -> 3f671a3a bfc0892f 3dcdacc2 bf8b0fe0 42d8bc7c 00000000", + "c1604278 bf85cfa0 4122674c 3fdcf3b1 3f93d886 3ee6edc7 4013d6be 3ebd05c8 3f000000 409af42d 4098b639 3f8fbf49 bd01a031 40000a5c c057049e 40c6c486 3f806cc0 40ecb1e4 409b2128 3dd77380 40352156 bfe79e53 bf82d2fe 00000003 bff3d111 3f83e7f9 -> c1863514 c219bc11 c0cfaafe 3f0ad677 46becea5 00000000" + ] + }, + { + "glsl": "SpanNoise cultmath_span_ridged(SpanPath, SpanStage, int, float, float)", + "tolerance": "ulp-bounded", + "cases": [ + "3fea7280 c08e1168 c22af615 bf891134 3edbd972 3f0f5500 3fa425ad 3f798864 00000000 3cdea50a 41ea595f bf4784ae bd468e15 bf388132 bffa4d00 c02988a0 40afb644 40892b4c 3f8f75e8 c08b2272 c006306e bfce3d85 c0297638 00000004 bfbabfb6 bec22876 -> 3d1fb19c bb9dc733 bd188555 3ed7d430 3d522e6b 00000000", + "c242f160 c18a36f8 41483378 bf1f4212 bf92778b 3f0b16f4 3fbb8e48 3ddd6425 3f000000 3fc7741b 3edea0d0 be5916fe 3f0db1ee 3f073e38 3f15fb30 bfa85654 40c6a9ea 40acc428 4056f278 40f9ebde bfb08250 3fc77334 bfd6cd94 00000008 c0399e95 be8910a7 -> 402d3939 c108162f c024bb57 3ec2649c 48b80388 47b20476", + "c233f27e 40b1bf30 41a825e8 3e48405f 3f3d0efc c030a387 405fdd04 3fa25487 3f000000 bb141adc 3c6811d7 bed1f1c4 3e47984b becf3eb0 c03e3816 3ed168c0 c0cca5ac 3f1dc9a0 40af4cb6 bf860984 3ef05342 3ea195f4 bfcd8373 00000008 c01c3e0b 3f8d234f -> c30deb7d 42928537 c306a1a6 410499af 4957e1d1 49b773f6", + "c1b10d86 41a0a6ac c1ddcf0c 3dfc30bb 3cee6825 3e48f7d0 3e7250a0 3e1ff8aa 3ca3d70a be00b574 3f4abc2e 3f24a953 3fdc6da6 be3e064c bf252158 c09c0ccb c04eae1c c0dc0d98 40323924 3fe8b228 403b8b83 bfc1daeb c02bdae4 00000008 4017da02 beca84f1 -> bf5ba05d bee48bdf 3edd154d 3f53b614 442578d6 3c5aec66", + "411ec370 c145d27c 4208ced2 bf88be4e be2d0d9e bf1a28b2 3fa045c6 3ce8abc1 3f000000 3c5960f1 422ef127 bf58352e bf48e90f be4e879a 40641170 c0a5cdac 40ad2e06 c086af04 c0c40af6 c0431922 c0054415 3fccd579 bff33b7e 00000005 3f70de56 3f85e0ff -> 3c664f30 bc2ffbcb bc8995ee 4093e0c1 3ca733bc 00000000", + "c1a41334 c212a9ba c1d73489 3e48fbb2 bf8bf89b bf054c8f 3f9d0d3b 3f001d97 00000000 bfb18964 421c0d09 bf7292a0 bebab488 3efb3913 c08cf84c c0a0b146 40ac8146 c0887542 be450cc0 bfc92d30 bfd6ba01 bfb600f0 bfef49d8 00000005 c0266889 3f88cd91 -> c2fc3a2c 4320325a c2fba4cf 4082beec 4831ac90 3de57502", + "423dc6e8 41445c24 4198394c bdd17054 3f18f21f 3f8e7bd4 3fa6dac4 3fe6d836 3ca3d70a be80453b 40f00224 bf6dba76 bdc61d84 be90c7e1 c0fd6411 408fbaa0 c0cab49a 3f24f2b0 bf76ab28 c01f1976 c03bd27d 40138e3b 400806fe 00000008 bf477940 3f978968 -> 3e799f63 bfab746f bd978761 413adb2a 4091a11e 00000000", + "411fe358 421199ca c1f64dfc bf648f7c 4006e3f1 bf70484e 402d70aa 3ef1bc73 3f000000 4060b792 3c51ef4e bed16128 bfa055cf 3f1abf58 40be0552 c0dafba5 40ba4e0c 40127fb4 3fef9bf0 3fa8b2b8 3faf05d7 3fdf73a9 c0298a19 00000007 c009b3e4 3e6f1e20 -> c2957009 c36e9035 42e64af0 3f8f43f2 4c0eaa00 4c3d89c8", + "42067dca 3f6da340 41efd84c 4001f727 bfbd2a4b 3fbed4a7 404c3232 3f0a2b14 3f000000 bc86ebc1 3f25d1c1 bf9f1d1e 402d1d75 be183aee bf3ab740 c0670742 40b24b16 4025587c c0de86f7 c0bb56a2 bfdcf734 bf0fc968 3e482a9a 00000002 bffc1ff4 3f0a8f95 -> bb97cdd0 3d88b2b3 bc21748c 3f82ab2e 3dd769c8 00000000", + "c1afc628 41db8b98 c23f1c7e 3f86e2b4 3f163eda bfd27f9f 400a3f3e 3f9a707e 3dcccccd bb574b28 40193250 3ea7e9b9 bdd9892b 3efeb299 3f4e3930 c0fbf211 40930e4c c01a2824 bfecb0dc 4097dd90 bf43998d bfc012b0 3de7aeff 00000003 3fb56df1 3f34857f -> 3bad6df9 3a41f180 bbe11a87 3fe7d6f4 3a8fb5fa 00000000", + "c2213bb6 c1e0e03d c1f111ad 3fabe695 be6e72d3 3ff01418 40146351 3f274817 00000000 3ec4c9f0 4012f0e2 4026ad78 3f255d47 bf0cb7d9 c0bfeeab beb06ca0 c0e3e2bb c0dca982 bc8e7000 c06deac2 3fd0d71d c026f32c 3f568448 00000005 bfd9ba46 3f315845 -> bf3d91a4 be3b910c c0a405d1 400611a8 42e2cd50 00000000", + "4053bfd0 c2023130 41a3dacc 3fb615c5 be5301d1 3f0c6d0a 3fc4ed7c 3fcb331f 00000000 bcae401a 3fca3645 3eb54a82 bf591cda 40246076 40233f8c 40d4d24e c09bf06c 40a3ee04 40950fba bfadb15c 400f2a3f bf378c46 c01a95c0 00000005 bfcba818 3e9269ce -> bc83eae4 bc81cfa6 bb840e0f 3eed1a0e 3d14abb5 00000000", + "423f979e c06b2850 4220875a 3ec7acb4 3f1ad3be 3ef165c9 3f5ef74c 3f08226d 3ca3d70a be7ec92d 429565c1 bf841a9d bf4e4d2f bd29afda 407fdb8c 40a626c2 bf03b020 bf93f368 40a68ebe 408236aa bf23ee28 be48ab48 3f19c6ea 00000001 3f5af316 3f467110 -> 3ca382d7 bd1cd072 bc3feff7 3e90bea8 401cf7f5 00000000", + "408bc148 42000906 c18c6134 bf94df64 c0125e33 bdcb9a95 402454da 3fe8f76c 00000000 3bdce0d4 42b4064f 3f17d9b1 bf2b7c2c be6669c1 40d51372 4097d964 403eac74 bf27ab40 40f1342c bfbdccc4 bea650dd c02628c6 bfdc298c 00000007 bf82df8c 3f33d9c0 -> bc3da1ec bbbcb9ea bc5a8d6b 3fd3fe31 3b941b61 00000000", + "c2458b67 bd7ce400 4107f9e4 beae2f90 3f7b7195 bf9fd3b3 40267817 3ff9f55d 3f000000 3cd8fc0a 3ce92964 bfa7835d bdb91727 3f69ab59 40e79b9a c073e1c4 40c4172e 40998622 c0665d56 beb36e70 bf443343 400c11dc bfad9f12 00000004 c02953e7 bf4ab08b -> 4079da04 3dd951c8 c01e1500 bf409ee7 461c3cbf 454c2269", + "c2120b1e c0ef4580 c194c533 bfac1582 bfa8fe84 3f908f26 400ff61b 3f0699d9 3dcccccd c01b8f0d 3e238855 bf674428 c0068408 bf2a5bcf 40d3231c 405bc520 bfdb0f84 bf111298 3da13300 40197c1c bff8c25c 4034d6e4 bf6d8190 00000001 bfe10307 3f65b2f1 -> c12e7358 c1ca4449 c09dc8b2 3f73424a 464964a1 43cd9f3f", + "c031e8d0 4130b434 c202229c bdfd5cb5 40037d57 c005c1a8 403c2d6a 3d8eca00 3dcccccd be1203b7 3c70fb97 bf8dfa3e 3eb0385a bf2054e6 3fc96690 bf45ba48 c03f3a60 c0bc8ea2 400f74d0 4006fb2c 3ff97831 400ffcc1 3fd25adf 00000001 4006fc96 bf462aad -> c048df51 3f6cbe9d bfd69394 3f73a9c2 45a36c5c 437c2bd4", + "c10503ac 4204640a c182165c 3eda129f bfe7aa4b bed27596 3ffefdd4 3f610663 3dcccccd 3b21c203 3f0d93ba 3f661ab8 bfc50e75 3fdfc371 c0aeb42c c04cc09a bffc162c c0f324bc bf5a7028 bfd0c3cc 3e93287f bfc43ddd bfa09eff 00000003 bf459bf2 be085bc9 -> 3a288521 3ab7dbc7 3ba7bcbf 3e78b5f8 3a15d0b5 00000000", + "c0a32680 41ec9d28 4247ba34 bf2cf583 3eaa0ec8 c0086aaa 40126026 3fa9fd8d 3ca3d70a bfe6f845 3c62123e bfc887f8 be9508ca bfecc666 40f0c276 bfaa07f0 c04ca5fe 40184624 3f9fa628 4050c994 3e3906a0 3f86443b bff6d0b2 00000007 bfb1644c bf8073ac -> c47aa3a8 c330d2be c49371c9 3e866ddf 4cbf6f30 48351954", + "42231636 40abfba0 41e35188 3ea51426 3f426e09 bd3f7306 3f5446f4 3e0ed7fd 3ca3d70a 3ec66c23 3ee7e223 3ed7bb1c 3ebf41b2 c0237d69 40f0f330 c0a6b382 bdc5bc40 c007d0fa 405be880 40f62f6e c03a9a13 bfa6b44e 3fdd80ed 00000004 c0122838 3ed4342e -> 3f5a2d78 3f4efd42 3f4e2afb 3f98b965 44307775 3ce2a6ac", + "c21e226d 420c26d4 403a7600 3f987e2f 3f78b8f3 bf032c5f 3fd09f45 3ef13924 3ca3d70a 3e04c347 3dcc98dd 3f76adf7 bf504475 3ea68622 40fb4a4a 40ad7af2 402a3d50 bec6ec90 4024e9c4 3ffb2560 bf3dbb71 403c32e4 4025bfe8 00000001 4010f424 3efaee93 -> 3da33db1 3e560006 bcb15928 3f772bc8 4290d45b 00000000", + "c1c33363 41b1ca50 41885658 be72c714 3e55f01a bf810f9e 3f8869ba 3ee86415 3ca3d70a bb910d39 3d055447 bf0377bb bfcaa426 be4d9ec3 bf1d0370 3fd37980 40d17abc 40883636 c069d9ce c0c4f35f 3f1ee87c c039f4ea 3fc32681 00000001 bfc7d71c bf81bba9 -> bc951803 bd9951f0 bbb7bcc4 3ddff510 3f546da3 00000000", + "c1d718e1 41f78200 bfcd5b40 3f470ce0 3f7a628f 3fecee7c 401013b5 3f658fde 3ca3d70a bdc2701c 424b20e8 3e99efd6 3ede2322 3ea20be3 402d28cc 405128f0 40c8550a c014598e 40bf7124 40feb9b6 bf77bf01 40071465 bedb6157 00000002 bf8044b6 3f786a24 -> bdb50b4e 3db9dfa8 bd27362e 3fb71f5a 3ece3e43 00000000", + "c2099819 41364e10 41b84738 bddac114 c014084f be45a3fa 4014b444 3cc633ba 00000000 be7bd38e 3fa75586 bea779b7 c00f9d56 bf2bf5be c0f16228 3f4d4340 c0cdea35 bd9f7880 bfe16824 c086c1cc 3f230d1d 403a7e7d 3fe7e947 00000006 bf6e2c2c be35532c -> 3ec63516 3e863340 bf9dde43 3ecce24c 40081933 00000000", + "41a2c848 be7feb00 42410d0a befca6d5 3f19e543 be244aff 3f9143e4 3f2e783e 3f000000 3ba5d4da 40ba37e3 bf71100a 40033d31 bf552a28 409e1cc4 407890f4 40db0428 c0b62ef2 40fea7d6 c0127bf6 be4ec094 3df24f08 c030906c 00000001 3f0a8748 be558e01 -> 3c50d233 3b0d3201 bbcdf889 3f6a78ff 3a4a945f 00000000", + "41fa8888 c1ace9da c23d13d5 be83d76f bfa412e7 3f7d0264 3fd44f65 3f7c7144 3ca3d70a 409df318 3d12aa82 be6a40c6 3f472266 3e21fd04 c0f6486e 4089d62c be374f80 409224da 404b08d0 c07110b0 401fbd77 bfd8519e be9064f3 00000002 c00acc84 bf0b9cac -> 430d1d06 c401f65c c2dc1a74 3f0e413b 4a661018 4515e520", + "41bb4034 c19cb20a c22b51e4 c027ce69 bf602661 beb76cb8 403d958d 3fdfc78c 3dcccccd bdfd5c62 3f05ff58 bf44d503 bf2d73a7 bd874210 c0afd3b4 40951636 40bc3c2c 407c1680 407c75a0 40b17308 403e7453 beabc6c7 bfb09e9a 00000007 4003a0e7 bf98d243 -> c294bf6e 429b6251 422dc110 3eb81a74 477b2d3c 459eec96", + "c23a3442 4247fc6e c204a66a 3e6fa6a6 bf10abd2 4015bfc6 401ac804 3ffe7cc9 00000000 3a83877b 3e13a65e 3f013601 3db4d3a1 3ecd8a49 3f936fa0 3e024bc0 bf7f3930 3ff3e788 40729f4c 40d033a8 beb13dbf 40217a64 bf800c4a 00000004 3fafdcac 3f88e973 -> 3bdd28a6 bbfa59fa 3c99ef52 40321f86 3c9412ae 00000000", + "c20bbb12 c0724e60 41e8da1c 3dd462a0 be0831b4 3f91261e 3ffce425 3fd44a9e 3f000000 3d9b447a 3f0d2b9d 3fa55223 3ff9b414 3dfb7b2a 40c8b3bc 3f9f8958 40f647ac c0fe9997 40b316a8 40e3a67a 40198d0c c0310424 40256b5b 00000006 bf1bed42 3f4bf5aa -> bd33ad72 3e4fd0b8 bd860c99 4036e175 3fba30ac 3f1858e6", + "423cd182 412a9448 3ffd54e0 bf3aa1f1 beb5f0a1 bc5ec628 3f6e1171 3f981494 3dcccccd bd810982 414c9cac bdcfe1fe bf2355f5 3f48c676 c0d16b6e c068c848 bf2bdc70 c058480c c0ba91b0 3f57e1c0 3f0d74b0 c02360ea bfd7aa04 00000008 3fb56f6a be5816ca -> 3cea206e 3c591645 3b17e982 3f409037 3e14f81c 00000000", + "403668c0 c1529be0 c1c4cf08 3f17edd5 bfacaec4 be515fd5 3fc65385 3f1d5503 3dcccccd c082f3a9 424ce35b bec23fcb bfe5c607 be68ef17 40b3bfea 40ffa144 c0a679e0 bf491798 c0f1a11e c0ea43de c02a5889 bfeb7521 c0045995 00000006 3f1c1e50 bf87598d -> 411158a2 c0465f35 412fb56f 3ebcf11c 44bf1e57 41a50482", + "41ff05f8 40eef330 423e9f06 3f94e61c bfce203d bf1c1e1b 4005e728 3f355ab8 3ca3d70a bfbb5702 3e86ac2d bf1af3ba 3e36fbfb 3eb4edd7 403b639c 40a3cb0a 40fd65ca c0857cfe c00c19f4 40d756c6 3fbe79c7 beac7a96 401b7fcd 00000007 402c7941 3eca9741 -> c12b2e60 40de3953 41a02488 3fa92ddd 49b5851f 4504a2a8", + "c2081236 c22f0ce2 41be72d4 be766201 be92a88c beec84b7 3f8ea784 3f8522e4 3f000000 c06bca79 3e993d9e 3fe4ed16 c0134dbd 3ee1a380 c0d107ae c0bd2894 c00ce5d4 3e84bde0 3f0b8410 3f61c690 bfa2829b bfcbd6a5 3fb9ab08 00000001 403a4227 be9bdf1a -> 4185a550 c17ce0b5 416e4db4 3f610379 4603bcd3 43e10866", + "c22e48ed c1e79f94 c0c16370 bfd29ce9 3f9c7fd2 3f431d56 40105811 3f2eee17 3dcccccd 40a88d6c 42c46d09 bf70f7a9 bfbf6845 bcc53b6b 401b1658 40cf2624 409c0214 40c77838 bf9984c8 40c79260 c01f55ec 3f1074f8 bf9e40a0 00000003 4001ad83 3efad906 -> c0602a2e 4016639a c1ad28f0 3f8620b1 462a3b57 440889ac", + "41d69a58 c1ffe54d 40db5d60 3e9ecf98 bf91770c bda28a1d 3f985270 3eeedd0c 3ca3d70a bd1cc74b 3fec4948 bebdd541 3e907e0d 3f485cda 40f846b6 3ff76f50 c0c95424 400118a0 407eff8c c02667ee c03765cf bff88611 403905c1 00000008 3fca16e0 beaa88cc -> bd9cc623 bda91322 3ccc5d66 3dbbda63 3e38231a 00000000", + "4063df20 bfe8bd20 41a4f3c0 bd604ccf 3e7bb9b0 3d13a5da 3f1b4866 3f344dc8 3f000000 bd3989ee 4152d630 3f497416 3f8c25dd 3f1519ee c0f15b4e c0badda2 40dea2ac 40207c80 3fcbd368 405af600 3fbb8bc6 403d1213 3f9ca8d9 00000005 bf989372 3f81d4bd -> be737f54 3da92dbc 3e3de229 404335c8 3f6427cb 00000000", + "4200bc86 c007cca0 41f7484c 403b58f3 3c1166d6 3efe6880 4048b15e 3fd54b2c 3dcccccd 3bb87d52 3fae160d bfdaefbf 3f6e6a31 bfd9d9a9 c09dd1f4 c0ba85b6 bf95fa04 40be700e 3fbcae78 c0c36d88 402a3077 402e7f15 4017b376 00000007 4018598e bf6bdf0e -> 3eb34ee2 3f7a783c 3fc41b28 3fa4eda7 423070c8 405973d3", + "bf870120 c20c2b89 c13e4bf4 bf61632e becbe2e4 400ab23b 4019127f 3f72aae4 3ca3d70a bac58759 3cc47cbb bf8b40f3 bfd2c732 3fd39cc4 403b9f9c 40ee8be0 409e3a22 c0c1f092 bf8a8ebc 40eb463a bf7de81e 401af927 bfcf70d0 00000002 3fb9aed7 3f055b03 -> 3c222d72 3c3fd32d bc0e801a 3f81a04a 3e54a178 00000000", + "40ee2d70 4193ad48 c10c3fd0 3f034d3e 3f338bfd 3dedb647 3f60691f 3fc28a75 00000000 ba8326c1 3e69e935 3f62f1d0 3fb5fbf1 bfc0a9d9 c004e3c0 bf10d450 40c72b68 bdb87940 c0c0533e 40414570 bf64385b bffb50b2 bf830fa7 00000003 bfdd6c9e 3ede9ff9 -> 3a30a30a bb881efa 3b10da06 3f996a62 3aed1f25 00000000", + "422f33f0 42191696 420a544e bfd24655 bf735d08 bee08c31 3ffde779 3fe3ed21 3ca3d70a bc95231e 40bf94f0 be5dffe0 3ff79e7c 3ee60514 c0e94a88 40a5b96a 3fc3fa70 409d1d06 409efc3c bf2e75f0 c0383c3a 3e2485d9 3fa418f7 00000004 bf86a17c bed5b99d -> bc029dd3 bbba19a4 bc2b1b98 3f57551f 3c4c5d81 00000000", + "422d5658 c129f3a0 c00419b0 bf8bc7a0 3fb68e9e 3f5fc09d 4000d913 3f47bafa 3ca3d70a 3d6c5360 4110ef14 3f6614bf 3e4a2da5 4017a113 40e7f7e2 40e0888c bfef4658 3fc91c80 bfc22b54 40a4dd00 3f65a8e5 3f81596f 3fbc97c6 00000004 bfdde638 bf889eb1 -> bcba6190 bf12f92e 3da6a680 bee619a2 40847d0a 00000000", + "c18ec486 c1d0e741 c2021e2f bf155271 be2305f4 c0016d23 4007e6a5 3f22c000 3ca3d70a 3eea9899 3f502447 3f9be9b8 bc8bea82 c0217138 c02c7ee0 40daeacc bf9df588 bed76b40 3f466c80 3f4d0120 3f91c6f5 bf2621e7 3f0a6238 00000003 c01d450e bf8e495f -> 40d8bc4e 3fdaa46a 408a9d48 3f753c55 4466e8d4 3f8c0100", + "42143d40 417443e0 41b8ab5c be8d4a6b 3c396f92 3bd55cc5 3e8d72e2 3e4a9a44 00000000 3acded64 3da82838 be90f6b4 bf6c6564 c009e0eb c05e34a6 4083435e 403a0030 c0afb266 3fe0ab30 c0e7673f 3dfb8665 40276f46 3f3318f8 00000006 c002e1dd 3f08beb3 -> bc997612 bb9c2e1e bd62f21a 3fc148ef 3f74011a 00000000", + "41fd555c c1a019df 4080c540 be610fa0 3e8511e3 3fd467bd 400ca14c 3f80dd84 3f000000 3f0589d1 3dce81c2 be05ca38 bf065133 bf096667 c0ff7b8a c0a60f24 407c18a0 c0813e9e 3fd56dd8 c0d707d1 3f9b074a 3f52786b bffbe45d 00000003 3fccdce4 3deb5089 -> 3f3f02d4 40a08142 40961e1b 3f3efd62 45008631 44bcb8c4", + "c0854798 41b1e17c c17dbaf0 bdf70a3a be393fd2 beacf065 3f040d46 3f92136c 3dcccccd 3d6d5bbe 3d9da3f4 bf9c0c7a bee538ae bf17353f c099d39c c088b3ca c0680334 c096af48 c0ddc8ff c0fa4f43 3fbebfac 401bc897 40032d1c 00000006 c01a84dd bf45b19f -> 4185d489 412d96a0 41569aec 3f8558c4 47925405 42f007c0", + "40260090 c09e1f38 c200467e bf08c9ac 3cbeabcc bf35a424 3f80969e 3f94901c 3dcccccd 3dca2f4f 3e22b911 be30bbec 3f019ac7 3f5303d1 bfbabdc4 409c0384 4046476c c0afb7ce 40f0a542 c03291d0 3f8d3194 bf98b27f c0320a25 00000007 4009fbae be96c5c7 -> 3d1fe5bc bec294fd 3e2d365b 3f12ac94 43e9040a 4010ca32", + "42094590 c2281d8c c1d54f09 3fc76dfd 3e44f52d bfdd007c 4018ff98 3f11fcf6 3dcccccd bbfebca5 420c6f94 bf37a4bf bfb4a3ed 3f6ad10d 3e617a00 c01b488e 40cf251a c02b291c c0b49af4 3f0b24f0 c0174fc1 3f0c0025 3fdef72c 00000001 bfa33801 3eb47537 -> bc5c4702 3c827fc1 3c472633 3f24decf 3ac71fd4 00000000", + "c1a1b19c 41605d20 4235e8e8 bf44583e 3fa82ca5 bcba5344 401fe642 3ffa1744 3f000000 c014b381 4040f656 bfae3065 3f1b01c5 3e8d2f74 40dd5474 40ccb418 40b8a220 3f007020 40bc23d0 bf440380 3f69c475 bed58421 c03b4245 00000001 bf580972 3ea4255b -> 3f9fd909 c08ded13 40827bf2 3f3730b8 439630cc 434c1837", + "42292858 c0876980 421206f0 bfc07646 bf8395c4 be8b225d 400d9067 3f3d9d20 3f000000 bd37c2f5 3d628972 c0070111 3f3c1611 bff7185a c0f242aa 408135a4 400d3948 406d5734 c0ec072a c0ab4ef8 3fb43cec 3f32bffc 3fbb5536 00000004 bf04c4e3 3f5727be -> be95df69 3db8729a beb91f6d 3fe3375f 423cbc37 41d2cc18", + "41038620 42065dfa c22cf126 3ec66ac4 3cd0c853 3fa5bb5b 3ffd6bec 3fa0ca21 3f000000 bbf7b4a1 4101741d bff2239a 3f5f0e6b 3faac588 3ed3b9a0 c047014a bf23d848 c08e4cad c0ff1651 c07fb278 bef19e58 c015199d c02eca9e 00000005 bfbf3b86 3e8e4eb1 -> bbe6b16b bbea74a6 bb52c4f8 3f5d4f42 3b9d1174 39ef6fab", + "41c4db64 420cb244 3fc61b40 bf30f98c 3fb05fa9 bfb6eee2 40130440 3ec7a71a 3f000000 3bb95f78 3d26224a 3fd4b898 bf8eebfe 3ebe7ca7 400c5714 40374cc0 c08ac116 40f4f4f0 c09190ea 404918c0 401e7a4a c008162d be1edf8a 00000004 c00c6978 3f62b67d -> 3fb7997f bf712f64 3ea29730 403115e7 42da6921 42cd3420", + "c200abde 416544c8 c199fa7a bec20642 3f2efe07 be9c4c53 3f7268c0 3f8a0611 3dcccccd be97adf1 3d1c5fc1 3eef2aa5 bef23d1b 402d73a1 bf287c58 409f424e 4086916c c0c1c112 be82d2c0 40b5ea18 3fe286d5 3f8580ce 3ed85727 00000007 c01a75c1 bdd8581c -> 409889d5 c0792288 41b52781 3f6bad2a 46038a3f 4228f243", + "c196c191 c1e1e8bd 41faf76c bf659b4a bf11065f 3f78669b 401a20ec 3ff87922 3f000000 3d4aa591 42a725c6 bf974ef6 bfccada1 be33dc54 c0fe0380 bff59a88 3f75fd10 40de1112 409368b4 402a1184 c03fc74a 3f64382d 3f6374f5 00000002 bf6768a7 3f0c711b -> 3dbe19a9 bd81cd00 bd38c684 3f8ab59b 3e385db2 3d2134e0", + "4242d8f4 42131010 41303d38 bfac0995 be631508 bf6315d8 3fd012ca 3e257f3c 00000000 3e8a4fec 3cdf42ac 3e00a39a 3fbd69cf bffa3963 4012e154 c09bba6a c09601a7 4014f070 c0518414 4097d396 c034aa0c 401c7976 3f087d4d 00000002 3fc30870 bf47de0f -> bfd88ce0 c184ccc4 41b8a7e4 bc8fade0 4573b484 00000000", + "420ff00a 41d3a02c 4199c188 be9f46e3 3f9e521a bff68fe3 40380a1f 3f90bd6b 3f000000 bc9a7ac3 40f3d525 3e657fee bec1c4d7 bf0d4ecf c021b548 3e340c80 40992fc4 c0d85aa8 408b3f4a 40f3336e c01623ad c0129176 bdf5485a 00000003 bfee7464 3e996eda -> bcaad2cc bd13433a 3c5155d4 3f9d1a80 3d60747e 3d1a9cb1", + "415400e0 3fe8a640 420912ee 3e4d3a11 3e1f115c bd46aada 3f8a012a 3fd1eb24 3f000000 be380a78 40e4fee2 3f6d58e5 3f311b3e 3ebb4f85 c04519f4 40c467ee 40e22898 bfea2708 c04e350c c03a967e bffe09bc 3f8dfe7c 3fe20a19 00000006 bf1d6bea 3f949d89 -> be400e0f 3d74b99d 3ed08af8 40c97b64 4038e8b6 3d79fcc8", + "4102c70c 41f45a78 c18d9c62 beabb108 bf2207c7 401a3bb3 4020e6d2 3e8104b8 00000000 3b14a7cc 409b0463 bf0de2e2 3f30662a 3f5f73f9 3eaedce0 40dad914 3fe27028 bf95ff40 c0e426b7 c0e07f5b c03e7319 bf53a5ae 3eb5a9cc 00000008 3fb64f80 bf84bf18 -> 3d9e3262 bc5d9b0e 3d9efeea 3f08b31e 3d338f7f 00000000", + "c069edb0 c1d9a784 42113700 3ed6a6a5 bfa22e93 bfb797e0 3ffad416 3ca45b53 3ca3d70a c00c2e03 4113dd5f bfb68878 bee94476 bea751ab 40f43b32 4029532c c0c3fec2 4080fede 4053ece8 c06a3152 c0311c0e 3fe867c3 3ff052db 00000001 3f7589c4 3f95f158 -> 3efaf314 3e48a7a9 c01661a8 3f6ccf51 43857860 3eca859a", + "4155e1e0 4193bef0 41913b68 3e89e493 c01c9020 3f8c34ea 404f43e6 3f8b70ac 3f000000 bfa10ff4 3d14d576 bf12fee9 be7bb656 3ee466eb c0e9b53f c0d80358 3fdae500 40386eec bfe62e6c c0acd630 400902d7 c01bb1b8 bf8d444e 00000002 40381a36 be66c8a0 -> c269443f c1afdb21 423afed2 3f502f17 483aaeb3 4838dd14", + "c18c2c30 4213f24c c23ee667 bedae7b9 3f8f6c93 bf2643d3 3ff28230 3f87ddf2 3f000000 40145aef 3df4002f bf227d52 bf1ce43f c005bd3f c0348b4c 3fea53d0 3fb83b10 be17a2e0 be79ae80 402caeac 3ff51a38 3fa9926c bfd2cd69 00000006 bf8ed37a bf049bd5 -> c0b3ee77 c126d354 c20535fd 3f224e46 47570f87 4636585f", + "c20841de c18cddde 41af1688 400ef975 be2acfbc beb03f7f 401bb070 3fd4add8 3dcccccd bc29747b 4226bb4b 3eb9ae5c bf238da5 be157fd7 bfd60b54 402c7b34 404fc260 c0e2598a 3fee6e28 3fe699a0 bfef869a bf2cc9b2 bfd1aa4d 00000004 c0392fb7 3edcb01e -> 3d23e030 3cd18c8c bc24400b 3f5a0ca2 3eb59d18 00000000", + "c1f6bdae 41704400 c20ddf2c bcb8f611 bdb629da 3e5cd2aa 3f4070d8 3f8471c1 3f000000 c07e1d61 3d82ee23 bf80a85a be1b4c87 bee82870 405bc35c bfcfa370 4055b430 c066d91e 40c54884 40690bcc c00313d9 bfa74b59 401ed56d 00000004 bf8ee446 bf582351 -> c38fb142 c222f40d c30a199e 3e089150 49629359 46239dda", + "42188056 c1c939e7 422c7f70 3f8cb81a bfeec60c 3f7fa9f8 4018ad49 3c32a067 3dcccccd c10668ca 3d0f6bd3 be084942 bd861d20 3f0108c0 3f664200 c0c1de1a bfe7ffb0 40819570 c085c65b c0e6c649 c02faf3b 3fba4069 3f5b976b 00000003 bfb467bf 3f2ea054 -> 42451398 c1c3a214 c329963e 3f67e1d7 4b4cd140 488f4957", + "41a50924 c0f20390 c214f079 be21929c 3d7626b5 be951253 3edb60b3 3f6b4506 3dcccccd 3e2cec28 3ee27d8a c025b4b5 3f2cbace 3e8ec015 403d151c 40807ef8 bfb208ac 408d0722 c069340e c011fc4a beac70f4 c0198398 be693576 00000005 bf4a2d3e bf47c07e -> 3e52d4ea bf06bb65 bee07cb7 3f9c8e89 410687d3 00000000", + "c1f89cc2 c1f80bf0 c1991238 c00c5ca0 3f212e08 bf109034 40166fdd 3e5fe880 00000000 3ad9ecc6 42900389 3ee1c2c1 3f560816 3c80f6b6 c0be1092 c0bb6440 c0667a1e 40da4930 409cd790 40cf3d34 40003de4 c02abec7 c0278f4c 00000002 4025f3df 3f367373 -> 3b7e64f8 bb04cccf ba508f2c 3fc21e6c 39fa955e 00000000", + "4184b7dc 401a6950 c238b14d 3f644082 3f904daa 3f404fa7 3fcf9758 3fd918d3 00000000 3f8de33a 4002b066 bf597c26 3fb6f8c6 3e09a83a 4039cd28 bf97de40 3fb7ee30 c0d3892f 40eff156 c03d2324 3ffcb335 c0277509 c0142959 00000005 4014f23e 3f8738e4 -> 422bf85f 422d3be1 c2a1bea6 4081414e 4702fc16 00000000", + "40ac6518 c20bc007 4246c90e bf3ff4a3 3fc95cc2 bf891061 4002e7d3 3fdcb91f 00000000 3f43ad75 42928f93 3fdc6638 be7f60b2 bf405c2b c064c5ee c0a057ef c0fb5e82 bf91501c 40aab4ee c024b7ea c0324165 3e6d5a9e 3fe448ea 00000005 3fcd82a6 bf8d935b -> 413b086e 420a41e4 4173437e 3dae1768 449919f4 00000000", + "c21b5bd7 c202438a c1e10d2b 400c423d bf05eb5e 3fe6136c 4039372c 3f1635ee 3ca3d70a bf23b53b 420a551b 3dd9e3f2 3fc29635 bfc8989d bf99b1c4 4095f7c8 c0c59a01 c0c9a881 c08d6d63 c0a62f1a c0090daa bfcf51a2 3ee4bfea 00000006 402e4b65 3f379b33 -> c0062328 c1c5a799 c1caa532 3ff0f4ae 47e90f4a 43a97449", + "bd423800 c170345c 41875bc0 c016098f bf6cc6a5 3fb9451b 403b4c95 3f81d0d5 3ca3d70a 3e16e24a 4094fb74 3ea8c370 c02093a2 bf7646ad 40831abe c0b40608 3fbe0078 3fe86e78 c0d7f85c 3fc57cc0 40010b21 bf30f575 c00e7c45 00000002 bfd68132 3f28c37b -> 3de91632 3e364c98 bd2b16ff 3f9d3a0a 4001f925 00000000", + "c22bd2d9 c05808c0 40cd45f0 3e27b1a3 bcf975cd 3e811da7 3eaea98f 3ff90945 3ca3d70a 3ec83f40 3cc69e2d 3ee20625 3ef51edb 3f5d6a0d c0ef705f c08cf02a 4095a0a4 408827f4 c0d35d93 40adb96e c0005524 be770fba c025ae3b 00000001 40069b98 3eb1e550 -> 3fcdfe15 40423f18 409fab73 3ee75c9c 46623c1f 00000000", + "c1c67439 c22425ee c19d1da3 bfc688db 3fd98179 bd80b667 4016d28f 3f0ce3c5 3dcccccd be5dff47 4030d576 be82d672 bdcef25e 3ffc5fe9 40d127b4 40c239b8 3fb918d8 c04f9c18 c098870a c0db67ba c021e3dc bf055efd 3e308efd 00000003 c00ec91a bf0aa7d6 -> bf192eac 3ecbe6d8 beec51ae 3f2cb1a7 41e0cc06 3f9ef8a2", + "c1f243ec c1ce9137 41d098a8 3ffd7b9a bffd2be1 3e923531 403f86da 3fe559b6 3dcccccd 3dbf6fb8 41123ef9 3f256383 bf840ef4 3fc7bf6b bf0d7540 40e1ed04 402d6294 c0fca598 407696e8 3dda5f80 bfed56d4 c018934f 3f783f7f 00000004 403b60fe bf8e7067 -> 3eb411a8 c0bd1fe7 c0867f0c 3e4bcde8 43eb8fc3 41f94e83", + "41abdc14 415d07d0 3f969660 bf2891b9 bf564dd1 40272e23 40696cf1 3fd38845 3f000000 bccd8751 3c78cef3 c031ac6e 3f75a257 3f10327e c0557d9a 40840bf4 bddc7280 40811a1e bf5fee00 c0849450 c034bd1f bf520f63 3f7d6393 00000008 c003bb73 3f8cdb2b -> c3f03aa8 43374404 42ad3926 40c9f680 4b0d3eda 4ad75d5c", + "40b8dc10 c23a05fd 3fed78c0 3f78d649 bf92dba3 bf8eca4f 3fefa8a0 3dcff8a7 00000000 40263715 3f28aab6 be0b52fd bf50fd2e bf4745bf 40982036 bf9fac30 40035058 c0c1c61c c0b6d607 3f9db768 c00d45e5 bf91c195 40334e56 00000003 4003e1b3 be72b84a -> bfb95b45 befa1af8 c096ac1d 3f191e4e 44b23a23 00000000", + "c1e61367 41207fa4 41e8c5a4 3e887d71 bf1eddb3 bfb59849 402098f4 3ff02499 3f000000 3f460e9b 3d82ca92 c00ec4b0 bc6a5b93 3e918486 3f472fd0 c079f2a4 40c54066 c09041d4 400ab0f0 c0a7a9f8 c00efc60 40172009 c0180f77 00000003 c00e2aa8 be959c06 -> 421a1d41 bf7c7857 c08be5ef 3f372041 471034f2 4624a779", + "41ce21b4 c191b7f0 c18d649a bd5cf80b 3e91dbe1 3f2f5a19 3f3e6a27 3fdcd182 00000000 bade42d2 3c9a0a99 3e2aa7b8 bcfb6332 bf9df02f 40b075b4 40fecbc0 c0ec0022 bfbf0720 c063bc48 406fe194 402ba660 3f870a17 40256552 00000003 bfdb06ee 3f4cae62 -> bb2d0790 bbc7d950 bd5cebb8 3fe625a1 3fbaa7cc 00000000", + "c051c350 c226a55b c1274a58 bf7dbf36 3fc780bc bf5e812e 400afcc5 3fa6cae0 3dcccccd bd6ff458 41f68d4b bf089cf5 3f5df8b7 3f51b5f0 c0e2f76c c0e7936a c0247dbc c0f28000 c0cb5363 40084704 3fe988b5 4037ecdf bf2fbc48 00000004 c015189e bf9611fd -> c02d4392 c0933b3e 4057c87c be232118 42671d20 4029bd9e", + "41810294 c12620a8 c22ea73d 3f8ab95c bf19dffd beba2fc3 3fa551a2 3eb24fe7 00000000 bf00b878 3f8d253c 3ffe500c 3fb8d332 bfd36600 40ac2304 c00ccb3e c0e68cea c01edf8c 40ee9770 bff1016c bf0eadfc 400cec9c bfba371f 00000008 c02e6ba9 3f1d2faa -> c164c98c c1079c6e c1f998a8 3ff09ed6 4911265e 3d615673", + "41cd1820 c1d3ed1f 41436130 bddc2031 3f34fa60 bfe3551b 401a7872 3f7f7f79 3f000000 bb9ffc7c 3c6c2895 3e10bf90 bf73c204 3cb7a53e c0ec8071 4080fe18 c014ec94 be89ff70 3fff5558 bf827e64 bf275975 c01088ba 3f10ea25 00000001 c007b3d6 3f2f5926 -> 3d6e2a29 beb4941e bb0d4d2f 3f32e426 40cc4ad4 404f483a", + "418a7b1c c207d1ce c1686580 3f3bd880 3f8798c8 c0112e99 402800b3 3f4e0bcc 3ca3d70a bf8504ab 405e4808 403377c8 bf056635 bf2ab192 bfe3d63c 3fec44d8 4085f044 c057cbb4 4081df28 40e66e02 3fa7354b 3f7f9f04 3fbf382f 00000005 4026692c bf9883e9 -> c208e9e5 42333b5c c345a6a0 3fa00f3a 488d1215 443bb654", + "c22f47fa c0a19c40 c19fce87 3f499935 bd69f365 bfc0e8e1 401d02bc 3fc07ba0 3f000000 bec2535a 41ee2309 4029708c 3ea60eba bd7809e8 c089ed2c 40c8e444 c0437e0a c03f71bc 40971910 405ea168 3f81f385 3f8f972a c00bbebf 00000001 3f842362 bec4c173 -> 3de61d37 bf25844c 3f3be08d 3f6d682e 41003efc 4115593e", + "41271358 c2101e25 409498f8 3fbe7320 3e3c79fa bf5b6ffc 3fdd4367 3da86392 3ca3d70a c0a31b24 40275442 3fbae5fe 3c93a8ad 40087169 40174dbc 401f3bf8 3e439900 403d08f8 40f2a0ac 40073330 401887d3 bfa7d8fc 3f01e478 00000004 4005e36a 3e91f6c1 -> c0243383 408fe051 c09ab747 3f85147c 4601ccd5 41265d38", + "4230fa3c c1cf482c 40e82408 3ef91178 ba4762c4 be872f30 3f0db1ef 3f484bb5 00000000 3eb230a3 41426b0e 3e240509 bebc44df bf0ae1e4 c084609a bfd5884c c077b8f4 c0a7b7f4 c0a9fc66 3fcfbff8 4009aaea 3f0bc13e bf6711f8 00000001 c0024d7b be867db6 -> 3e7a69f0 3e726ff5 bdb75f63 3f351620 40409d9c 00000000", + "be88ec80 420c1962 3ec8af00 be861079 bde0456a be1632d1 3ea3932c 3fc239e9 00000000 400d8373 3e0432d2 3f991eee bd913160 3f68171a c062ae48 c0afd3b1 bffb2ee0 c083598e beabe7c0 3e3b5a40 bf9bdca1 4005ffa8 3fa4b50c 00000008 3f61d4f4 3f233fc2 -> 41f33b8e bfdc94c3 41c355b7 4010cf06 4639f5e8 3995dc80", + "4129e73c c1ad1da0 415cce44 3fbb7e0d c002cb36 bf011a6c 40261ee6 3fc78bf9 3ca3d70a 3f8ecdaa 3c2cd424 400f1f50 beaf7b79 3f948245 4056e80c 40e84dbc 40c976d8 40c8b314 40f55f1e 40e6195a c02ce8f0 3f63298c 3f450278 00000002 bf2b6348 bf5b7862 -> c1f8b3dc 3c1ee000 c19cf842 3e7f6708 49525756 44968ea8", + "400a1980 40ea07b8 c1b6b0b2 3f5b4a22 3f8fdbbf 3eeee1df 3fbe7ba4 3cb2239a 00000000 be1d9830 3cb9de6b 3f71505f be5b7c8c bec7105c c0014dd6 bf02a908 bf2f0468 40d3b6e6 40e04242 bfbd4f8c 3eea79ec 40337a94 bdf2d2fd 00000001 3f6f833d bf50c55c -> c11151f1 3fe21b98 40827c8d 3f2c3bc0 44a0fd75 00000000", + "c15b6f18 41e26ff4 41ab9414 be145621 3fe7e38b 3e8852b9 400991d1 3f201ba4 3f000000 3b6bb177 3d6a187f 3f8073b4 be3a6760 be5f0286 409189e2 3eba0ea0 40d0195a 40ef9e0e 40a5904c 405f77bc 3f9e61fe 3fdaf7e8 bfd3b779 00000008 3f91f116 bf6fb75d -> 3b807220 ba766b40 bbf99788 3e7b4134 4075c939 3f32918b", + "c1adb9c9 c21289da 411dcccc 3ea145ea bde8200c 3f025fbe 3f22dee1 3fab526d 3ca3d70a be7f2aa9 413db298 bfb7f07e 3e82bffd 3f986383 3ff30d58 c04e4548 c0a00eba 3fb2fe38 40d92de4 405e54ec 4005a51e be52ba5c 3f73cb97 00000004 3f1b749c 3e3500a6 -> 3c7f31ae 3ec8dc60 bd9bb537 3f897837 3fb49a23 00000000", + "c226432e c1a10579 4233da2c 3fb94e5e bf0b9ab8 bf848c8b 3fee4911 3f8aee2c 00000000 be0369f8 3fd20aea be8eb572 bfc4e70c 3f887d18 4097c562 409ee11e 408a4142 c00e078e bf0779a0 bfa45868 be21f2e6 3fb38383 be5a0e16 00000007 bf20b94f be6d7098 -> be557f7e 3ddf56be be73ad34 3f06e4f2 3e58ed59 00000000", + "c21b0e3a 40eeb868 c22234b9 3f898ea3 beb23ee6 3eb9adf1 3fa3c2b6 3f6df166 3dcccccd bb1991c8 4237f111 3e796cf8 bf272cc0 bfb3aa81 3fdbcac8 c07c2dec c0329026 40ca666e c08b7432 c0ec05c1 3fac4cbb 403f316b 3f2e024e 00000002 3fc99b20 3f59c664 -> 3b67d0da 3bc18ca6 bb157065 3fdcec88 39f18920 00000000", + "c2101754 c1b1e708 4110481c 3e7e996c bed26ca4 3e6658fa 3f233a66 3f893922 3dcccccd c11717c2 3e69ef93 3f540592 3fdafd61 3fec1e47 40b00fe8 c01745da c0a07c64 c0266eb0 401515a0 4009fd58 bee5e486 bfeb51fd c03be3f0 00000005 401e3d63 bf306021 -> c2904658 c20d4d78 430a1fac bd20ed88 4c158cee 46f4ed53", + "41fcfee0 c00829f0 c1ff32b0 3d6f0eaa becea094 bf99647a 3fa6ee98 3ff522aa 3ca3d70a bad30873 3c55fea2 bf51964c 3ebf2bb3 3e7dd637 c0441c16 4053ab0c c072d636 40d63858 c0bd3518 c0e93d31 c0326598 3f02a9b9 bfd93854 00000003 bf6d8d58 3de3d2a0 -> be379d91 3dacc438 3d6a8e83 3f57a028 3eac69d9 00000000", + "c1d428ee c1916639 42348622 be788dbf 3f45ea9c bf8b7392 3fafc0f9 3f43df42 3ca3d70a 3c53bc46 418ebf7f 3d5f52ba 3e057e61 bf04b9a7 bf2870c8 3f89d528 c0a24555 c0dbe576 c0e79dbc c090680e bfdc9d56 3fff3d01 c014328f 00000002 c017dd10 bf300b53 -> 3d1727ca bd39bf77 bc13d8ca 3e606dd4 3ca32e4f 00000000", + "c1c7a10e 41ed517c c185786a be86d413 3eda143a be3446fd 3fb5bbfe 3fe394ad 3f000000 bea17efe 3c807ef7 3f2308b5 bfac73a9 bf82ffb9 403f9ddc c0a7934e 40c38c14 c00717f8 40a92e96 40496af4 3fc93577 3fb12b11 3f08b0c6 00000007 3fcc62b4 bf533a73 -> c2d31870 435e53a0 43295374 be28ef82 4a61a9e0 48c7c6d4", + "c1ad06c5 40a3c080 4116ec74 3f068a05 c0089a73 3f30ab98 401921a1 3f631d05 3dcccccd bcad8ce5 404675b4 bffaf472 bc97d3f4 bfd3b4ee 40ad7de2 3faa19e0 befb05d0 40fb739c 408788aa 40843dc0 3e0fcecf 3ecc8f71 4013b732 00000002 3fdd608a be9afba8 -> 3cedc305 bda30e69 bcaf190a 3e5331a4 3cf496fc 00000000", + "c210359e 421c52b8 41f81bec bf10f75c be96efb6 3fa805d3 3fbbaae5 3ea571c0 3ca3d70a be81c379 4285dfe6 3fa5e027 3f594ea7 3f453c80 c0766db2 c0efa670 3f9959e0 40c005b2 c0b48dda 402e8cac 3f91990e 4031343c bf2dc575 00000005 4006cb90 be83f21a -> 3f1eb020 bf1fecb9 beacfa4b 3f1c6785 419d721c 3c333302", + "c15de874 c199303b 422bd976 bd93b918 bf6603f2 be38986d 3f836709 3f896696 3dcccccd bf1654a7 40c000ea 4022693d bf54962c bf5fe40d c0d40e5d c08e7010 c0750b22 c0ac5ef8 3f544c40 4096f8ae 4020536e bed5e297 c03bf118 00000004 bff2bf97 bf181908 -> bf6b2a5c bcf18a40 bff4c320 3f111f14 43a762bf 4026bb58", + "3efcf200 422ce730 421fe8e2 c03a2f33 3f35f611 3dc0a27c 4041fd02 3fdf446c 3ca3d70a 3c9d0a35 4097c439 3ebe601c be982a51 bf0cc9b4 c069a160 40a5fa0a c093ede8 409d774e c0ea3c2a 3fec3ed8 bf0d90ea 401914ce beee0810 00000008 3f7192f6 3f13bc28 -> bcb8c777 bcba9d32 bcb16782 3f948adc 3cc39039 00000000", + "c1b20093 c23fbcef c231f54d bf4c237e 3e10cdb5 3fc83f0e 3fe3727f 3e1cd38c 3dcccccd be42f56b 3d44bd3b beb30d97 bfa65144 3de000e8 c060c236 4015fb84 40b7a360 bf560da8 3fdc26f0 40e58ba0 bf02040c 4017ddc4 bfa1cf8f 00000005 c03438f3 bee61b65 -> c0e56e26 c1e7e643 4023a1f4 3f3c4e08 483f1a9a 443ae949", + "40d62540 c11c9ef4 422fbe16 3d85a235 bfad46d1 bf44f358 3fd750ac 3f9e5c1a 3dcccccd bbd77675 3c401297 bf9548cc 3ce63fd5 bfd3e4c4 40e13fde c083136e 40b66864 c02a4cc6 c0f58842 40d11aca 3fd62a63 3f83587b bfe05808 00000003 3f91a990 be94bdaf -> bf76ccdb 3a89abbd bfacb60c 3ef79553 41d48b26 00000000", + "c1ec8af3 c17888d0 bfc49420 3f44ea9c 3f8424cb 3f9dd470 3fe949fa 3fff6f37 3ca3d70a bb367dbd 3e306652 3f0a1704 bf6bb839 bf5a63e4 c0d5d09c 40b198f6 3f154dc0 bfe7edf8 40862d96 c0d94fe4 3e89aa85 c026079e 4002ab7c 00000001 4004105e beaf6439 -> b97329fb 3c939ff7 3c4f1fd7 3edf634c 3c0a2dd6 00000000", + "c1288558 c21aba46 c113a77c 3f5f759f 3f41bac3 3fe9aeea 400ffa85 3e36ae34 3f000000 be4f98ab 40f9bf11 3f80e8d9 3f13e8d6 bf11e049 bfb4ae58 40e367ea 408c1168 40856198 3f8cd798 c09a5452 3f1465f5 bdd79f97 c023fd38 00000005 c03313c4 bf9911dc -> c1834f1c 423b74a1 420cc609 3f199b66 4699b3fc 46bad0d8", + "c2194ee2 41b1860c be22e900 3e8edef1 bedab753 bfbda154 3fc88fe6 3f4213a0 00000000 3d9aeb74 404336e2 bfa7b8d6 400aa9d6 3ef26a85 c0c7f2c7 40fbcab4 3fba7620 40e07c76 40a5385a 40821f96 bf73a45e c0207068 4003d555 00000008 402e637f bdd5f2b7 -> bd4f2416 bcd69a55 be07eb64 3f13e7bf 3f13668f 00000000", + "c1da52a9 c204814c 41235b18 bf9486c2 bf86815e 3f4a2bb8 3fe298c1 3f58667a 3ca3d70a 3e131c3e 3f02af52 3fbc728c bfe217ff 3fa50efb c02c131a 406bf330 c0ca1b71 bfb08998 40e3a990 c07ceb76 3f92bd69 bf81faeb bf6c4b0a 00000001 c02fe12f 3e43a01a -> 3e9e6cfd be914f8f 3e8f9077 3f37c117 4024cb97 00000000", + "c1d28c78 c23404ee c22d59c5 bc8befd3 3fc95e1b 3fe4e908 404aa697 3fc8d64c 3f000000 3e573197 4268798e bf76cff6 c028cfee becc156e c0a26c7a c085525c c0e04b0c 40d50ee6 3dc4b880 403e43f8 400ddcb9 3fb17879 3f9d3047 00000005 3f9fbd9c bf7fd59d -> bfd34bae 3f2d1f98 beedbe69 3fa9fef3 420faeac 4252696c", + "c20db53a 4183c854 c20dcc80 3fca4c16 bcfc2ca9 3fb244eb 401a4de8 3f1bc58d 3f000000 c09848f8 419aa961 be6a2666 becb49c2 bed486a0 402b2580 c0802890 40c4f14a c0c488ef bf37f798 3f82b4d8 bf4d08e6 c02ea82e c02d9689 00000007 3f8e1464 bf3f081e -> c106728f c2040e96 4213f8c7 3f587768 45d9463f 45c4b932", + "42322dfa c2275029 414344e4 bff6ca86 bf957c1c 3fd1c46b 403b2bdf 3fb02961 3dcccccd 3f1aba86 3fb2a0c6 3e0e4f4d be63dfdc bf065f2c 3f864f30 c008b7a2 bd2caf80 c005dcd8 40e52e62 404418e4 3f40075a 40240fcb 3fe18acd 00000004 bfd74e6f 3f15fae0 -> c02cb6a6 bf338d28 40023f57 3ffcedae 434a746a 418b172a", + "41f5926c c1db949d 4218b210 3f0a0e27 bf6a03e4 3f62007c 3fe6962f 3f578660 3f000000 3d861259 3f2bd5d5 3e31ed58 3db4fcb1 bef134f3 c0842c3c c09158f0 409ab06c 4088cb84 bd217180 c0bccacb c037df72 c0374729 c0044d84 00000003 40229960 beaa1959 -> bb8e1048 3c9d7720 be7a9fdf 3f01cc80 40832344 40021e8c", + "c195ebad c0c43870 40741270 bdd0abd2 bf43244f 3decab29 3f4ef040 3fc4453e 3ca3d70a 3ec1c89c 422083ea 3ff3fb44 bf752d2f 40015547 3fad1de8 c0878106 bf0c12b0 40c6f410 400c3160 c0301ee2 c039b669 4022390d 4032662f 00000001 c014b9d2 bf5fd889 -> 3e6d1730 be84e20f beb2489f 3f6be030 406dfe86 00000000", + "c04e93a0 41e85d04 40ff7048 bffb9d57 bf8090c6 be949c1d 4016aa45 3e82b8c1 3f000000 bf368399 415c429e 3e3983bb c00f6248 be1e7e20 409a2422 400fd89c 3db22f00 40f10470 c0b878d7 c009bb24 402bb0c9 bf5f2557 bf9d5044 00000008 3f306bd6 3de3ec75 -> bdc79810 bd5dc635 bee705fe 3f7d3a50 41eee2af 4212b9b7", + "c1816d5a c23e9afc c22d8f92 be2401e0 3f9bbd37 beae8507 3fa3077c 3f7e8ae2 00000000 3d6a8368 4001e0b6 3ffcfb29 bfabb179 3d7c9ae3 40f7dcfe 404ef864 c09c000f 3f720fe0 40e45ac6 3f2d7360 3e94c3ed bfd30121 c037b69e 00000002 c03fd270 3ed8a1bc -> 3d4b5bde bcb662bd be0ce6c4 3f855bba 3eb64c9a 00000000", + "41e341cc 423875ee c236e2f2 3f5148fa c007c3ee 3eac3d7c 40141e06 3f4ef035 3ca3d70a 3c3423eb 3d5ccdd3 3deaa56f bf2d1ddc bec5c390 3f4b09a0 40c135b8 c0e32f7c c0834b20 c0887b58 409f7e04 40363816 bf358a7a bff9face 00000004 3fed529c 3f56042b -> 3ec8f4fc bfe3fc08 bf6b587e 40093477 428f664e 3d5ea319", + "c1a82414 41d9989c 415c7128 3fa49cd8 3ff979bb bef14008 4018bca7 3e5fab27 3ca3d70a bbf0e3cc 40133824 bf1940d7 3c403486 bd3ddd68 c0cc0a1c 40d33b6c c0c72010 c08cbd26 c0e1e443 3f32e340 3fcbc861 3f489190 3efca998 00000004 bfd2ebe0 bf418e7f -> 3c6e126c bd19e9ea 3c6b0d00 3d82bd74 3cae9cc1 00000000", + "405d5e00 42448d66 419434e4 3eb63650 bf2c29d4 3f2c299c 3f880940 3ef2320e 3dcccccd bba650fb 40a4b240 bf4a628a 3e817dc1 bf53a97d c096138c 409e88dc 3f4f1430 c0bc8a37 bf93a48c c0eeaa8a bfaeb077 3f123123 3f21ba7e 00000004 40208933 bef1d82a -> bbc8007a 3c8c01a6 3bba9809 bd985561 3cf4a0a2 00000000", + "41a33b10 41c2171c c1d7ad11 bf85d3d9 bf7cc078 bf485706 401f03fc 3fd8f20f 3f000000 bcf5771c 42720f57 be99d4cc bed356d0 be8b6925 c0cce9e9 beb35ea0 c0af92e5 40507ef0 be89d240 40d0f872 bf6332d6 3e4d8e9a 3f59bb19 00000002 40063cbe bf2c7d42 -> 3e0b7aac bd5c9d80 bd833aca 3d9b0f38 3e39a484 3d335012", + "421884f6 c243bedd 41ba50c8 3f5a56c0 bd116843 bf36a695 3f93a184 3ed11283 3dcccccd 3cf2d74a 3eedbda3 bfcc1333 be9becda 3fc75b0a 3f210f90 40eb3bcc c0d0f1de bff44fc8 c002b782 bfc4a960 bfa862f9 bf700761 3ff6e6bb 00000006 403e5d9e be96e194 -> 3e38b126 3ddc22cc bec9d8cb 3f4a689f 422f2810 3f19ddb2", + "41265a20 415df998 42188a8c c022d417 3f885d2e bf7ff981 403bc4ca 3f8a45af 00000000 be1d84c8 42666978 3f10cd54 3f6a8dcb 3f436504 bfe6e4a0 c02be40c 3fdb8100 40dced5e bfd5a978 40ed04d6 401cba05 401d7c1e be5076b2 00000002 bfd1067b bf88cd17 -> bdb17a3c 3ebd1028 bec0ab12 bc27e6c0 401554ad 00000000", + "c1a22c2e 41d675ac bf2ad580 3f89ee40 3e4f3fc4 ba8b1369 3f8f4a61 3f93671d 3ca3d70a bc9d1dc8 3d1f8fbc bf232a94 bfd15b16 3fa0245b 400ea160 4011e914 c0c7c979 3fd03240 c0cc4a90 40fadfd4 4038b4cf 3e196092 3f2fe774 00000004 40278a96 bf9363c6 -> bfecdb4c c0d0eeac 406dfd9a bc188e00 45ebb776 3f8ae834", + "c1f9ad60 c21791c0 40dc15f0 3f6306e1 bec5153c 3fb68a32 3fe071d4 3fc3b536 3ca3d70a be1610ed 41fde6fe 3f985603 3e21406f bec1f6f5 c0858bd8 40e3ef4a 40ad57a6 c08b31d4 c05b3828 3e4e6440 3e4f8209 4036d2da bfb6e2a4 00000002 3f2aafe4 be06ce86 -> bce63ec3 be05f470 be10c223 3f0ca5af 3f0f4a87 00000000", + "4159f3b4 c11554f4 421f5096 bfa1b748 bf964c99 bf78c154 4007d335 3fb67f81 3dcccccd 3bbc300a 426c6244 bf7997af 40269fb7 bed7a8ff c0175ad6 bf99837c c033440a 40827f54 bf9059c0 c0388bd4 400d7042 3f3f11fe 3f247e2a 00000006 bf73a615 3dce4932 -> bc17fbb0 bb501816 3a7ff013 3f85afd0 3a4fe0b2 00000000", + "c20cb7b5 40989250 4028d370 3e22ab45 bf84f571 3eba64f5 3f912591 3f8bf190 3ca3d70a 3ec0fa14 4188cbbb bfbaf502 bf67035b 3f442b43 c088b2e0 409cee68 3e93de60 bf3ccd78 40594040 40a5cf96 c01aa39f 3e6002d9 c035a12e 00000007 c02882ad be040f6a -> be2af283 be897b31 bf1fe82a 3ee4da6a 4203c2ff 3c23fcba", + "41cc6488 423fc49a c02ea340 3fd0638b be4cde75 be8f5a69 3fd98d17 3fe3e5df 3ca3d70a bdd2f002 42bcdc8c be6d007f bee6e80e 3d82f38b 40539724 c075e974 bff33074 3ea5b2a0 c06821b8 3eeb9d80 c02421b7 3fcf6cd2 c02aaa85 00000005 c0185e68 bf8b90c7 -> c0b0eca8 3fbe19b9 c11a4249 3f8ebf06 447b5469 3e114223", + "c1e6ab06 c2476b3e c0d1eb08 be5f69a1 bd57dd96 bdff7178 3ee9bc88 3fffa9f6 3dcccccd 3f73ec46 419c6314 3e55056b 3e36e4c1 3fe6bd3f bfe3fe14 bf72ebf0 406c94f4 40d69a06 3fcdd4c8 40bdeb40 400faa72 3ff70fc1 40193188 00000002 bfb647b5 3f948371 -> 3ff280e8 4008953c be7f76fe 3fdbc7cc 431497bc 3e25c6c3", + "41f982d0 41ca5848 41930524 bf09a164 bf25bc99 be8c8f1c 3f801b5f 3f940ad0 3dcccccd 400011a9 3e6e741f 3e965c98 3f398321 bf21e66d 406eafd4 3fd45428 c0bc7b18 c0ade870 c083e87a c0a6a1f2 3fbcdac9 4039373d c00c3749 00000008 3fcd711d 3f208551 -> c11aeefb c171bf61 41454168 3fdf3fcb 4893dafd 44b9298d", + "4213e936 c14921d4 423de094 bf162f1f 3eaf7c7c be99155c 3f3e0896 3f2be470 00000000 3c0afd6c 407a3138 bf9ff9d5 bffc0fca 3fe7bcb1 40cb8fa6 bfc0dac8 3dc65400 c0da22af c03268a2 bfad2390 bfd7f2e5 bf8d3a2e bec6acb5 00000001 3fe8b5ac 3e1df387 -> 3c6a0106 bb75969c 3bd629c7 3e4514ec 3b2aa01b 00000000", + "c16a350c c2322a93 417218b8 3c73a53b 3a6b69b8 3eb2ed0f 3ee604d6 3f7ea6db 3dcccccd bed06a39 3e448f52 becd8520 3f03945a 3f10df87 c0ca0539 bf95986c c0bad06c 4090ccc0 c0914424 3ffb2948 3f0c45e6 3fa29f93 3fdd5c21 00000007 401a5cfe bf347413 -> c0f30bc6 c1753bdb c23e40e8 3f2bf6f7 49c00f71 44cbb6b2", + "c2408499 423eb648 c1e4d635 3f5d7fd1 bf1344c2 bef74a5c 3f95889e 3f8f9eb2 3ca3d70a 3e493f54 413aa7a7 3eec6917 3e871a79 bece3dfd bfe07040 407550b0 3ea68540 c033007a bd8e4fc0 3ea04860 3f67ec7d 3eba04b5 c032ee80 00000006 3fae4c5a 3f5189e9 -> bf8e28a5 3ed7b853 bf660a78 40528333 41c3cf4a 00000000", + "418304dc 41e9d4b0 c2183157 3dea1c4c 3ee6cf13 3f03cbdd 3fb93faa 3fc0e385 3f000000 3bdefbf1 40395934 bf970daf bed22f92 beb31613 c0537200 c02a3d66 c0d1e0da c0a5a773 c0a4950c c0b0a7d7 bfc88552 c00d7901 bef650da 00000004 3f127ac2 bf4f4abc -> 3c487a0e bb08787f bca8ae6d 3eec0df8 3b0221d6 00000000", + "c208f3d6 41ef0588 41f700c8 bfaf68f7 3fd49e23 3efa01f8 40284dae 3f57e52c 3f000000 40c1549f 40252a24 beb2fb43 bfd18905 3fce5410 c063a24c 409dd52a 40e493e2 3fe88120 40810026 4092eff6 c006a9ba 3fd050e6 bfe4330a 00000004 bfb57eb2 bf8abcae -> 418b4778 41e7dcf0 41a65edd bf03adb1 4710fee4 474c1028", + "4209eb72 c1e02fa8 4191f07c c0205386 3d8613f0 bebfbf3f 40222980 3f82a55c 00000000 3ee726b9 3f110a3b be563342 bf0416ae bf216b49 bf88c854 c01d42a6 40fe7d0c 40b82620 c0874a1d c099ffa3 bf618d6e c02e441b bfd7712e 00000004 bfbbb600 3f8d6f6a -> 406e099b 3e356044 c089766c 4033f9dd 43b8c901 00000000", + "c1f1cd3b 4243a39c 41f2b388 c0044e62 3f4281f9 bf53a49a 40202d4a 3fc04a0a 3dcccccd c05212b6 4265eef6 bee86415 be888687 bdd6b352 bf3c3c80 40d88332 c08c20e0 be530720 bfab1818 bfc82754 3f72b0f0 c03f43a2 bf0fc33a 00000003 401f7743 3f48f6f0 -> 40944c42 c1ab12f0 420c0b1e 3fd28b9f 468ac177 444a1d5c", + "4213d654 c23643c1 41559bac 3f13fac6 bf9dfc51 402a0789 403f191f 3fb80e40 00000000 40751f00 3f0f4644 3ed3401a bee4e6b6 beaf08f7 c014db00 c09e3cbe 40c41a2a 40f6aa6e 408fbade c08ea5d4 40092deb 4033f9d3 c02b70aa 00000007 3f8cdaca 3f50d3f2 -> 4168e436 c102ed21 4153ee07 404bbf32 46047958 3b71bfc9", + "421b1c4a 4197b814 41244164 3f8cad88 3d92be01 bf36671e 3ff0e6f6 3f91fe99 3f000000 bfc772a5 413f3ba9 bf4bb6bc bf528b8c 3fa9c2a3 400d8da8 407b54f8 40f5c6ba c0aec372 c07049ba c09b3b87 bfcf4efa c02c4bca 3f8d4bd7 00000004 4005b4e0 3f6e8e24 -> c03b1484 c18740bd c153fbe4 402fadf8 463ba213 458858c9", + "42151534 42109578 c195be17 c002920b 3f44a94f bf5ca5c3 4017731e 3f8e102a 3ca3d70a 3ec581a2 3ea4b07b beeba22c c02b0593 3f8f0a80 c0df69f5 c095c94a beef4ff0 c0abf6f6 402d4a3c 40849186 bfa4894b 401cee21 3f1ba1aa 00000006 bf0d66ea beda3763 -> 3e4e3d9b c0281e09 3f22e524 3eeca174 42b1600f 00000000", + "41c8a888 417405f0 c1bec1f1 3f682542 3e965542 3dd9bfcb 3f758657 3f8dfd43 00000000 3b945321 3dc4671d 3e8f9abb bfd9f96b bfd3eea5 3f8784d8 406ec0b4 40c67aba c0dee19a 3fc6ef18 c036839a be6c7e8e 3e8dbb81 4024e023 00000008 bf6f1645 be3a5599 -> 3c612472 bd0d973c bc4e541e 3f2fecff 3d74703f 00000000", + "421d00ca 423b3ac2 420aef90 3f04c9f1 3f43b6d4 3f6ec805 3fabeec7 3fc27ff0 3ca3d70a c08e3152 401eb95c 3ce02357 bfbdb781 bf46be1c bf720148 bf8f7e00 3f17eed0 bda03b40 c0fa1950 bf714c80 c02c6c3c 3fa5dcb5 3f1a6bf4 00000002 3f062979 3f4a405d -> 410cc2f0 bfb29506 c0a420aa 3f838236 44a72135 00000000", + "42100c56 420277a0 40c806d8 3f96809c 3fb17fb7 bf03ce96 401a1f29 3f84c044 3f000000 3b00708d 3ddfae58 bf9665e2 bfaed904 bff5374a 409a631a c027ac04 c0dafae8 c030456c c063d114 40cbe896 3eb1f629 3fec8b22 bfb736c5 00000005 400b3a32 3f265888 -> bd91e7a6 bda7cd4a bde73073 3fced169 4016b51a 3fdbe910", + "41b870e4 4237f190 c209a0a6 3fa7a5dc 3f0c085d 3f971685 40011478 3fdaa457 3dcccccd c0447376 41399842 befcf508 3e801af8 bdb2c713 40025100 40e06f96 40bd4642 c0e8816b 4075d67c 4036fc94 3fd71798 4029eb70 3f8bb113 00000002 4022d4f5 bf20409c -> bfa4990a c0143ae6 c078ab3c 3ec50704 45258aa4 42a54e9f", + "4104ef28 c0db4188 4178e2d8 3e3ed993 bf608a18 3f15b4f7 3f9f24a3 3fdd26e8 3dcccccd 3eaa1e5a 3eacfd7b 3f5d938c bfe1b6fc 3ff0df2e c0bdb876 405a8d24 c0df0772 beeadb50 c0d4d918 c0bb2262 3fb2f9a1 beba0c5e 40327670 00000007 bf5b106f bf5ef043 -> be5e2086 be842633 3faf8891 3f056d66 430c6c70 3f91b869", + "41f00938 c1ad30d7 c21f61d7 3f69d1fd 3f783f44 3fa7e67c 3fefabc7 3e13d626 3ca3d70a c01a4000 41af5f39 3fbce4b8 40042f81 be4b809d c05a1140 c05f4028 c03240f0 c0f69156 c0e0102f c08a77ea 4008ae8a 3fb64d0f 402413c6 00000003 bfb389d0 3f801d81 -> 4015fce6 c0313cd4 c020be30 3ff7db23 450a5049 3fd0e246", + "40430660 4210255c 4124efe8 3fe5e82a bf233806 befcb121 40004482 3fe25abb 3ca3d70a bc8528cd 3fb38406 3f6d42d8 beb1bc5e 3f340563 3fc50680 40a36ece 40f6119e c0c5b813 bfb28dbc c0f4c7fa be88bb78 bf7323b2 c00131db 00000002 3f83227d bf787cbf -> bcf2aa7e 3c853da8 3c654ab6 3e87672c 3c285208 00000000", + "40845238 41bf0310 c1819460 bf81c686 3ff5442d bf1ea54b 40104caa 3f857fa0 00000000 3c1e5fca 3dabda47 3f368439 3f4fd836 3fceedf3 c0ca0aae bf557d40 409f9cfe c07f4d64 40a3647a 40e7436c bf21533f 3edce022 3ee755e5 00000008 c03599eb 3e8a29b2 -> bd8ee903 be05902d be54064e 3fa466f8 431fd509 00000000", + "c15dddc8 423b2112 c2098551 bff4134b bfffee42 bf80ff18 403c3964 3fa93416 00000000 3cd71ff4 42066b23 3dc97936 bf84670e be25fe4c 401910dc c0860ade 4078d9b0 bfc5a358 c0c8f2cd c0e2790f bf4b9a04 bf114f44 4029ee91 00000007 c006056d bf6f760a -> bfea207e bed82401 3f9b629f 3f342d4f 42df3d4a 00000000", + "c20cf166 4230d7d4 c05be2b0 bfea4e16 3f29801e bfcf0ecc 4038b542 3f35cc40 3f000000 bfbe5ea6 4188d738 beba2873 bef6561f bd4a4b0b c06ad304 c0f0fd0a 3e766e80 c0deffd6 40fa2042 c0897d34 4032df97 3ef552f0 4028fa0e 00000007 bfe5017d bf824735 -> 42012e15 c1b4e9d2 c2e1ffe0 3ebd1dfd 484f4e35 48b60f82", + "c05f4b80 c228765d 4200ec32 3d1926a5 bf5cc310 bee3bae1 3f7e2c0c 3f8bc45d 3ca3d70a bebfb26a 422dedac bb57fb84 bf179e79 beeb6c87 409c2dbe 40326d6c c02b4418 c0cb2ef3 c0cbaa4f bf13cb10 c02f0a9e c038fde8 40028c58 00000006 bfbde930 be554c11 -> be34c3ec 3ee37ad8 bf0f69ad 3f20e76b 4102ec28 00000000", + "41b509c8 422f82d6 bfd9c3c0 3fc83436 c0106a7f bf8e1bf3 403e8433 3e1c5647 3dcccccd bc07f681 3ca04f08 3f36d446 bf8759d8 bf82e73c c0691a60 c02e02aa 40e071f0 40ea33a4 c0b06364 4027e1ac be94e57f be24ff2a 3ef1abc0 00000001 3f4ab252 3f74f0f2 -> 3e884bfb bec286da bec7be35 3f5d524d 41201984 3f6c3158", + "c09c3aa0 c11a4968 41a4c5f4 3fe82933 bf952f70 bf3bda0a 4011c10c 3f966a6f 00000000 3b4e10d8 3d1ebe66 bf8ec049 3fe9e252 3fab4e77 40fd6fc6 3fb465d0 40b8a29a 408d3a86 3e6b9100 3e7b1f40 4018e85d 3f5b7f19 bebae0a6 00000001 3f66eaaa bf834931 -> 3da836da be01d65b bdb5ff3f 3f098c44 3e46dcb1 00000000", + "41ddaa04 4183e8c8 c1d012ca 3d7601bb 3f96891d 40058b76 401a723d 3f5c8d91 3ca3d70a 3cbe52db 40c2fc3a bf1eeb24 bfd4437f 3e941278 c0d5311a 40d8f84c bf22fac8 40dc3fba c0d3f707 40ca2f1a c01f2ae3 3fc758dc 3fc3c260 00000003 3fa1de7a 3e5b67c0 -> 3d130eec bbfe40cb 3b33a6ec 3f85bbb4 3c9c7e19 00000000", + "bf332780 42335d28 c1f0b69a bed9427d bfe1fc01 3ede081d 4002be98 3eb442b0 3f000000 be7ace3d 3e0201bc 3daf7b97 bf8c980c be2b8c63 40212a48 c0e60fb3 40e0eebe c0a61065 c0dc5f7a 408bb54c 3f52b7db 3f2c0167 3dd43f83 00000001 bf9c68f4 bec25c5b -> 3f2828b3 c063b65f bf497c34 3f28edaa 434f157d 433476c0", + "c20818a4 c1c20596 c14557bc 3f2774b5 3f150cd9 bf319ae2 3f909984 3f1f8819 3ca3d70a be0bdfe1 4158e341 3f99825c bfe2ce69 3e0a51c1 3ec38500 40f93dcc c04f8454 bf2ae330 40bfcef6 40a84d28 3ffc4c42 c00ecca4 3f828059 00000002 3f284ef5 bea22f6d -> bd38dd8f bd849180 bcd63ebe 3f1550c8 3efd091b 00000000", + "40e4e7a0 3de85a00 c16f228c 3e9d2769 bfa67276 3eee593c 3fd76c7e 3f0946eb 3f000000 3b7e603b 4195c3c6 3ea330f3 bf8bcb23 3e0c5585 40e99652 c0076416 406db390 bf652068 40e42f12 bfd9d25c bffaa28f c03cffe0 3ff9485a 00000002 3fb19775 be861b4b -> bb9e52f2 bb1fdb27 bc0d9678 3f1d341e 39f2d25c 00000000", + "c12fd5a4 c23dc797 c1d74b84 3db68ec3 3cbc0525 4028e855 402965de 3e9b12f0 3ca3d70a bc47ea8c 402c4f38 bf9890cd bfb66cc2 bfddba01 be6bd160 c0a01cc0 407d1920 c06e6378 c0b6da56 40e5edd2 bfc5ee15 c012f14e bec72e02 00000005 bf9138c0 3f36190a -> bd36f7b6 3d94f02b bbbb8fed 3ff80a47 3c730758 00000000", + "c21ddcbc c1e6aaf7 4098efa8 be97b15f bf3a05de 3efabbeb 3f6f3c3e 3dc2d971 3dcccccd bf0f9332 3cb32aa9 bfecdc9d 3f050341 3fc51fe2 c061a7dc be62aaa0 bec53120 c0c967f0 3f13b620 4085919c bf4042d9 40191edb 3e50d584 00000007 3fab4191 3f9667c0 -> c45582bd 4389a300 442c59ec 40f682a6 4ab755a6 46a63c7e", + "c0c47f30 c223a06f 421b7a10 3c2d6bc5 3ffbfd9a bfd567ff 405b402c 3fd89187 3f000000 bb0822f2 40f38feb 3f0a24e8 3d8e8e61 be91630e 409ae534 c011e10c c06277ec 408a2708 40a01eae c080373d 4037009e bec93741 c037ce04 00000007 3f194c50 be0a4fca -> 3ac60383 bba723f6 3ac43e0f 3f35c31f 38edc599 00000000", + "413f6a34 c120926c 41dcef90 3f23170d 3f069923 c0293e46 406a6eec 3fe480c2 3f000000 baca1979 40bb4442 bf6c30fd bf36810f bee0483c c0ced2a7 c0facd1d c0c4255b c0da80c2 c049a476 4081288e c0280bb6 c0194d4d 3e505c72 00000007 c0378306 bf8d77e8 -> 3ea31cb7 bf946d8a bef6020f 400f9850 42a75bd5 435ce415", + "c20a41cf 4205b35c c23b455c 3e9eae4a 3f972942 bf4df0d1 3fbdd3a6 3e55e2a0 3dcccccd 3d5e433a 3cac6360 3edcb820 3edd7c83 bef48cfd c0361ab8 40e5d3a2 bfedddd4 40ebcc7a be9de2d0 40ce56d6 be06c646 403bc73b c03efbbc 00000004 c01cb000 3ee75b27 -> bfbd0898 bfd4dfaa 3fea6822 3fc21a1b 4632b421 43645608", + "415b0504 c1de0a10 4087ce80 3f1709c5 4026c2b5 3f26763d 4031eee0 3fc3f2a7 3ca3d70a bc0e100b 3ec325e5 bf3283a0 bf26fddd 3f892c67 c0a20084 4096a308 40ddfcc6 4017df28 c0b81396 c0cb05d7 3f247207 c0336b22 3fbfa767 00000008 3f76bb68 3f82966d -> 3d0e5fbe bbfb8c9e bdffffde 40ca42ac 3db51604 00000000", + "c1ac6922 41e4f3f8 c122175c 3e516572 3f78e05a bf2b4fe8 3f9951d8 3e5fe343 00000000 3df982ea 402a64b4 3f73f622 bf4f3807 3dee7f30 c056b30c 40d3af5e c0d3547e c0cf4ebf 408e2e38 409f4d06 3fad72a4 3ff0ff42 3f33b0b1 00000006 3f126d28 bf805d91 -> beacba9b be8a490d 3d413b69 be554f1c 3f2f7c31 00000000", + "c227cdee c22b288e c22ffc98 3e57b3f7 be0c7768 bf8c2390 3fa8800e 3ec5b832 3f000000 bee0f9b0 3f625952 bf9b73e0 bf32fd3b 3f6f6e3c 3f861d00 40fdf8f0 407b45ec 40cea606 c0a35256 bf7ae098 3f500c6d bf8b3e35 bf871af1 00000008 c01168a4 3f1b5d8a -> 41392480 401584db 410b56ef 3fec19c9 476981bf 46a1105c", + "c22e3468 c1c04c8d c014f7d0 3e3f1313 be239c61 3e4f3dca 3edef6d8 3f96012c 3dcccccd 3fbcde70 40d7cf38 3ef39094 bf71843c 40040b09 40336014 3fb4ba10 bfc915b0 3db2a480 409b1cd8 40fde7c4 be0577a3 c0244f38 3f039ec2 00000007 c000022d bf81e8fc -> c22c5549 c31c8114 c2bbb167 3f5c9958 492fcc95 43f8c68b", + "c2447810 4235772e c2173d03 be3cef78 3fa2ba2b 3c8e4325 3fa802e5 3fb235e1 3ca3d70a c073b0a2 40478c4a 4018cb3a bfc0b161 3f0aba7e c0321c46 c03cb964 c003ee88 40441abc c0af0b9c 40900ec4 3fd6d952 3f932572 3f81d04a 00000008 4006b040 3ea2f733 -> 3fd099b9 412f7b82 3fb0d05f 3faa0bfe 46e08848 417f0220", + "c21cdce3 c240f969 41c8fa7c bf92c581 3e685eeb 3bc5fea6 3f998197 3fc24eb2 3ca3d70a bb07b0c0 4258d170 3e83898f 3f239a4c bed59347 c0cb4615 3f46d0b0 40fde91c c097785c 3ff4de30 40dc575e c017d84f bf9f0930 bfdd2fb5 00000001 401ee780 beea04cb -> 3876ba0a 3b249fe4 3b883197 3f31790b 38e34cdd 00000000", + "4214903c 4219c4fe c232eaa1 3ef824f3 3f04b49b be8857ee 3f90e649 3f3f83fc 3f000000 be42cc13 3e4b1e83 bfdfcf25 be5dafec bf0abf0a c0a1cea2 c04b2510 405c75e8 bf9f2aa0 c0d7eb07 c0c2de22 4013fff5 3ece6993 c018ef9a 00000005 bf4ae5ce bf62bc63 -> 3eefc2a4 be9b5204 bf20162e 3f00631e 42da693a 418c5907", + "41a75624 42280c8e 420a1a26 bf850577 3ee2f51b bd3b9ee2 3fa59516 3ea6c614 3f000000 3adf7ccf 3c76aa1a bfc5c2ef bfddbdea bfe9dc41 40daa5e4 40e3d2ac 40b46bc0 c0dd8598 3fc37868 3fbd6f48 40322650 3fe823be 3e962ea2 00000007 bfe0ba46 be7a40d2 -> 3ca3f8d1 3cf4085e 3ccc9335 3e4ceccb 401fab4d 3e24e374", + "40b951a8 41aac368 422c6000 40129fd4 3d12247f bfcdc10f 4034b830 3f9eeff4 3ca3d70a bce1f9d7 408ca779 3e665833 3e7a4d30 bfbce18e c0a0199a c0c42b0d bffbe200 402cf560 3ff24160 c0e0857d beaca4e4 401bc9a2 3ede87be 00000003 3fc2d6f3 be0f5e3c -> 3b9c6e4a 3d9e7c8a 3981f5e9 3f36764e 3cbd6544 00000000", + "becc4780 40889f28 c206854c bfe71bcd 3e9f9f09 bfb0a158 4013b43f 3f34a9b7 3ca3d70a bb6ac332 3d6b33a3 c030d9c8 3f5e14b4 bf19c4c6 40c6f0c6 c0ea426e 401fee44 40b9a2ca c0af6f1f bfd626bc 40337375 402dd93c 3fe53c10 00000005 3fd9f9b4 bf2de2eb -> 3f0559b2 be47a596 3ddffa08 3f36254f 40936aea 3bc094d8", + "422f0bb8 416c4240 419d471c 3f546161 3d7b094c 40017f58 400e2586 3fd53572 3ca3d70a bad5f983 3ecdd2a3 bf748d09 3fc14b04 3ede0a9e 4040fc30 409d271c 3fd2f460 c09b96e8 c01e9a6e c083bdf4 c03fabc7 3e384c5e 3fed644f 00000003 403452f2 bf65b849 -> bd1f21f0 3c6e65e6 3cd35c15 3f1b3baa 3cf105ad 00000000", + "41ac55dc c191dbdb c1ccb4ce bf63dce9 3f287ec2 bfef5f4c 40143e7f 3fb73551 3dcccccd 3e331722 41637057 bec9000a bf1c0806 bf37dea6 4099bb30 bf0e3be8 408c5b76 bf69c470 404dc2dc 3fe6e950 c02ca89b 4024d173 bff261a4 00000008 c03bdad2 befb4cb8 -> c00f0df8 bf46397e 3fd738a3 3ea946e2 4752893d 450a57ec", + "41c7b1b0 c08f3428 41f7339c be855c02 3c28b277 3fa7483d 3fb97674 3f94daae 3dcccccd c10368a3 3f4ceac8 bc8275b5 bf2414ba be3b4de1 c0215c1a 40e4cd76 40cbdd02 40edf1d6 40ff34fe c050454a bfc3e950 c0210242 c013c3c0 00000007 bf891858 3f685a3d -> 4247b4b8 c244b350 c20d7350 40762b20 473562a1 443b162d", + "c2439162 c228e543 c20df8df 3f8b8281 3fd487bf 3ed54bc9 400ae9de 3fb4aa5b 3dcccccd be68ba18 3e41bb60 bf0a0ec0 3f7ec08c 3cdd03ee 40f6bae6 c0f60db9 3fc866b0 bec0e900 3fef9920 3fe43a70 3fd2ce3a 403e2afe 401e90f8 00000001 4002562d bee308aa -> 3db703fa 3eb4ab4c beb80019 3f79bc4f 42a097a0 3ffa1a79", + "421320a4 c2241b1c 42373c68 bf2075cd 3ea44dad 40019258 40092f77 3f867b89 00000000 bb953739 3ebe5e1b 3fe46f17 be1d7578 3fa1ebe7 403c55fc c05ea4d8 3fcac480 3e8ff640 bc26c200 408a1c24 3f06622b bf99d0af bee9a878 00000002 3fde7643 3f829ffe -> bc39690a bce067f0 bc46b270 3f9a5679 3ca2a31e 00000000", + "41080400 41d637ec c12ccea4 bee4709f be25423a be8ffc3f 3f20cc4f 3f4407b8 3dcccccd bfc211c8 3d67b858 beb3e659 3fa9dd1d c00db66c c007ae2e c08af21e c0c3e66c 40fa9d1a c0d52a08 c0d79d37 c0298ece c007e8b9 3f0f5819 00000005 4006a3b3 3ea668be -> bfe2fa2a c1a19813 41feaf68 3f8ab30d 48e5975c 44235e69", + "41ea520c 423554bc 4229cb46 bf4b0a7b bf4f6913 c02e6470 4044290f 3f917c13 3dcccccd bbb0d1e0 3d1a61da beaeeb1b 4030aabc 3f391028 3ee13e60 408408c0 40ccac4a c0c44b0e c0a9b29d 3fcd8ae0 c03ad682 c035d735 bf0592c2 00000003 40102bd4 bf9626a7 -> bdbea3ef 3f867c78 3e4d8dc6 3f4435c6 424052e5 3da4ba21", + "410221b8 c10a060c 42390e2e 3f11f4ca bfdd6b31 bd882795 3fe94a5f 3fe0a818 00000000 3d25f222 3c3378ad 3edab8f6 be8df112 3df6e85a bfd7d834 3f823148 3e9cf860 3ff68138 c0cc7e21 c0c85af9 3fd2ad43 3fc5d382 c01e232e 00000005 bf7954c2 bf8a0d73 -> c14a78b9 4103845f c057fe12 3fb442ca 44c6d540 00000000", + "419649fc c241a51e bfb62c00 3ffc3400 bfb21c98 3f3fa4e0 4023f7a8 3eba1013 3dcccccd 3c727512 40208866 c01eb644 bf09b7a5 be84d9ce 3fa67058 bffc8fd0 c0d0b0c6 3f942e18 4072f008 c0ab6ff4 be711294 400e3230 4010c475 00000001 bf1fe14e 3f3414b6 -> bc66e773 3d038a61 3d2eafe0 3f109296 3b9ac6da 00000000", + "c1b2edd6 c1b99b2b 41f0154c 3db93da1 3f8a4853 bfc74a2c 3ff6a073 3e975e66 3dcccccd bbe12db3 408770e8 3f82236a bf94b110 4005000f c0862cf3 3f4cefb0 40ee55e0 4056c1a8 3e9c8a60 40634758 c017e33f 401bb399 bf2a59d1 00000006 3fcc2ff1 bf71f954 -> 3d3faba0 be0e32c3 bdb84b04 3eb769c3 3e274b4a 00000000", + "c1a45ef8 c219ad6b c14dd6f8 be8e40a4 bf7f46bd bf86deae 3fc1ea90 3ff27ddc 3ca3d70a bfbcd6f8 3e9b1dd7 bef6e0c6 3f870a9a bef8a6d7 403dbeb4 bf9ee7b0 bf76f7f0 409cec54 c0fbfcaa c0f01dcd 3feb7f80 4025a4ee c0133ee6 00000007 c0031539 3f14e4e7 -> c1d09605 42132dc6 c0a6f3de 3ff8a2b6 48e5de65 431c197c", + "41ac900c 40efc048 c23f71f8 3f020faf 3e8af1a7 3f021472 3f73d7a3 3fec10c0 3dcccccd bd87feb0 40121016 bfb5aaed bfce7b72 3da9f597 3fff3ef8 c0457a84 3f5f5b30 4060984c 3fcd8f18 40ffde76 be39a45f bfcab762 c0265233 00000008 3f0ad820 3ee403ea -> bd86c735 3db694fa be04a3c2 3fa54d87 3e0fac30 00000000", + "41b2480c 41e7c140 40930ac8 3f9739f8 3db9cdcd bedb4306 3fa55f7f 3fccfebf 3ca3d70a 3fecdbd7 41bf960a beb5d4ca beda53c7 3ebdc061 4081c802 c08c6792 3fbcde80 c0ce2bd6 3f94c750 40d3e0ce 3f675068 bf949da7 40197aff 00000005 bfbb34c7 be3aa7ce -> bf42a57e 4083b8fe 407b8299 3ed8a6be 439abdb1 3ca80fb6", + "c1db83dd 4061bdf0 41dff8f4 bf24f048 3ea0ea7c beb8f821 3f53cec4 3e7c2c40 3dcccccd 3bce21e1 41f1601a c018a39a 3f6c577f 3f319f0e 4080949e c01808ee 4058a120 c08df236 c0b3c680 c0681634 3e962d1e 3e480314 be132509 00000001 403d25ac 3ea8147c -> 3ba1555e 3c37d8c3 3b3016db 3e67cfac 3aaca449 00000000", + "c1d94843 beed3d00 4106e780 bfb468a2 bf6ce940 40035ec4 405db8b8 3fceda33 3f000000 3aa13dca 3d3e0809 bf0f9485 402031a3 bea7fdc6 3f9fbed0 408bd5c2 c076bb0e c060e40e 40e8ea9c bf1266e8 3ebf0759 40070519 c02b29b3 00000008 3f4cabc4 3e607af2 -> bb3c3d20 3c80e377 bbc34a63 3f93c09e 3d0cc930 00000000", + "c22dd7a5 41f46eb0 41140788 bea13bdd c0148929 3f87873a 40256f85 3e154f86 3dcccccd bdb250a0 3f302598 3f65f673 3f125287 bf8d727a 400e3268 40d15c7a 406c154c bf5bde00 40e76fd4 4032928c bfc2c2df bfe36239 be03ce1d 00000001 c0288828 3e5c8ed0 -> 3da46b6d 3e5637e6 be99d29c 3f55104e 3edd2481 00000000", + "c1ac2e5e 42032226 c184335e bf8cd11e bfaf3d4b 3f4ec37c 3ffa54ea 3f90a2f0 3ca3d70a 3ca3e626 3c343112 be92bac3 bf4fdbea 403746f6 beda2e70 bf0d6c30 408cb95a c060ffae 40bc18e2 c0950fea 3fda67ed bfda0de7 bf98a046 00000001 bf55c9f7 be1a9a94 -> bd22f3be be19afeb 3ee7451e 3f6fedb2 433823bb 3db09d16", + "41a81e18 4215ced2 c15e044c 3f275b3f bebd379d bf8b46d0 3fac0080 3f8ac1ab 3ca3d70a bfeeb6a7 40094ee5 3eb6b6fa 3e2ae8d6 3fbdcb51 c0292786 40c0a83c c03b710e c0441efa c08f43b9 c09ea83b becc82ce 3f168d1c 402d87c5 00000001 bf80d21e bf16eada -> 40432b85 3f69be54 bf4eabca 3f7ba26b 4302dc39 3e17bd9c", + "c18074ea 41d60cd8 c115cf98 3e5037ad 3db79ea7 3e0a7d7f 3f9260fd 3fe22915 3f000000 3fbc534b 40862ffc bf89538c 3f9c325e 3f018d4a bf3ed4e8 bf777a30 c03a0970 406e9454 bff51dd0 c02954ee c002717f 4032267d bf39c3c9 00000008 3faaf710 3e40486e -> 4037f41e 3fd0fe22 4052229a 3f76086c 43351852 41a80a1e", + "bffc46e0 3f467080 bfa0e1a0 3d9a5b52 be9270ab 3ff52774 3ff80f6e 3cf9bc60 00000000 4054ea47 41a5090d 3f08aab7 be7c9a1b be73a7d3 3d49e100 40ca0b46 409d1e14 3f0411d0 c0bb73b4 c06dcaca 3f911b80 4010555a bfbe8a51 00000007 bf778ff0 be1b635d -> 4041b15f bf9d6ab8 41048ac0 3e66f4a8 43c1a39d 00000000", + "c20ae504 beeccb80 4244a5ee bd8d3ac0 bee26391 be0299ae 3eee3fca 3dc74147 00000000 3b90f8d4 3c70428b bf9cf86c 3fa6ca69 3fe3bcfd 3f87d8a0 be474400 c0c2dac7 40ce524e 402a3444 c0deb7b6 3ea55deb bf126f52 c03502b3 00000007 3f77e5ba 3f831a89 -> befd0292 3f067cac 3f308159 40b41102 413be292 00000000", + "40f148d8 3fa7b500 40f36488 3f97e39b 3dc14a0e bfb45c45 4010477a 3f51cf1b 3f000000 bf0ca859 422b75a3 bfc59f20 bfb307b6 bf34daa7 40dc4ac4 40c83bc6 c0c71f41 409f3a3e c031099e 3fc08e50 be9779c0 bf890c2a 3fccf6f4 00000008 3fed5a52 3ed91803 -> be18834a bf8edfd2 4020ddf0 3fbf0103 44320a80 4435c9c6", + "41f73a54 c1cfac7a 4209339a 3ed8e695 3e913522 bc887e2d 3f029454 3f8c2d8b 00000000 3da20ab4 3e2e6b4b 3f7445df bfd7cb0c 3f17299d 40c29e12 40ad69c0 404e74dc 3f46c910 3f9dda00 40ee972e 4018c2e8 bf3b32d3 c0261f0a 00000003 4021d5c4 3efaaf25 -> bf2b0460 40111965 bf057306 3f71b0f9 429f1e06 00000000", + "c230ba00 c23f913f c23b614d bec94484 40152eb4 3d6f3f0e 401755cb 3eb45334 00000000 3f6762f1 40c1555a 3f7e2408 c0028c96 3ef1284d 40a5c072 bf373618 40d06054 c044da0a c0d8af2e 40a72e60 3ffaaed2 bf91c44b 3f883d5d 00000008 bfd64a97 3edd4040 -> 3f168e85 3e6f6218 bf954401 3fa3fb7c 43dd8231 00000000", + "4001cee0 c22517e4 41305d84 bf030fd2 3d9b14b7 3da4c4c0 3f35e1ed 3fef0483 3dcccccd 3ab33176 42bddaff bf2f2f28 bf81dce1 3f31abc1 c08df16a 40d723a2 bfde057c 403be448 406fa384 40fff258 3fd9c322 bfcf8538 3ffd33c4 00000007 c02274ea 3ea77d3f -> 3b7b136a bb8c52aa bb22bb35 3f8822c7 3c1e32f6 00000000", + "420cf0f8 c22048be c2390d07 3e00e24c bd0c4d74 3ed7bc10 3eed030e 3f8bb3ed 3ca3d70a 3f841c85 3fdd8aae bfd5db97 bfcbea89 3f7b098b 40eebae8 c007f0d8 bf813054 c0bf3dcb 40b68838 40910a26 40397c29 bf661dd7 403d800c 00000002 bf791000 bf323e3e -> 40078a08 be148fc8 3ed38f14 3edacbb6 4239099a 00000000", + "40746710 c1fb3287 bf5ce3c0 bfbc2d12 bf5b1eb5 bf138c28 3fe71c67 3ef13e39 3ca3d70a bf6739cd 411da0c0 3f08e726 3f38b569 c01a38f7 40a92822 c03232c4 beb49d60 4020df10 3f9590e8 c0e5b090 c00448ce 3e897f6e bfbfd22f 00000002 c018d2e6 bf8f96b5 -> 40fae6a5 bfd602c8 3fa4d747 bcaa4da0 43a4eb4f 3d8a2fca", + "c1f4273f bfeed700 c1b33f21 be858480 bfe697cf 3e87f297 3ff196c1 3ef511b2 3dcccccd c0f03c1f 3e5149d9 3ed9151a 4031776c be81e7fb 400f6cdc 407d5244 bfad3ffc 40313ec0 3fd4a270 bed1d170 bf94c083 be4e3a32 40326c3f 00000004 c0052040 3ef221cd -> bf6e0230 42242f20 41d51e46 3f8bab8b 4990cc6c 4720ee63", + "c1fdbb19 41d28878 42236faa 3f8bf76e bf125524 c005da28 401ef97e 3f0e915d 3dcccccd be8f9d15 40ac8504 bf8fe3b1 3e7e7e2a bf90a6b5 c00b23a0 3e040900 bddf8800 be2dc0a0 c0eaa6c0 40f74b56 3f996baa 3ece0c6f 403edf91 00000002 40156d15 bec7590c -> bebc7521 bd4fe418 3e838c32 3f2997d9 4154305c 3f20e6e1", + "4233c5d0 c1fa4197 c246a55b 3fa7d7c8 bf95e510 3ed24919 3fe77140 3e0c3942 3ca3d70a bfa36b9e 4253025f 3f00b7b4 c025eeff 3e885fe4 4008572c 3fcbbd28 c0729fdc 4038fd48 c091dc28 3f670670 bf4cd0f2 3fa43abf bf0cb1e3 00000001 bf378ccc 3f19e0ae -> 3f5d9067 3e101a13 bfc94457 3f751eb8 42799f1d 00000000", + "40467c20 c1158f5c bdbf8200 bf82d1e1 3f2b89b1 3f0f5481 3fbb831c 3f9a7b45 3dcccccd 3f06abd8 3d316d3a 3f431ea2 3e938cdb bff9a866 3fc84ae0 bcfde100 40aa10c8 40b3a468 40ab1682 40ef5ba0 3ff6c5d0 3fd1d5ad 3fe3913d 00000001 c0102718 bf95b213 -> c1853667 c0c40233 42315bd7 3f32d792 460039d8 42cf6c9f", + "c22b099c 420535cc 421c43f4 3e824856 bf4695db bfb8c57f 3fd648a7 3f48f2fc 3ca3d70a bfde71c7 3e096abc be8fb255 bef95d2d be369972 bf7d5200 408712dc 401a6730 c05a3938 40fd5436 c011dfc2 bee78393 4029421c bfde6e7a 00000008 40053a37 be559d3c -> c01eb491 c00ba064 c014ba43 3f42c1b5 4752548d 42105219", + "3eacb900 c1656ac8 41df234c 3f90934f bfb9e5a2 3eaa3271 3ff82106 3e0d12d7 3f000000 3e731932 406e3dba bf89e6dd 400e1969 3f7af565 c090867a 40b0165c befc3710 3feff8d0 40c58432 3f916828 3fd2ad37 3fa88a40 3f69ee30 00000005 bfb6d3ec bf06e7c3 -> 3fa86cca 3f8db435 bf2940ce bbffac98 4191c038 411a1690", + "c17675f8 423de1ce c1ade794 3ee7205d bf35bc83 402b8642 4035d91e 3fcf2ae4 3ca3d70a 3ea8bc56 3df8ace5 bf3aff6d bf48dc05 3ffc4a10 3f13c300 c0bba0c0 c05d2534 bf2934a8 c0eb094a bfdf37ec 3fd73da3 bf837353 be4e2e5a 00000007 bf257d20 bf152aa3 -> 3facf8d8 3f95249a c04f20b8 3f432b62 4406f9f2 3f87f013", + "c1f60fb0 c0828048 c23d74c6 3e09e1bd 3e9a4f96 bf6f33c4 3f7e74d3 3e192b89 3ca3d70a c0da9fd4 3cbe2437 bdccd08e 4009e073 3fec0ead 40fd27bc c03347a4 40dc938e 401943e4 40a6dd4c 40f554e0 3ffc2259 bfcee7e5 bfc79531 00000002 bfae25e3 3ec1f232 -> c20daff4 43960a11 4382c584 3f9e106f 4afa47c7 449dfdd9", + "c21782da c1cfa990 41e9a100 bf7f1cdd 3f18ec94 c00bb281 401f1efb 3f2d3b48 3ca3d70a 3cd139a7 41088dd9 bf11bf1b c01700de 3fdf8f61 bf214370 407d2a0c c00c3be6 bfd555dc 407d2470 bf89bf9c c00ae3ac c0153a80 3fa1fcec 00000008 bf900ad0 3f591078 -> 3c0da248 bcd9fa20 bd02d9aa 4076220d 3e284629 00000000", + "41598db4 407d6c70 c22ab723 3f2f5771 3ffd68b5 3fc4da70 4033c154 3ed6e9cc 3f000000 bba34626 41b36d0b 3fccc02c befb159b bed8270a bea6a1b0 bfb28b10 c0fbc5f8 c09e4a30 3fc8d718 4082e77c 40027da8 bdf6d107 40127020 00000007 c015736e 3f90afdc -> 3f907e32 3d7ba320 407d65fa 40d73ae2 42b4d8f5 43286cc8", + "c1eda4fa c120827c c144c7f0 bd68b357 3f8ff218 bfe638fc 4012be40 3fdab1fb 3dcccccd c077a003 409dee32 bf51b051 3fdb117f 3f3cbcdc 40987bd6 c0b75db4 c066d11a 40e42180 40d38276 3fcc0ac8 3fc3347a c0222e3a 3ffdf249 00000006 3fc7d313 bf1715d3 -> 3f946570 c0339098 40fd040c 3e4695fc 46693784 44363433", + "41d7ac00 4224c4a0 c20ad69d 3f349a29 c0069414 bf89bfbd 401dc6f2 3ea86d39 00000000 bee63c74 404d300b beafbcb1 4035abce 3f2af444 c0a4954c c095fa72 c08f32f0 c0df5f7c c0cff5c8 40815338 bf13170d 3fb7dabf 3f98a87e 00000004 3f9c06f6 3f8ac415 -> 405dc1b2 3e8a037a 3f45ace0 4031fe46 425f75d6 00000000", + "41adc838 c1ac282e c1ee25cc 3ee586b2 3f3c86c3 bfdfa635 3ffb587c 3f4333f2 3ca3d70a 401e3cee 40a17629 3f375e56 3f9df7c3 3ed6f8c1 40f03cee 40f69590 408db662 40e14096 408a7234 40a41b64 3ed36d57 3f7d1d54 401a17bf 00000003 c037d2b0 3f807ad2 -> c1df82f0 413bb820 c0986653 3fe03fba 46cee0a6 4219b155", + "41b960e0 41d4221c 416b0170 3fafdd80 beb766c4 3ff36ff0 4019ccaa 3e98154b 3dcccccd 3d070e73 40379194 c0090a6d bd3219af bfee05b6 3f8f9ad0 3f11aef0 c095179e 3fa5be40 40258a58 c0d44136 c01ccd47 bed6c52e bfeba6c6 00000007 bfc7e41f 3f6a724d -> beb7136e 3ebd6c4c 3f10366c 408eb558 414bc86e 3e25cce1", + "42294be0 c134b8f0 bd151400 bf1064eb bed09957 40057fbf 400cbaee 3e8ab696 00000000 c0da59bc 3fdc1971 bf1e563d 3f1285ed be984f84 bf804950 40e67fe8 3fc95678 c07c3b2e 406aac8c 3fd87bd0 3e49cf20 3fae8939 3fa358a3 00000004 bfcc2f12 bf8a3f1f -> 400bbd40 c23d165c 421087d3 be1dcb38 471350a6 00000000", + "c1216d1c 41aeb718 c1526560 be95e283 3f461880 3f80e248 3fa9b84f 3f917d41 3ca3d70a be45c4c6 41108b3d bf4f9f11 bfb904db bf984429 c0e4461c 40a3edce 408357e8 40c27dae 40d3560e 404be218 c01bc57c 3e97cced 402a1c99 00000001 bf5073b4 bf90f350 -> 3db2d706 3e600727 be80b885 3f19376a 3f2915a2 00000000", + "c203f577 42364fcc c18c7174 bf31a700 3f66eeb6 3fc343ae 40026539 3fabb527 3dcccccd be0b0d7e 3ce1ca82 3f80790f 3e427746 bfd26ea5 c019114e c0cadc62 40c15c36 40eac0b6 40a6e55c 40cb9ae2 4005a81b 40388d92 403d7aaa 00000002 bf6c257e 3f0d0aa6 -> 408f9d3f 3f59a114 c0f6b52e 3f7a1baa 44f7f8da 4283a852", + "c0d639d0 3f958360 c1ac930c bebc9f73 bed4b607 3e148cd4 3f133d90 3d80657e 3ca3d70a 408444d0 3de262e5 c00ed154 bf3bca69 bf92ea94 40d55dc8 c0df3bc0 40bb20f8 c06b9162 c050a92e 401df2c0 3e43b721 3f62ede7 40351177 00000004 403a2827 3e98a699 -> 42244f14 4185494c 422bb1e4 3f8bb854 49f64fc0 432e4f5a", + "bfbf7aa0 c1b7f8f7 c1007e98 3e8ee766 bf63b00e be61d038 3f7c4a59 3e8d5bdf 3dcccccd bdbde0d0 408b1b5d 3f54d221 3f2e31ee bf8761e9 c02f8390 402b3a44 c0b25b03 c0a41f82 40b9c89c 3fb51840 3f349ef1 3fbca3eb 40257d24 00000006 bf14e574 bdddb3eb -> 3d04c3d8 bd482408 3d0dd9d5 3f2bf7d4 3e89e5d9 00000000", + "41c8f5ac c18813ca 42359962 be3c07bf be771792 3eb7c397 3ef090ba 3fe58e47 00000000 3e47e434 3fa33370 c00aadf7 beae0e57 bfdc87ba 40c908fc 40e47d4a c0b375fa 40f6bc68 4098ca7e c0bbe7ac c02f17ed 3f5d52ed c03e0c40 00000005 bf5f43e3 3f975806 -> be4bcc74 bf089912 bf85faa0 4073e0d0 40808138 00000000", + "424660b6 415a6198 421fc8b4 3f878036 3f12a37c c012ed0e 402cf115 3e617b2e 3f000000 bce50c74 3eeb9752 3e80a002 3ea547b4 beadc5ea c09654e0 409fa95a bf8fc4ec 407e35a4 409671fa c0d0667c 4023a237 3f6a5c44 c02789b8 00000001 3fd95a5e be725210 -> bda85bea 3d37cb87 3ceeed3c 3f3a93de 3e5260ec 00000000", + "41a1647c 41b63494 c2106df9 3f6804c5 bf4d99e6 bfd44635 40054ba3 3fbb6428 3ca3d70a c0513461 425cf399 3c56395a 3fd0e63c bfbaf301 c0fe7d97 3f13b8f0 40b8d8e4 c0e6a2f0 c0adda50 40f00afe 40040e75 c0324bbc bf2ad2cb 00000005 c0117b96 3f7dfd3a -> 42174b6f c33e57a9 c34dc06e 405e9e5a 48f91204 44591766", + "c21ce6e8 c1bca152 c1d508ae bfa161ee 3fd0b89b bfe33641 40346a96 3f7ceaa4 3dcccccd bd7e12d5 3ef3c2ad 400b6112 bead4de9 be075537 c068e484 402285d8 c0fe4b5a 40f9dac0 40ba9ec0 c0c19a76 c03441b8 3eee70a2 bfd8e614 00000007 c020a6a0 be5bf82e -> be42993d 3da93c9a 3e1faebb 3f420456 4199690a 3f6ae662", + "c2224066 c1af1196 419b89d8 bf784bda 3f25228e bf4742cd 3fb3526f 3f510742 00000000 3d8843d5 4005a01b bec472d9 bfbb5175 3f60f79f 40add0f4 3fec3f68 beb24c20 40af1e90 40cfdabe 402d4a70 3e9e6fd8 3fa85078 3f13e66a 00000007 c02965aa 3eb2a887 -> bd9bfb97 bd96161f bd82e061 3fa29121 42437d84 00000000", + "c1d676b8 c13450c0 c2261d0a bef31efd 3fc87eec 3ef0b11b 3fd9f9e3 3fce80fd 00000000 410a58d7 42871523 bc32e252 bf6ad891 bf8fe30c 409c5f42 bf8cd934 c06ac8c4 bf83891c 4037cab8 40e062f6 bf53c6d8 3fb0e675 bf47c704 00000003 bf8d7fd8 be2cecee -> 4079b264 4127fe0a c103f9a5 3ee02aca 44d78dec 00000000", + "c1755a34 41f6cd7c c20ee96b 3f25cd57 bf37e706 bd4de917 3f77f195 3fb52e6c 00000000 c0eb5e51 4021f711 3fbb3978 be9b6155 3fec1955 c0e36714 bf3307b8 c0564386 bf5e7740 40e06d90 c0a0c9f8 3fcb6603 3fc48c30 bf8da881 00000004 c03b7132 bed9b901 -> 42819343 c060c740 c2447eee 3ee4d0de 476de4ba 3c6c7f20", + "c1730440 415f9b7c c20f427b 3f985376 3ec1efab 3fdd63df 4014f45d 3ff862b8 3dcccccd bd4c8e33 4032861d bfdaef8a bfa75c39 bfea9eab 409681b6 c00e5e3c bf88fe00 4073d1b0 c0c5bb67 c098ef3a 400ee8ed 3fa4dc03 bf8d31b4 00000004 bfafe4e2 3f57c168 -> be4b0186 3d095dac 3da105db 400ea3ad 3f6c5a95 00000000", + "c146f5dc 3f8a6800 4184164c 3f6acaaa bfb0fe52 bff546b1 40244b6d 3fcee1ee 3ca3d70a bff011ca 4063b010 3fb474e2 3f9aa3f1 400aaf07 40f1c8a2 40323af4 407501bc bee50350 40db6f92 c0fdb14a 3fa09ba0 3fedac24 3fc8ecbc 00000006 402478e5 3f513bc7 -> 41ea80d4 c2581071 c10fcb00 402586ab 49865fb0 4510428b", + "c1d4a3c2 41e980e8 c203bcaf bd301c4b bd7ba3ba be5ed535 3e6bb111 3fd6df9f 00000000 3acd1104 3e1872f7 bf370aec bc570408 3fdd0b41 c023e018 bfd6ac74 bf110158 c0e544c0 c08ac5e4 3f6441a0 bfd7d4f0 bfd06e83 bf5107ca 00000004 3fa6187d bee02c39 -> bc7d3db4 3a437ef8 3d36c523 3edca53e 3c0eb56e 00000000", + "415e9658 40a013e0 3fc57180 3f2699ee bf7211e6 40069a7d 401958c1 3ece4e1b 00000000 bab33a04 3c639795 bf32ad7f 4013b460 3f91931f c0efb5fc 40ff278a 40de2e44 c09e1e9c c0da67df 40f2c4be 3f47d353 3ec75bc9 bf5d9a2c 00000004 bfe03272 be134208 -> 3d125aad bde91169 bd53cc84 3f3224de 3ec05419 00000000", + "c16e83d0 42042352 c122c750 bf95d12d bfeed5e9 3fd81749 40319c75 3f94518e 00000000 bb347d4e 3fcecbd3 bf836979 bf2cac70 3f87ad7b be164880 c0ea4e73 40dfec10 40e2891c 408646a0 bfeef8ac bf413682 3ff61851 bf5b9887 00000002 bf66d6dc 3eb2e62e -> 3b31deaa 3bb9909e 3bed17a5 3f1e6015 39918001 00000000", + "42269fc2 c1711870 c1f91a4d 3f9c026b 3f4e398c c01c803c 40364e39 3fd78c82 00000000 3fa31418 40c3cc5a 3f67b4d7 3f8b7f43 3f8faedb 407e4158 c0b4a6be 40e450ac c0a78f4e bfd9a92c 3e14d080 bf122cd0 403bc438 c02f910d 00000006 bf9087f6 3f1033d0 -> 403a37fc c02ff588 bd9022da 3fca2654 42fb2282 00000000", + "42242ee2 41bba57c c216fb90 bf0bc2b9 bf9d73b9 be8b34f1 3fb17c26 3f2e34b0 3ca3d70a c027ca51 41cf1d93 becbe7fc bf2b2fe1 3e35b269 405d1274 bf54a9d0 400cd6f0 c0cbf4ae 4007cc20 bf614268 c035d4ee 400fb104 401ca604 00000003 40310264 3eef716a -> bd642200 40708dae 4155d8a3 3fb3fdf5 45cfa8e8 4003618a", + "4234fbfc c2122ff2 c1beddcf bf846681 3fa0c25c 3f820b44 40027e10 3f9a8d3a 3dcccccd 3b3ce2bf 3c7525e2 bfac5028 3f987c0c 3fffcf05 c0eb7841 402c6edc bfec0648 3fbf2ce8 c0c8c9ea 3fa28838 be8f0921 befce5e2 3f7cc1fa 00000005 bf3d522c bec87b61 -> 3dc3bcdc bdbabd2f be12751d 3f0419d6 4024b9a0 00000000", + "4104c9f8 41a5b7fc 4143b1e0 3ccd6849 3e3b9ce8 bf132247 3f1ee2b6 3f582a4b 3ca3d70a bd1b6c92 3fe42ad2 3fd088bc bed718e4 bfea25ae 3fca08b8 40e55822 400d8458 c0aaa5dc c079e134 c0ed0298 bf8448e8 c00fde83 3e933a6c 00000005 c03dbdf8 3ecd25ba -> be227229 3d45882e bd31c577 3fa67293 41506870 00000000", + "c2415e3f 420a58f4 c15e6b38 3fbabbe5 bee0d915 3db67f44 3fc44699 3eba4314 3ca3d70a bc2e36cf 3c6737a0 3ffac37f 3faa6173 bfb48235 c02c0b1c 3ff38700 c0d3760e 40f3e2dc 3ed2dd80 c0bee1e9 3fe665af 402c1ed1 402d0934 00000003 402ecc9f 3f026d94 -> bffca0ee bfa8a864 3fb24e56 3fa6ee44 4411ca7b 3de29426", + "41311d5c 42476422 4175b460 3e06c431 3e912ee1 3e18eedf 3eb162cc 3f883ca7 00000000 be71d6b9 3ed724da bf0cdc75 3d8dab3d 3d75dd7d 404a2910 c0fba821 40b0fbe6 c0e93675 40b9daf0 c0d310d3 3ff00ac3 bf9ebf7b 4001bfa3 00000005 3f8eb6b0 3f958453 -> 40d5d4da bcbc7b40 c0148ef2 409cc645 42c9293e 00000000", + "41849800 c188d01c 41c72b04 bdd28ae4 beedfee5 bf837164 3f90e18e 3f3f55f0 00000000 3b0d2fc8 4236f87d bf39e059 3f2e8b81 3f74e94f 409bab14 4004ea5c c0dfa028 3f15b8f0 40bd0658 40f00d4c c03d5e5d 3fd6171b 3f14477a 00000008 4023bcd8 beb3a626 -> 3c1191c1 bb1feb74 3c0b933a 3f428fb6 3da36fdc 00000000", + "422b6520 41258304 c0839c80 bf7be9f9 bf5e6f5d be832d17 3fb0ec81 3db71d93 3f000000 bab888e5 3d2777da bffc2caa 3fcdc7ff bfbf07b1 bfad3168 406be260 c0b132a1 3f177070 3f3a7250 c03e6f00 402b13e6 403e9444 403f1752 00000005 3fc9b537 3e80067b -> ba77e11a 3bb8a89f bae78866 3f9248c4 3e00dfe5 3aeaa69b", + "41d93ec4 422da77c c2100d66 3f52fd44 3febeba3 3fa7c7f0 401c2757 3fd15b5a 3ca3d70a c08b04fe 3c9ab5f4 3f64b957 3f00166b 3e8499d1 c0318716 c0d2f8c4 c03076c4 40aaf1a8 bee4d250 c01196bc bf93badc bfc7a3c4 401329e9 00000003 4038f474 bf67d4d5 -> c56ce0f4 c5053dc2 c4891c8e 3f0ccf21 4d38aa8b 48af23ac", + "c1929f8e 41790590 bd960400 401c4ee9 bef7d899 bf905fa8 40316b2a 3ec68420 3dcccccd 3e2351d4 3d8250a0 bf11a5ca be6aff5d 3e360e5b 3fb5bef8 406b31b0 bf6c5108 be5bcca0 3f9523a8 be3fd840 bfbe3742 3f6faee8 bf6227f9 00000004 3fd683e2 3f187ba5 -> 40594256 3f654700 bed6cbe8 3f9fc85b 455dd8d8 43679f29", + "40653cc0 41fa519c c2431b4d 3eec3129 3fc4bc98 bf3584e5 3fe13e56 3e87c466 3ca3d70a 4047c965 3d3a6a63 bf948db3 bf1fca65 bf84aa3d 40f42854 c0860ed1 3fb75a08 40ed70ea 40a2e562 3fa437d0 bfe25dd3 c0025cd0 3e6a13b4 00000007 bf12ee58 3f7ada76 -> c363268b c2e25afd c34d34a0 40995dd9 48bcadde 42ffb11b", + "42060ba2 c2181a78 41e2e484 3f9298b5 bfd5ea1c bf6b356e 4048300d 3fe741dc 3f000000 3b27c028 423a8cc6 bec5937d 3f22c857 3f807614 3fe5bac8 40d1014c 40e06ba6 c034bf4c 40a3aa92 40217fb8 40396fc4 3df08d77 3fb28b80 00000003 40099c68 bf32bfca -> bb131d2c 3b94b2a7 bb43e70f 3f9c4e3e 3b4883f5 3c04918b", + "41d5de38 c1ada306 42039892 bfdd6bd1 bfaa73dd bee8d71e 403bc376 3fb43217 3f000000 3a946acd 3f963133 be85f94a becba2e1 be643b88 c0aa4450 bfdf4a84 40c9c04e c0131930 bf47dfe0 3f835170 bf538f19 be8d9115 40279934 00000007 3f031cfa be896c82 -> 3a026a39 39981093 ba5dbea0 3e3ebb35 3836a673 00000000", + "4195aa60 41b06974 c21ceea8 3f58b126 bf4c74e6 bf2c6c1f 3fad0ed5 3d97abcd 3dcccccd be1939cb 41426600 3f2ab0e4 3f134efd bf4d326f 3f79a300 c07f9092 4011104c c0d31088 40b2fae8 c0513bd8 c03c3727 40211be6 bfcb09c4 00000005 3f7ce6ae bf1e3a1b -> 3d4b7df4 be7b82fa bdfd8add 3ed9abbe 40062b0e 3c839832", + "41afca98 c0f0ba90 c15c98ac 3ed180bb 3f8070f1 3fdea70b 4012bfa5 3ef96064 3f000000 bb40db89 3f0a9a78 3e08c4f6 3f662756 bf9ec6fa 40fe6132 c093367a 3fbd2668 40305d14 c0dd1a7e c0a512be 3efa9ec6 3e6f1752 bfc093cd 00000004 bf844966 3eada52d -> 3a2173c0 ba484745 bbc58f13 3f243750 3ae80b11 00000000", + "41d094bc c202b819 c201aa2a bf7db73a 3f9b96e6 3f7d3b07 3fed5833 3ff07d68 00000000 bfd4b274 3fa8aeee bf09f985 3f958589 bcbeb2bc bf1afd58 c0976a28 c0edfad8 40ed87b2 beef1f80 40fb0eac bff86753 be018fb7 bf38c8ab 00000007 bf9c384c bf4064ae -> 40a21cf9 c07ba750 c0dcf284 3d2b8aaa 441ce254 00000000", + "c1ae9742 411b1700 c20d8743 bfda1baa bf8340a1 3fd19fa0 404a5212 3f95c42b 3f000000 4035279f 4291afee bff4e053 3f60a32b bed1ffd7 c06cb2a4 40f5c9ec 3e754840 3ec956a0 c01b3976 40dc8798 3f58755c 3f2d2388 3ef062db 00000002 bfe74772 3ec94537 -> 408605d2 41007456 c03fc0f4 3f34cb03 447e7cb4 44fa7761", + "c216ee85 c127f310 c1bb144f 3eb77b33 bfb32866 3ea24baf 3fbd55d5 3eb3f30c 00000000 40eb80bf 413c5bcc 3da00a8c 3f3c7390 bf5c0da6 c09c9d27 c083e545 c0664168 c0ceeafa c0d07cc7 40282d48 beb600df c02401e0 40308e26 00000005 40189521 3f8e03e2 -> c410da48 41c1ab28 4245c80d 406bcc9f 4a74ac64 00000000", + "3df8a600 41598410 42434180 3e827a58 be43e9b8 be55a288 3ed2928c 3fc27f6f 3ca3d70a be245184 41cd0868 3f34c621 3d445a60 bfce4ebc be93ea70 bd493980 bfb703bc bbadf400 40aea938 40dd1e1e 3fc4f850 c0152028 bf022156 00000004 bfef5367 3f97db24 -> 3ecd1490 bffaec2f 3e0b1778 405fa670 4244231a 00000000", + "41bdbcf4 41622840 c1ee87b1 3fa4e545 3fcf23cd be2dcbe8 400c034d 3f8fc47b 3dcccccd 3e161f66 42619d37 3eb3b58e 3f5f2465 401dcfcd 40344a08 4088201a c0b31977 c0cd9128 c0b95c18 40d06f64 bfd5894c 402ece16 bf7b814f 00000001 3f14f412 bee5526e -> beb98b0a 3d0c16dd 3d0af233 3ebf0216 3f6e13dd 00000000", + "c0ae7b30 c21d3785 c215d66e bf0fa51c 3f8fb861 3f431a42 3fea596c 3f398b34 3f000000 3f114470 3d345583 bf566182 bd9aa56d bf5dcbdf c0b532e2 c042b046 40ae6634 c0522c8e c09bd0e2 c098fdba 3f5cb1a1 c009ff86 40068ad1 00000003 3f9a3b1e bf0dd5a1 -> 4022dc59 bf1f6e19 40620d1c 3f296649 46b1063a 451be93a", + "415b0230 4210e88c 423e04be bd46ba60 3ca434f5 bf353c05 3f3e2716 3fd28434 3ca3d70a bc15ff6f 40747f9a bf010946 3f12bbf8 be6344b0 3f94efe0 40f56e6e bfbf48b8 40a32f82 40f13d52 c03a1644 c00ea8e5 bfa67b49 40133be3 00000001 403798f4 bf3f30f0 -> bc8654aa bc81c89b bc97d886 3f25dee3 3a89058f 00000000", + "4232537e c2118822 40c5caa0 bf8e31ff bfd0dd19 3f06d5c6 4002e188 3dc7c676 3ca3d70a be9d9aac 41171417 bfb892ab 40147a0d be061071 c0f2a4e8 c09788e7 3d808680 c096ec7d 40d0184a 3fabfe40 3f48c058 401a3a79 3eb9126d 00000007 40143588 3f41e7e3 -> 410c3cfe 409a2cba c0ccd5fa 400d21fe 46f550ea 4249a858", + "407f85f0 c1430d68 c146ae30 bf5a17da 3f809e1d beaf8ee2 3faf0aaf 3ea02383 3ca3d70a c0a71a2a 4269582d 3f429f25 bf6e1ac3 bf394a53 3e859220 3f139b00 4083f2a6 bf125398 c0b081b6 c082cbf4 bf13ad7e 3f0a4877 beae2505 00000001 3fb20b39 3f4f7f91 -> c00fa6f3 c0ef51fa 40e1db5d 3f1d8b80 44bda93e 3ebc96a6", + "c21935c3 c2468d13 c2177725 3ee5a1ca 3f464808 3f35170b 3fa3ce23 3e8e405a 3f000000 3c040ee5 419a36df 3f9c325c 3e962edd 3fc9c396 c094bc2c 405512d4 c0fcda26 c0b686c6 40cdcc9c c0b0fe28 be945c82 403cd347 bf99b9f9 00000003 3fb49022 3edec033 -> baa5bcfa 3c2a21bd 3a09f296 3f2cdd9e 3bb96438 00000000", + "c214748d c1dc96cf 420d9cd4 3f094d59 402de007 bd1b4e2c 40340513 3eddc881 3dcccccd 402650d2 3f8ffe67 becf807a bee210ec bd54483c 3fd82798 40778220 3fd19578 c0d2dbfc 3f8da4f8 3f7c65d0 c02ccefd 3e8c0c23 3ff7d0ee 00000005 401e66e2 bf5e4016 -> c2c2db8f 40d3d520 c1c7bd53 3f9c6fe2 48b4b6f6 46d05d5e", + "41f4bbc8 41545bc0 41b08a4c 3f1dd455 bfb98915 3f9eab0d 4012ce84 3f142fba 3f000000 bacab197 3ee544ae 3f0118c2 3f78e0c4 3fd9a379 c0a907da c0137ff8 3fe9d9d0 4049e2d4 c02e415c 40a1faa0 c011e17a bffc1409 3fc915dc 00000004 40252540 bf163326 -> 3c81b888 3cc9ca80 3d01ac32 3e87f86e 3d43603a 3cabb410", + "c1c6c2f9 423ffc58 422dc620 3fad3ff7 3c6cc92a bf0fa80f 3fbb8f01 3faf0e7a 00000000 bede19bb 3fadeba7 c0046d06 3f82d312 bf8e137f 40b3eab0 407d9c04 40e2f1b0 c054887a 406016a0 c09eb08a bfd8e72f 403b9891 be95fc09 00000005 3fec2f24 3f91d46e -> 4088b79c c0a2d686 40bb3c64 409215f7 44db3283 00000000", + "c10ba524 c22f602c c160b040 bea8bd09 3ddeb0d7 be8018a2 3edb0b8a 3fbc02a8 00000000 3e37dd5b 40d74659 3f986756 3fb2228f bd9fbced c0b87494 40509e2c c08a1e22 3f8dc790 c0595346 c0f2d506 3f46e0b2 bfac97dc bf1603eb 00000004 40396822 3f4b22e1 -> 40043b50 bfec2a47 c09b9a66 3ffb9912 439bc3b3 00000000", + "41bb4080 c1987c4b c0e9b150 3fc933c7 3de43987 bf971892 40040060 3f6f971e 3dcccccd bc635862 3d298882 bf67aaf7 400e205b becf2411 c0e63b1d bf9aa8d4 c08a05a1 3f726780 40ad77de 402f57d0 3fea7d3f 3fa8c63d 3fa3413b 00000006 3f9e1aa2 be93c726 -> 3c5b8c21 bd92a1b7 3c1df783 3f173ea7 41317dc2 3abf439e", + "41a12408 40734380 3fa48300 bccd64bd 3e781408 3e9e81a1 3ec9ae0b 3ec2bc4e 00000000 bea0258f 426dad90 c02d4d77 3f70e878 bf60d080 40983a14 4093c36a c080e544 4049929c bff50968 c0d777e7 3f94ca0e 401ff7c0 400a45af 00000005 40390b96 be13e8e9 -> 3e1bfc00 3ece6bd7 bd8e17d5 3f56a3ca 41842974 00000000", + "412b6f40 c1953156 3f41ba80 bfb9da99 3e6951a1 401cc5b6 404235a9 3fe3a835 3dcccccd 3e86da80 3d4e1eb5 4027ba2f bf053e7a 3f9d34e4 c06afaf6 3fd57cd0 c07e8ec0 40a21dd4 3e0bed00 405a1a80 bf0d7fc5 3e770e98 bfec1039 00000002 400bd496 bed78fd7 -> c0666ba0 3f77e0c6 c023292a 3ed58d64 458f5e1a 42496d16" + ] } ] } diff --git a/packages/cultmath/tools/compile-glsl.ps1 b/packages/cultmath/tools/compile-glsl.ps1 index 45929cac..8ec2b82c 100644 --- a/packages/cultmath/tools/compile-glsl.ps1 +++ b/packages/cultmath/tools/compile-glsl.ps1 @@ -29,12 +29,21 @@ if ($Phacelle) { if ($Cycles) { $library += Get-Content -LiteralPath (Join-Path $repoRoot "shaders\CultMath.Cycles.glsl") -Raw } +# A parameter of a struct type the library declares is passed as that struct's constructor with each +# field's zero, read from its declaration (a field may be a struct itself); every other type is its zero. +$structs = @{} +foreach ($declaration in [regex]::Matches($library, '(?s)struct (\w+)\s*\{(.*?)\};')) { + $structs[$declaration.Groups[1].Value] = @([regex]::Matches($declaration.Groups[2].Value, '(?m)^\s*(\w+)\s+\w+\s*;') | ForEach-Object { $_.Groups[1].Value }) +} +function Get-Zero([string]$type) { + if ($structs.ContainsKey($type)) { return "$type($((@($structs[$type] | ForEach-Object { Get-Zero $_ })) -join ', '))" } + if ($type -eq 'int' -or $type -like 'ivec*') { return "$type(0)" } + if ($type -eq 'uint' -or $type -like 'uvec*') { return "$type(0u)" } + return "$type(0.0)" +} $calls = foreach ($match in [regex]::Matches($library, '(?m)^\w+\s+(cultmath_\w+)\s*\(([^)]*)\)')) { $arguments = foreach ($parameter in ($match.Groups[2].Value -split ',')) { - $type = ($parameter.Trim() -split '\s+')[0] - if ($type -eq 'int' -or $type -like 'ivec*') { "$type(0)" } - elseif ($type -eq 'uint' -or $type -like 'uvec*') { "$type(0u)" } - else { "$type(0.0)" } + Get-Zero (($parameter.Trim() -split '\s+')[0]) } " $($match.Groups[1].Value)($($arguments -join ', '));" } diff --git a/packages/cultmath/tools/compile-hlsl.ps1 b/packages/cultmath/tools/compile-hlsl.ps1 new file mode 100644 index 00000000..6e19ec82 --- /dev/null +++ b/packages/cultmath/tools/compile-hlsl.ps1 @@ -0,0 +1,42 @@ +param( + # The HLSL files to compile, each as a lib_6_3 library with its own directory as the include path. + # Default: both copies of CultMath.hlsl (the canonical one and the Unity package's byte copy). + # tools\compile-hlsl.ps1 -ShaderPath is the negative control. + [string[]]$ShaderPath = @(), + # The dxc to run. Default: the x64 install get-dxc.ps1 makes; it is never searched for, so a dxc + # that cannot launch (one built for another architecture) is an error, not a skipped check. + [string]$DxcPath = "" +) + +$ErrorActionPreference = "Stop" + +$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..") +if (-not $DxcPath) { + $DxcPath = Join-Path $repoRoot ".tools\dxc\current\bin\x64\dxc.exe" +} +if (-not $ShaderPath) { + $ShaderPath = @( + (Join-Path $repoRoot "shaders\CultMath.hlsl"), + (Join-Path $repoRoot "unity\org.gamecult.cultmath\Shaders\CultMath.hlsl")) +} +if (-not (Test-Path -LiteralPath $DxcPath)) { + throw "dxc not found (parameter DxcPath). Run tools\get-dxc.ps1 first or pass -DxcPath." +} + +$outputDirectory = Join-Path $repoRoot ".tools\hlsl" +New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null + +$index = 0 +foreach ($shader in $ShaderPath) { + $index++ + if (-not (Test-Path -LiteralPath $shader -PathType Leaf)) { + throw "shader not found (parameter ShaderPath, entry $index)." + } + $shaderFile = Get-Item -LiteralPath $shader + $output = Join-Path $outputDirectory "$index-$($shaderFile.BaseName).dxil" + & $DxcPath -T lib_6_3 -HV 2021 -WX -I $shaderFile.DirectoryName $shaderFile.FullName -Fo $output + if ($LASTEXITCODE -ne 0) { + throw "dxc failed with exit code $LASTEXITCODE (parameter ShaderPath, entry $index)." + } + Write-Host "compiled shader $index of $(@($ShaderPath).Count) (lib_6_3, -HV 2021, -WX)" +} diff --git a/packages/cultmath/tools/compile-hlsl.ps1.meta b/packages/cultmath/tools/compile-hlsl.ps1.meta new file mode 100644 index 00000000..d59a4297 --- /dev/null +++ b/packages/cultmath/tools/compile-hlsl.ps1.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3f6c1e7a9b2d4c58a0e4d7b1c95f2a86 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl index 09616940..21360d07 100644 --- a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl +++ b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.Affine.hlsl @@ -14,7 +14,7 @@ static const float CULTMATH_SNOISE_HESSIAN = 55.642143; // An upper bound on the operator norm of the third-derivative tensor of cultmath_snoise(float3), the // next order after CULTMATH_SNOISE_HESSIAN; provenance in math.Span.cs (SNOISE_THIRD). -static const float CULTMATH_SNOISE_THIRD = 536.9152; +static const float CULTMATH_SNOISE_THIRD = 535.8009; float3 cultmath_af_point(float value) { return float3(value, 0.0, 0.0); } float3 cultmath_af_symbol(float x0, float a) { return float3(x0, a, 0.0); } diff --git a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl index 8379b5f1..b3e8f915 100644 --- a/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl +++ b/packages/cultmath/unity/org.gamecult.cultmath/Shaders/CultMath.hlsl @@ -302,10 +302,10 @@ float3x3 cultmath_snoise_hessian(float3 value) // The integral over [t0, t1] of clamp(a + b t, lo, hi); see the C# math.integral_clamp_linear comment. float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float t0, float t1) { - if (b == 0.0) - return clamp(a, lo, hi) * (t1 - t0); - lo = min(lo, hi); + if (b == 0.0) + return t1 != t0 ? clamp(a, lo, hi) * (t1 - t0) : 0.0; + float direction = t1 < t0 ? -1.0 : 1.0; float start = min(t0, t1); float stop = max(t0, t1); @@ -315,7 +315,9 @@ float cultmath_integral_clamp_linear(float a, float b, float lo, float hi, float float leave = clamp(max(atLo, atHi), start, stop); float before = b > 0.0 ? lo : hi; float after = b > 0.0 ? hi : lo; - float total = before * (enter - start) + (a + b * (0.5 * (enter + leave))) * (leave - enter) + after * (stop - leave); + float held = enter > start ? before * (enter - start) : 0.0; + float kept = stop > leave ? after * (stop - leave) : 0.0; + float total = held + (a + b * (0.5 * (enter + leave))) * (leave - enter) + kept; return direction * total; } @@ -603,6 +605,119 @@ CultCellular cultmath_cellular(float3 p) #include "CultMath.Cycles.hlsl" #include "CultMath.Affine.hlsl" +// Span stages (invariant 8): the SpanPath, SpanStage and SpanNoise of math.Span.cs, field for field in +// C#'s order, and cultmath_span_stage, its octave stage, statement for statement so the text is a bit-for-bit +// mirror. They follow the includes because the bounds read CULTMATH_SNOISE_LIPSCHITZ, _HESSIAN and _THIRD. +// A shader caller builds a SpanStage itself (SpanStage(1.0, 1.0, vec3(0.0), vec3(0.0), vec3(0.0), vec3(0.0)) +// is the identity of math.SpanStage.Identity, which has no shader form); see math.span_stage's comments for +// the span, the transform, the bounds and the allowances. +struct SpanPath +{ + float3 midpoint; + float3 velocity; + float speed; + float acceleration; + float halfLength; +}; + +struct SpanStage +{ + float frequency; + float stretch; + float3 axis; + float3 shift; + float3 offset; + float3 octaveStep; +}; + +struct SpanNoise +{ + float4 field; + float curvature; + float fold; +}; + +// The octave stage behind cultmath_span_fbm and cultmath_span_ridged, as math.span_stage is behind span_fbm +// and span_ridged. The Frobenius norm of the Hessian is written out in C#'s order (GLSL has no _mRC member +// access); the C# local named step is octaveShift here (it would shadow GLSL's step()). +SpanNoise cultmath_span_stage(bool ridged, SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + float3 q = path.midpoint; + float halfLength = path.halfLength; + octaves = clamp(octaves, 0, 16); + bool stretched = dot(stage.axis, stage.axis) > 0.0 && stage.stretch != 1.0; + float3 direction = stretched ? normalize(stage.axis) : float3(0.0, 0.0, 0.0); + float squeeze = stretched ? 1.0 / stage.stretch - 1.0 : 0.0; + float norm = abs(stage.frequency) * (stretched ? max(1.0, 1.0 / stage.stretch) : 1.0); + float3 mq = stretched ? stage.frequency * (q + squeeze * dot(direction, q) * direction) : stage.frequency * q; + float3 mv = stretched ? stage.frequency * (path.velocity + squeeze * dot(direction, path.velocity) * direction) : stage.frequency * path.velocity; + float amplitude = 1.0; + float scale = 1.0; + float value = 0.0; + float3 gradient = float3(0.0, 0.0, 0.0); + float curvature = 0.0; + float fold = 0.0; + for (int i = 0; i < octaves; i++) + { + float3 octaveShift = (float)i * stage.octaveStep; + float3 x = scale * (mq + stage.shift) + (stage.offset + octaveShift); + float4 n = cultmath_snoise_grad(x); + float3 g = float3(n.x, n.y, n.z); + float3 mg = stretched ? stage.frequency * (g + squeeze * dot(direction, g) * direction) : stage.frequency * g; + if (ridged) + { + float s = sign(n.w); + value += amplitude * (1.0 - abs(n.w)); + gradient += -amplitude * scale * s * mg; + } + else + { + value += amplitude * n.w; + gradient += amplitude * scale * mg; + } + + float weight = abs(amplitude); + float factor = abs(scale) * norm; + float nu = factor * path.speed; + float rounding = 9.5367431640625e-7 * (abs(scale) * (cultmath_csum(abs(mq)) + cultmath_csum(abs(stage.shift))) + cultmath_csum(abs(stage.offset)) + cultmath_csum(abs(octaveShift)) + 1.7320508 * nu * halfLength); + float valueSlack = 6.103515625e-5 + CULTMATH_SNOISE_LIPSCHITZ * rounding; + float gradientSlack = 2.44140625e-4 + CULTMATH_SNOISE_HESSIAN * rounding; + float hessianSlack = 9.765625e-4 + CULTMATH_SNOISE_THIRD * rounding; + float3x3 hm = cultmath_snoise_hessian(x); + float frobenius = sqrt(hm[0][0] * hm[0][0] + hm[0][1] * hm[0][1] + hm[0][2] * hm[0][2] + hm[1][0] * hm[1][0] + hm[1][1] * hm[1][1] + hm[1][2] * hm[1][2] + + hm[2][0] * hm[2][0] + hm[2][1] * hm[2][1] + hm[2][2] * hm[2][2]); + float hessianBound = min(CULTMATH_SNOISE_HESSIAN, frobenius + hessianSlack + CULTMATH_SNOISE_THIRD * nu * halfLength); + curvature += weight * factor * factor * hessianBound; + if (ridged) + { + float3 w = scale * mv; + float reach = hessianBound * nu * halfLength; + float gl = min(CULTMATH_SNOISE_LIPSCHITZ, length(g) + gradientSlack + reach); + float travel = (abs(dot(g, w)) + gradientSlack * length(w)) * halfLength + (hessianBound * nu * length(w) + gl * factor * path.acceleration) * (0.5 * halfLength * halfLength) + 2.0 * valueSlack; + fold += 2.0 * weight * max(travel - abs(n.w), 0.0); + } + + scale *= lacunarity; + amplitude *= gain; + } + + SpanNoise result; + result.field = float4(gradient, value); + result.curvature = curvature; + result.fold = fold; + return result; +} + +SpanNoise cultmath_span_fbm(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(false, path, stage, octaves, lacunarity, gain); +} + +SpanNoise cultmath_span_ridged(SpanPath path, SpanStage stage, int octaves, float lacunarity, float gain) +{ + return cultmath_span_stage(true, path, stage, octaves, lacunarity, gain); +} + float cultmath_value_noise(float2 position) { float2 cell = floor(position); diff --git a/src/GameCult.Caching.MessagePack/CultMathJson.cs b/src/GameCult.Caching.MessagePack/CultMathJson.cs index 0e65bf22..dfd15c7f 100644 --- a/src/GameCult.Caching.MessagePack/CultMathJson.cs +++ b/src/GameCult.Caching.MessagePack/CultMathJson.cs @@ -14,7 +14,7 @@ namespace GameCult.Caching.MessagePack; /// The canonical System.Text.Json shape of every public CultMath value type, the JSON twin of /// . Vectors, quaternions and colors are objects keyed by their /// field names ({"x":1,"y":2}), matrices are arrays of row objects, rect is -/// {"min":..,"max":..}, CultCellular is {"nearest":..,"edge":..,"id":..}, and CultPhasor is {"cos":..,"sin":..}. +/// {"min":..,"max":..}, CultCellular is {"nearest":..,"edge":..,"id":..}, CultPhasor is {"cos":..,"sin":..}, SpanNoise is {"field":..,"curvature":..,"fold":..}, SpanPath is {"midpoint":..,"velocity":..,"speed":..,"acceleration":..,"halfLength":..}, and SpanStage is {"frequency":..,"stretch":..,"axis":..,"shift":..,"offset":..,"octaveStep":..}. /// Property names are fixed: PropertyNamingPolicy does not rename them, and /// PropertyNameCaseInsensitive makes reading case-insensitive. /// @@ -481,7 +481,7 @@ public static class CultMathJson Add(UIntScalar.Instance, new Fields(("state", Val())), a => new Random { state = a[0] }, (v, a) => a[0] = v.state), - // Compositions: matrices are arrays of row objects, rect is {min, max}, CultCellular is {nearest, edge, id}, CultPhasor is {cos, sin}. + // Compositions: matrices are arrays of row objects, rect is {min, max}, CultCellular is {nearest, edge, id}, CultPhasor is {cos, sin}, SpanNoise is {field, curvature, fold}, SpanPath is {midpoint, velocity, speed, acceleration, halfLength}, SpanStage is {frequency, stretch, axis, shift, offset, octaveStep}. Add(f, new Rows(2, Xy()), a => new float2x2(a[0], a[1], a[2], a[3]), (v, a) => { a[0] = v[0].x; a[1] = v[0].y; a[2] = v[1].x; a[3] = v[1].y; }), @@ -514,6 +514,31 @@ public static class CultMathJson a[0] = v.cos.x; a[1] = v.cos.y; a[2] = v.cos.z; a[3] = v.cos.w; a[4] = v.sin.x; a[5] = v.sin.y; a[6] = v.sin.z; a[7] = v.sin.w; }), + Add(f, new Fields(("field", Xyzw()), ("curvature", Val()), ("fold", Val())), + a => new SpanNoise(new float4(a[0], a[1], a[2], a[3]), a[4], a[5]), + (v, a) => + { + a[0] = v.field.x; a[1] = v.field.y; a[2] = v.field.z; a[3] = v.field.w; + a[4] = v.curvature; a[5] = v.fold; + }), + Add(f, new Fields(("midpoint", Xyz()), ("velocity", Xyz()), ("speed", Val()), ("acceleration", Val()), ("halfLength", Val())), + a => new SpanPath { midpoint = new float3(a[0], a[1], a[2]), velocity = new float3(a[3], a[4], a[5]), speed = a[6], acceleration = a[7], halfLength = a[8] }, + (v, a) => + { + a[0] = v.midpoint.x; a[1] = v.midpoint.y; a[2] = v.midpoint.z; + a[3] = v.velocity.x; a[4] = v.velocity.y; a[5] = v.velocity.z; + a[6] = v.speed; a[7] = v.acceleration; a[8] = v.halfLength; + }), + Add(f, new Fields(("frequency", Val()), ("stretch", Val()), ("axis", Xyz()), ("shift", Xyz()), ("offset", Xyz()), ("octaveStep", Xyz())), + a => new SpanStage { frequency = a[0], stretch = a[1], axis = new float3(a[2], a[3], a[4]), shift = new float3(a[5], a[6], a[7]), offset = new float3(a[8], a[9], a[10]), octaveStep = new float3(a[11], a[12], a[13]) }, + (v, a) => + { + a[0] = v.frequency; a[1] = v.stretch; + a[2] = v.axis.x; a[3] = v.axis.y; a[4] = v.axis.z; + a[5] = v.shift.x; a[6] = v.shift.y; a[7] = v.shift.z; + a[8] = v.offset.x; a[9] = v.offset.y; a[10] = v.offset.z; + a[11] = v.octaveStep.x; a[12] = v.octaveStep.y; a[13] = v.octaveStep.z; + }), }; var map = new Dictionary(); diff --git a/src/GameCult.Caching.MessagePack/CultMathResolver.cs b/src/GameCult.Caching.MessagePack/CultMathResolver.cs index 8383a464..8f287a5c 100644 --- a/src/GameCult.Caching.MessagePack/CultMathResolver.cs +++ b/src/GameCult.Caching.MessagePack/CultMathResolver.cs @@ -180,7 +180,7 @@ public sealed class CultMathResolver : IFormatterResolver return value; }), - // Compositions: matrices are rows, rect is [min, max], CultCellular is [nearest, edge, id], CultPhasor is [cos, sin]. + // Compositions: matrices are rows, rect is [min, max], CultCellular is [nearest, edge, id], CultPhasor is [cos, sin], SpanNoise is [field, curvature, fold], SpanPath is [midpoint, velocity, speed, acceleration, halfLength], SpanStage is [frequency, stretch, axis, shift, offset, octaveStep]. Shape((ref MessagePackWriter w, float2x2 v) => { w.WriteArrayHeader(2); PutFloat2(ref w, v[0]); PutFloat2(ref w, v[1]); }, (ref MessagePackReader r) => { @@ -236,6 +236,59 @@ public sealed class CultMathResolver : IFormatterResolver } return value; }), + Shape((ref MessagePackWriter w, SpanNoise v) => { w.WriteArrayHeader(3); PutFloat4(ref w, v.field); w.Write(v.curvature); w.Write(v.fold); }, + (ref MessagePackReader r) => + { + var value = default(SpanNoise); + for (int i = 0, n = r.ReadArrayHeader(); i < n; i++) + { + switch (i) + { + case 0: value.field = GetFloat4(ref r); break; + case 1: value.curvature = r.ReadSingle(); break; + case 2: value.fold = r.ReadSingle(); break; + default: r.Skip(); break; + } + } + return value; + }), + Shape((ref MessagePackWriter w, SpanPath v) => { w.WriteArrayHeader(5); PutFloat3(ref w, v.midpoint); PutFloat3(ref w, v.velocity); w.Write(v.speed); w.Write(v.acceleration); w.Write(v.halfLength); }, + (ref MessagePackReader r) => + { + var value = default(SpanPath); + for (int i = 0, n = r.ReadArrayHeader(); i < n; i++) + { + switch (i) + { + case 0: value.midpoint = GetFloat3(ref r); break; + case 1: value.velocity = GetFloat3(ref r); break; + case 2: value.speed = r.ReadSingle(); break; + case 3: value.acceleration = r.ReadSingle(); break; + case 4: value.halfLength = r.ReadSingle(); break; + default: r.Skip(); break; + } + } + return value; + }), + Shape((ref MessagePackWriter w, SpanStage v) => { w.WriteArrayHeader(6); w.Write(v.frequency); w.Write(v.stretch); PutFloat3(ref w, v.axis); PutFloat3(ref w, v.shift); PutFloat3(ref w, v.offset); PutFloat3(ref w, v.octaveStep); }, + (ref MessagePackReader r) => + { + var value = default(SpanStage); + for (int i = 0, n = r.ReadArrayHeader(); i < n; i++) + { + switch (i) + { + case 0: value.frequency = r.ReadSingle(); break; + case 1: value.stretch = r.ReadSingle(); break; + case 2: value.axis = GetFloat3(ref r); break; + case 3: value.shift = GetFloat3(ref r); break; + case 4: value.offset = GetFloat3(ref r); break; + case 5: value.octaveStep = GetFloat3(ref r); break; + default: r.Skip(); break; + } + } + return value; + }), }; var map = new Dictionary(); diff --git a/tests/GameCult.Caching.Tests/CultMathJsonTests.cs b/tests/GameCult.Caching.Tests/CultMathJsonTests.cs index 90d739be..2920c2f3 100644 --- a/tests/GameCult.Caching.Tests/CultMathJsonTests.cs +++ b/tests/GameCult.Caching.Tests/CultMathJsonTests.cs @@ -45,6 +45,12 @@ namespace GameCult.Caching.Tests """{"nearest":{"x":1,"y":2,"z":3,"w":4},"edge":{"x":5,"y":6,"z":7,"w":8},"id":0.25}"""); Json(new CultPhasor(new float4(1f, 2f, 3f, 4f), new float4(5f, 6f, 7f, 8f))).Should().Be( """{"cos":{"x":1,"y":2,"z":3,"w":4},"sin":{"x":5,"y":6,"z":7,"w":8}}"""); + Json(new SpanNoise(new float4(1f, 2f, 3f, 4f), 0.25f, 0.5f)).Should().Be( + """{"field":{"x":1,"y":2,"z":3,"w":4},"curvature":0.25,"fold":0.5}"""); + Json(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }).Should().Be( + """{"midpoint":{"x":1,"y":2,"z":3},"velocity":{"x":4,"y":5,"z":6},"speed":7,"acceleration":8,"halfLength":9}"""); + Json(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }).Should().Be( + """{"frequency":1,"stretch":2,"axis":{"x":3,"y":4,"z":5},"shift":{"x":6,"y":7,"z":8},"offset":{"x":9,"y":10,"z":11},"octaveStep":{"x":12,"y":13,"z":14}}"""); } [Test] @@ -69,6 +75,9 @@ namespace GameCult.Caching.Tests RoundTrip(new rect(-1f, -2f, 5f, 7f)); RoundTrip(new CultCellular(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 0.25f)); RoundTrip(new CultPhasor(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8))); + RoundTrip(new SpanNoise(new float4(1, 2, 3, 4), 0.25f, 0.5f)); + RoundTrip(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }); + RoundTrip(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }); } [Test] diff --git a/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs b/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs index 98f126af..1882bb80 100644 --- a/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs +++ b/tests/GameCult.Caching.Tests/CultMathSerializationTests.cs @@ -102,6 +102,9 @@ namespace GameCult.Caching.Tests RoundTrip(new rect(-1f, -2f, 5f, 7f)); RoundTrip(new CultCellular(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8), 0.25f)); RoundTrip(new CultPhasor(new float4(1, 2, 3, 4), new float4(5, 6, 7, 8))); + RoundTrip(new SpanNoise(new float4(1, 2, 3, 4), 0.25f, 0.5f)); + RoundTrip(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }); + RoundTrip(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }); } [Test] @@ -137,6 +140,24 @@ namespace GameCult.Caching.Tests "92" + "94CA3F800000CA40000000CA40400000CA40800000" + "94CA40A00000CA40C00000CA40E00000CA41000000"); + Hex(new SpanNoise(new float4(1f, 2f, 3f, 4f), 0.25f, 0.5f)).Should().Be( + "93" + + "94CA3F800000CA40000000CA40400000CA40800000" + + "CA3E800000" + + "CA3F000000"); + Hex(new SpanPath { midpoint = new float3(1f, 2f, 3f), velocity = new float3(4f, 5f, 6f), speed = 7f, acceleration = 8f, halfLength = 9f }).Should().Be( + "95" + + "93CA3F800000CA40000000CA40400000" + + "93CA40800000CA40A00000CA40C00000" + + "CA40E00000CA41000000CA41100000"); + Hex(new SpanStage { frequency = 1f, stretch = 2f, axis = new float3(3f, 4f, 5f), shift = new float3(6f, 7f, 8f), offset = new float3(9f, 10f, 11f), octaveStep = new float3(12f, 13f, 14f) }).Should().Be( + "96" + + "CA3F800000" + + "CA40000000" + + "93CA40400000CA40800000CA40A00000" + + "93CA40C00000CA40E00000CA41000000" + + "93CA41100000CA41200000CA41300000" + + "93CA41400000CA41500000CA41600000"); } [Test]