diff --git a/Saved/Research/intervals/REPORT.md b/Saved/Research/intervals/REPORT.md new file mode 100644 index 0000000..870af83 --- /dev/null +++ b/Saved/Research/intervals/REPORT.md @@ -0,0 +1,116 @@ +# Analytic intervals and packed signs: one-day spike + +Date: 2026-10-02 +Result: the near-region interval/bit compiler is promising at this corpus size. The experiment does **not** prove the complete realistic visible world can be built in under one second: the near output is smaller than the proposed 60 MB workload, and the far-field generator is a coarse sheet proxy rather than the production cave hierarchy. + +## What was built + +`interval_spike.cpp` is a standalone C++20 program. It generates the same ordered quadric CSG world for both implementations: + +- A point reference evaluates every one of the 17,373,979 near-region lattice nodes, using a 3D AABB index to select possible features. +- The interval path intersects the same fixed-point quadrics along all three 67,081-line axis families, sweeps sorted entry/exit events with a 64-bit membership word, composes the ordered edits, and fills packed sign words. +- `OR(8 corners) & ~AND(8 corners)` finds active cells 64 at a time. The mesher joins edge crossings around cell faces, uses an exact center sign for ambiguous faces, and emits indexed position/normal buffers. Near-region vertices come from quantized analytic quadric crossings, not edge midpoints. +- Sub-cell strata that have no lattice point inside them receive an explicit analytic surface patch. The patches are filtered against the final composed occupancy. +- The near cube has 256³ core cells at 25 cm and one node of halo on each side. Its cell region is split into 64 pages. Edge vertices are shared in one keyed buffer and assigned to a deterministic owner page. +- A separate far-field stage builds five stacked parent ellipsoid sheets as indexed patches, without generating children. + +The corpus has **372 features** per seed: 18 rooms, 147 overlapping ellipsoid tunnel segments on winding and vertical connections, 40 pillars, 36 stalactites/stalagmites, 42 shelves, 24 floating masses, 12 craters, 40 crowded-junction edits, and 13 thin strata. It uses the current HyperComplex scale as a guide: 26 m room pitch and roughly 0.28–0.37 pitch radius fractions. Five deterministic seeds were measured. + +The crowded cluster has 40 overlapping operations. Across the whole corpus the largest candidate list on a line was 58 shapes, below the 64-membership limit. The largest primitive-interval list was 52. No tested line overflowed the membership word. + +## Toolchain and numeric contract + +The host reports an AMD Ryzen 7 5800X3D with 16 logical processors. The build used the requested VS 2022 `vcvars64.bat` and `cl /std:c++20 /O2 /arch:AVX2 /EHsc`. + +From the plugin root, the tested build command was: + +```bat +call "E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" +cl /nologo /std:c++20 /O2 /arch:AVX2 /EHsc /Fe:Saved\Research\intervals\interval_spike.exe /Fo:Saved\Research\intervals\interval_spike.obj Saved\Research\intervals\interval_spike.cpp +``` + +Each benchmark process takes ` ` arguments, for example `interval_spike.exe 1 6`, and writes one JSON record to stdout. The final per-process records are `final_seed1_w1.json` through `final_seed5_w8.json` in this directory. + +Shape transforms are Q15 integer matrices; quadric coefficients are rounded once into Q28 per cm². Occupancy points and interval endpoints use signed 1/16 cm integer coordinates. A shape contains a point when its integer quadratic predicate is `<= 0`; the endpoint inclusion and ordered CSG operation rules are fixed. Line endpoints are found by integer bisection over the canonical coordinate grid, and mesh crossings are the midpoints between the last outside and first inside coordinates. + +The sign paths use integer arithmetic only. With this corpus, local coordinates stay within each feature’s conservative AABB; the largest Q28 coefficient is bounded by the 4 cm minimum radius, and the largest possible quadratic terms stay below 10¹⁸, within signed 64-bit range. That makes the sign decision identical on x64 CPUs implementing the same C++ integer rules. The far parent surface root and explicit thin-patch parameterization use floating point for vertex placement, after the sign/classification decision; cross-vendor bitwise mesh identity was not tested. On this 5800X3D, final geometry hashes matched across worker counts and repeated fresh processes. + +## Timings + +Each row summarizes five fresh processes, one per seed. The p95 uses linear interpolation between ordered samples. The interval stage includes solving/composing all three axes **and writing packed sign words**; active-cell masks are timed separately. Meshing is single-threaded in this spike, so worker counts primarily affect interval generation. + +Times are milliseconds, shown as median / p95 / worst. + +| Stage | 1 worker | 6 workers | 8 workers | +|---|---:|---:|---:| +| Corpus and spatial hierarchy | 0.5 / 0.6 / 0.6 | 0.5 / 0.7 / 0.7 | 0.5 / 0.7 / 0.8 | +| Interval solve, compose, and sign words (3 axes) | 154.2 / 167.8 / 170.8 | 48.2 / 52.6 / 52.9 | 43.2 / 49.0 / 50.1 | +| Active-cell masks | 2.1 / 3.4 / 3.7 | 2.1 / 2.3 / 2.3 | 2.1 / 2.2 / 2.2 | +| Near mesh emission | 188.4 / 197.4 / 198.7 | 191.2 / 229.3 / 238.4 | 192.8 / 219.8 / 223.9 | +| Far-field proxy | 8.6 / 8.7 / 8.8 | 8.6 / 8.6 / 8.6 | 8.6 / 8.9 / 9.0 | +| **Near CPU total** | **349.3 / 357.9 / 358.1** | **241.1 / 280.3 / 288.3** | **240.2 / 270.9 / 276.7** | +| Near plus far proxy | 357.9 / 366.5 / 366.7 | 249.7 / 288.9 / 296.9 | 248.8 / 279.5 / 285.3 | + +The interval stage fits its 150 ms allocation at 6 and 8 workers, but not at one worker. The near CPU total stays below 430 ms for every measured run. The 6-worker mesh p95 exceeds the proposed 220 ms mesh allocation; the 8-worker p95 is just below it, while its worst run is slightly over. + +## Output and memory + +| Result | Median across five seeds | Range | +|---|---:|---:| +| Near vertices | 282,404 | 272,704–296,660 | +| Near triangles | 563,052 | 543,828–591,512 | +| Near mesh buffers | 13,535,700 bytes (13.54 MB) | 13,072,232–14,219,284 bytes | +| Near plus far buffers | 19,848,460 bytes (19.85 MB) | 19,384,992–20,532,044 bytes | +| Process working set after validation | 45,191,168 bytes (43.1 MiB) | 44,199,936–46,628,864 bytes | + +The proposed 60 MB output workload was not reached. This corpus emitted about 23% of the round-3 estimate of 2.4 million triangles, so the measured mesh time does not establish how a 60 MB result scales. + +## Interval and crossing statistics + +Each axis has 67,081 lines. The table gives the median line p50, the median of the five per-seed line p95 values, and the worst single-line value over all five seeds. The per-seed line p95 uses the sorted index `floor(0.95 * (N - 1))`. + +| Axis | Candidate shapes per line: p50 / p95 / worst | Primitive intervals per line: p50 / p95 / worst | Composed crossings per line: p50 / p95 / worst | Occupancy runs per line: p50 / p95 / worst | +|---|---:|---:|---:|---:| +| X | 0 / 16 / 44 | 0 / 11 / 40 | 0 / 8 / 20 | 1 / 9 / 21 | +| Y | 0 / 17 / 46 | 0 / 12 / 40 | 0 / 10 / 24 | 1 / 9 / 23 | +| Z | 1 / 13 / 58 | 0 / 9 / 52 | 0 / 8 / 22 | 1 / 9 / 19 | + +The busiest observed line used 58 candidate slots, leaving six membership bits unused. The long lists are localized; the median line has no candidate shape in X and Y and one in Z. The distributions and maxima were stable across worker counts. + +## Correctness and determinism + +- **Full sign agreement:** all 15 final benchmark processes compared the pointwise reference with all three interval-axis sign paths at every lattice node: zero mismatches. +- **Mesh crossings:** zero sign-changing lattice edges lacked an analytic crossing; zero contour-degree faults and zero open cell contours. +- **Page ownership:** all 64 near pages passed unique edge-key and owner-page checks. Shared border vertices use one canonical crossing record and quantized position. +- **Tangency:** an exact tangent line retained its single-point interval. +- **Coincident edits:** coincident carve/add intervals had identical roots, and both operation orders produced their defined last-operation result. +- **Negative coordinates:** the full lattice includes negative world X and Z coordinates and passed the point-reference comparison. +- **Thin strata:** 13 sub-cell strata were in each corpus. Across the five seeds, 1–10 had no structural lattice point inside; every such feature received explicit surface triangles (288–1,993 triangles per seed). The deliberately isolated 12 cm-thick air seam had no lattice sample and two crossing tags on its central line. The validation gate was also given a deliberately omitted thin surface and rejected it. +- **Determinism:** for each of five seeds, the combined geometry hash was identical at 1, 6, and 8 workers. A repeated fresh seed-1/one-worker process also matched. Per-seed hashes: `e5ad62303de5f641`, `17a0d35b9c500445`, `c74544dc1b78ce7b`, `69cef91397829b57`, `afb5e5e8fa54b8a9`. + +## Far-field result and limit + +The proxy selected **47,040 nodes**, **92,150 patches**, **94,090 vertices**, and **184,300 triangles** in **6,312,760 bytes**, with zero descendants. It takes about 8.6 ms. The grid spans ±6 km on each horizontal axis and has five stacked parent layers. + +This is not a proof of the far cave hierarchy. The patches are all on the same 125 m grid; the reported LOD bands are metadata rather than different tessellation levels. The parent shapes are five broad ellipsoid sheets, not the room/tunnel grammar used in the near corpus. The square reaches about 8.5 km at its corners, and no far/near transition surface was built or validated. The production far field is therefore **unproven**. + +## Gate decision + +The near interval/bit core passes the **430 ms timing** portion of the first gate on this 372-feature corpus, and crowded junctions stay below the 64-shape line limit. The output is only 13.1–14.2 MB nearby, however, so the proposed ~60 MB mesh budget was not exercised. The 6-worker mesh p95 also exceeds its 220 ms sub-budget. Thin-feature and shared-boundary checks pass for the tested feature family. + +This is a positive near-core result, not a whole-world pass. Do not treat the under-367 ms near-plus-proxy figure as evidence that the realistic six-kilometre cave world is ready for the Unreal publication/collision test; the far hierarchy and larger output workload remain unproven. + +## Failed and retried builds/runs + +- Build attempt 1 failed because Win32 `min`/`max` macros expanded `std::min`/`std::max` calls. `NOMINMAX` fixed that class of error. +- Build attempt 2 then failed on the room-center array dimensions and the Windows `far` macro colliding with a local variable name. Both were corrected; subsequent MSVC builds succeeded. +- `vcvars64.bat` printed that `vswhere.exe` was not recognized on each build, but it continued and launched `cl.exe`. The final build succeeded with only unused-parameter and bool-comparison warnings. +- The first benchmark launch attempt used PowerShell’s unsupported `Start-Process -PriorityClass` parameter and did not start the executable. The retry launched the process and immediately set its priority to `BelowNormal`. +- The first executable run completed but failed validation with 878,033 sign mismatches and no thin crossing tags. The line solver had omitted the shape-center offset when converting the quadratic vertex to an absolute coordinate. After correction, the full sign comparison passed; thin surfaces and explicit contour-integrity assertions were then added. +- A final-run preflight initially aborted at a self-imposed 6 GB free-memory threshold; no process started. The machine then had 4.5 GB free while another foreground process used about 12.3 GB. The benchmark working set was about 45 MB, so the final checks were retried below-normal with a 1 GB floor. All three checks passed, followed by all 15 final fresh-process runs. + +All final runs exited 0. No Unreal process was launched, no `Source/` file was modified, and no commit, push, or stash was made. + +## Next step + +Extend the CPU corpus until it emits the intended ~60 MB / 2.4 million triangle workload, with page meshing parallelized deterministically. In parallel, replace the far sheet proxy with actual selected parent room/tunnel/strata shapes, radial horizon clipping, and a shared near/far transition. Re-run the same validation and gates; then decide whether the Unreal publication/collision test is justified. diff --git a/Saved/Research/intervals/bench_seed1_w1.json b/Saved/Research/intervals/bench_seed1_w1.json new file mode 100644 index 0000000..0899fdf --- /dev/null +++ b/Saved/Research/intervals/bench_seed1_w1.json @@ -0,0 +1 @@ +{"seed":1,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.497,"interval_solve_compose_and_sign_words_3_axes":153.430,"active_cell_masks":1.978,"near_mesh_emission":172.709,"far_field":8.577,"validation_outside_timing":242.817,"near_cpu_total":328.614,"near_plus_far_total":337.191},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44474368,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed1_w1_repeat.json b/Saved/Research/intervals/bench_seed1_w1_repeat.json new file mode 100644 index 0000000..ef780e7 --- /dev/null +++ b/Saved/Research/intervals/bench_seed1_w1_repeat.json @@ -0,0 +1 @@ +{"seed":1,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.493,"interval_solve_compose_and_sign_words_3_axes":151.928,"active_cell_masks":2.082,"near_mesh_emission":178.676,"far_field":8.558,"validation_outside_timing":241.893,"near_cpu_total":333.179,"near_plus_far_total":341.737},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44490752,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed1_w6.json b/Saved/Research/intervals/bench_seed1_w6.json new file mode 100644 index 0000000..c385ea2 --- /dev/null +++ b/Saved/Research/intervals/bench_seed1_w6.json @@ -0,0 +1 @@ +{"seed":1,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.725,"interval_solve_compose_and_sign_words_3_axes":72.450,"active_cell_masks":3.939,"near_mesh_emission":254.653,"far_field":8.601,"validation_outside_timing":320.588,"near_cpu_total":331.767,"near_plus_far_total":340.367},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":44904448,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed1_w8.json b/Saved/Research/intervals/bench_seed1_w8.json new file mode 100644 index 0000000..ec466a5 --- /dev/null +++ b/Saved/Research/intervals/bench_seed1_w8.json @@ -0,0 +1 @@ +{"seed":1,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.501,"interval_solve_compose_and_sign_words_3_axes":49.895,"active_cell_masks":2.151,"near_mesh_emission":183.250,"far_field":9.053,"validation_outside_timing":243.429,"near_cpu_total":235.797,"near_plus_far_total":244.850},"near":{"active_cells":272986,"vertices":275646,"triangles":549585,"bytes":13211864,"hash":"ff39dc05fa0b59e0","pages":64,"index_ranges":67,"page_border_vertices":14777,"ambiguous_faces":3870,"multi_cross_edges":416,"thin_detected":13,"thin_hidden_lattice":3,"thin_meshed":3,"explicit_thin_triangles":639},"interval_stats":{"x":{"lines":67081,"candidate_total":181692,"candidate_p50":0,"candidate_p95":16,"candidate_max":44,"interval_total":110023,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":88770,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":193718,"candidate_p50":0,"candidate_p95":18,"candidate_max":45,"interval_total":111420,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":91616,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":206488,"candidate_p50":0,"candidate_p95":13,"candidate_max":58,"interval_total":120012,"interval_p50":0,"interval_p95":9,"interval_max":52,"crossing_total":109308,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"ab4a669002ce586d"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":3,"audit_thin_crossing_events":2},"output_bytes_total":19524624,"combined_hash":"e5ad62303de5f641","working_set_bytes":45068288,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed2_w1.json b/Saved/Research/intervals/bench_seed2_w1.json new file mode 100644 index 0000000..408d30c --- /dev/null +++ b/Saved/Research/intervals/bench_seed2_w1.json @@ -0,0 +1 @@ +{"seed":2,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.505,"interval_solve_compose_and_sign_words_3_axes":165.156,"active_cell_masks":2.084,"near_mesh_emission":184.966,"far_field":8.577,"validation_outside_timing":246.244,"near_cpu_total":352.711,"near_plus_far_total":361.288},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45158400,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed2_w6.json b/Saved/Research/intervals/bench_seed2_w6.json new file mode 100644 index 0000000..570b856 --- /dev/null +++ b/Saved/Research/intervals/bench_seed2_w6.json @@ -0,0 +1 @@ +{"seed":2,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.499,"interval_solve_compose_and_sign_words_3_axes":51.718,"active_cell_masks":2.512,"near_mesh_emission":191.036,"far_field":8.609,"validation_outside_timing":269.096,"near_cpu_total":245.766,"near_plus_far_total":254.375},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45715456,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed2_w8.json b/Saved/Research/intervals/bench_seed2_w8.json new file mode 100644 index 0000000..53193e8 --- /dev/null +++ b/Saved/Research/intervals/bench_seed2_w8.json @@ -0,0 +1 @@ +{"seed":2,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.496,"interval_solve_compose_and_sign_words_3_axes":52.960,"active_cell_masks":2.198,"near_mesh_emission":191.148,"far_field":8.569,"validation_outside_timing":246.765,"near_cpu_total":246.802,"near_plus_far_total":255.372},"near":{"active_cells":283161,"vertices":286734,"triangles":569817,"bytes":13720900,"hash":"dab38d009700f1f0","pages":64,"index_ranges":74,"page_border_vertices":15582,"ambiguous_faces":2854,"multi_cross_edges":536,"thin_detected":13,"thin_hidden_lattice":10,"thin_meshed":10,"explicit_thin_triangles":1993},"interval_stats":{"x":{"lines":67081,"candidate_total":187058,"candidate_p50":0,"candidate_p95":17,"candidate_max":43,"interval_total":111999,"interval_p50":0,"interval_p95":11,"interval_max":40,"crossing_total":89658,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":199365,"candidate_p50":0,"candidate_p95":18,"candidate_max":44,"interval_total":120586,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":104152,"crossing_p50":0,"crossing_p95":10,"crossing_max":22,"runs_p50":1,"runs_p95":11,"runs_max":19},"z":{"lines":67081,"candidate_total":204987,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":124121,"interval_p50":0,"interval_p95":10,"interval_max":48,"crossing_total":112170,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"0293ad0bb4aab1f8"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":10,"audit_thin_crossing_events":2},"output_bytes_total":20033660,"combined_hash":"17a0d35b9c500445","working_set_bytes":45731840,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed3_w1.json b/Saved/Research/intervals/bench_seed3_w1.json new file mode 100644 index 0000000..d2237a2 --- /dev/null +++ b/Saved/Research/intervals/bench_seed3_w1.json @@ -0,0 +1 @@ +{"seed":3,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.508,"interval_solve_compose_and_sign_words_3_axes":155.158,"active_cell_masks":3.366,"near_mesh_emission":198.934,"far_field":8.589,"validation_outside_timing":264.253,"near_cpu_total":357.966,"near_plus_far_total":366.555},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":45920256,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed3_w6.json b/Saved/Research/intervals/bench_seed3_w6.json new file mode 100644 index 0000000..6c1c241 --- /dev/null +++ b/Saved/Research/intervals/bench_seed3_w6.json @@ -0,0 +1 @@ +{"seed":3,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.518,"interval_solve_compose_and_sign_words_3_axes":51.474,"active_cell_masks":2.204,"near_mesh_emission":203.409,"far_field":8.579,"validation_outside_timing":247.255,"near_cpu_total":257.606,"near_plus_far_total":266.185},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":46493696,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed3_w8.json b/Saved/Research/intervals/bench_seed3_w8.json new file mode 100644 index 0000000..4896c87 --- /dev/null +++ b/Saved/Research/intervals/bench_seed3_w8.json @@ -0,0 +1 @@ +{"seed":3,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.499,"interval_solve_compose_and_sign_words_3_axes":43.016,"active_cell_masks":2.446,"near_mesh_emission":240.567,"far_field":8.808,"validation_outside_timing":267.854,"near_cpu_total":286.528,"near_plus_far_total":295.336},"near":{"active_cells":292580,"vertices":296660,"triangles":591512,"bytes":14219284,"hash":"600f405efd49ce29","pages":64,"index_ranges":65,"page_border_vertices":15757,"ambiguous_faces":7772,"multi_cross_edges":264,"thin_detected":13,"thin_hidden_lattice":1,"thin_meshed":1,"explicit_thin_triangles":288},"interval_stats":{"x":{"lines":67081,"candidate_total":190357,"candidate_p50":0,"candidate_p95":16,"candidate_max":42,"interval_total":112011,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":95212,"crossing_p50":0,"crossing_p95":10,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":21},"y":{"lines":67081,"candidate_total":194781,"candidate_p50":0,"candidate_p95":17,"candidate_max":46,"interval_total":112410,"interval_p50":0,"interval_p95":12,"interval_max":40,"crossing_total":93860,"crossing_p50":0,"crossing_p95":10,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":215639,"candidate_p50":1,"candidate_p95":13,"candidate_max":57,"interval_total":124874,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":118734,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"dbc331a49aa1719e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":1,"audit_thin_crossing_events":2},"output_bytes_total":20532044,"combined_hash":"c74544dc1b78ce7b","working_set_bytes":46301184,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed4_w1.json b/Saved/Research/intervals/bench_seed4_w1.json new file mode 100644 index 0000000..e15e8e8 --- /dev/null +++ b/Saved/Research/intervals/bench_seed4_w1.json @@ -0,0 +1 @@ +{"seed":4,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.485,"interval_solve_compose_and_sign_words_3_axes":153.462,"active_cell_masks":2.555,"near_mesh_emission":189.354,"far_field":8.573,"validation_outside_timing":242.167,"near_cpu_total":345.856,"near_plus_far_total":354.429},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45002752,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed4_w6.json b/Saved/Research/intervals/bench_seed4_w6.json new file mode 100644 index 0000000..2e27139 --- /dev/null +++ b/Saved/Research/intervals/bench_seed4_w6.json @@ -0,0 +1 @@ +{"seed":4,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.478,"interval_solve_compose_and_sign_words_3_axes":51.348,"active_cell_masks":2.415,"near_mesh_emission":187.195,"far_field":8.576,"validation_outside_timing":243.163,"near_cpu_total":241.436,"near_plus_far_total":250.012},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45342720,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed4_w8.json b/Saved/Research/intervals/bench_seed4_w8.json new file mode 100644 index 0000000..51c5e43 --- /dev/null +++ b/Saved/Research/intervals/bench_seed4_w8.json @@ -0,0 +1 @@ +{"seed":4,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.485,"interval_solve_compose_and_sign_words_3_axes":40.148,"active_cell_masks":2.266,"near_mesh_emission":188.109,"far_field":8.564,"validation_outside_timing":242.086,"near_cpu_total":231.009,"near_plus_far_total":239.573},"near":{"active_cells":279309,"vertices":282404,"triangles":563052,"bytes":13535700,"hash":"4ed64f01c5df738b","pages":64,"index_ranges":69,"page_border_vertices":13975,"ambiguous_faces":3838,"multi_cross_edges":680,"thin_detected":13,"thin_hidden_lattice":5,"thin_meshed":5,"explicit_thin_triangles":1120},"interval_stats":{"x":{"lines":67081,"candidate_total":181609,"candidate_p50":0,"candidate_p95":16,"candidate_max":41,"interval_total":109240,"interval_p50":0,"interval_p95":11,"interval_max":37,"crossing_total":88640,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":17},"y":{"lines":67081,"candidate_total":191723,"candidate_p50":0,"candidate_p95":17,"candidate_max":42,"interval_total":114274,"interval_p50":0,"interval_p95":12,"interval_max":39,"crossing_total":98854,"crossing_p50":0,"crossing_p95":10,"crossing_max":24,"runs_p50":1,"runs_p95":9,"runs_max":23},"z":{"lines":67081,"candidate_total":201505,"candidate_p50":0,"candidate_p95":13,"candidate_max":56,"interval_total":124143,"interval_p50":0,"interval_p95":10,"interval_max":50,"crossing_total":113480,"crossing_p50":0,"crossing_p95":8,"crossing_max":22,"runs_p50":1,"runs_p95":9,"runs_max":17}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"75f729c8e51da37e"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":5,"audit_thin_crossing_events":2},"output_bytes_total":19848460,"combined_hash":"69cef91397829b57","working_set_bytes":45486080,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed5_w1.json b/Saved/Research/intervals/bench_seed5_w1.json new file mode 100644 index 0000000..366612f --- /dev/null +++ b/Saved/Research/intervals/bench_seed5_w1.json @@ -0,0 +1 @@ +{"seed":5,"workers":1,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.470,"interval_solve_compose_and_sign_words_3_axes":172.633,"active_cell_masks":3.120,"near_mesh_emission":184.374,"far_field":8.605,"validation_outside_timing":244.536,"near_cpu_total":360.596,"near_plus_far_total":369.201},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44163072,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed5_w6.json b/Saved/Research/intervals/bench_seed5_w6.json new file mode 100644 index 0000000..4cb8dd5 --- /dev/null +++ b/Saved/Research/intervals/bench_seed5_w6.json @@ -0,0 +1 @@ +{"seed":5,"workers":6,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.468,"interval_solve_compose_and_sign_words_3_axes":52.449,"active_cell_masks":2.217,"near_mesh_emission":184.168,"far_field":8.708,"validation_outside_timing":248.390,"near_cpu_total":239.302,"near_plus_far_total":248.010},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44736512,"validation_pass":true} diff --git a/Saved/Research/intervals/bench_seed5_w8.json b/Saved/Research/intervals/bench_seed5_w8.json new file mode 100644 index 0000000..fa23d98 --- /dev/null +++ b/Saved/Research/intervals/bench_seed5_w8.json @@ -0,0 +1 @@ +{"seed":5,"workers":8,"shape_count":372,"near_cells":16777216,"near_nodes":17373979,"membership_limit":64,"overflow":false,"ms":{"corpus_hierarchy":0.471,"interval_solve_compose_and_sign_words_3_axes":43.064,"active_cell_masks":1.906,"near_mesh_emission":195.702,"far_field":8.607,"validation_outside_timing":264.699,"near_cpu_total":241.142,"near_plus_far_total":249.750},"near":{"active_cells":269847,"vertices":272704,"triangles":543828,"bytes":13072232,"hash":"885750d27c475ba9","pages":64,"index_ranges":70,"page_border_vertices":12496,"ambiguous_faces":2638,"multi_cross_edges":976,"thin_detected":13,"thin_hidden_lattice":6,"thin_meshed":6,"explicit_thin_triangles":1282},"interval_stats":{"x":{"lines":67081,"candidate_total":186280,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111929,"interval_p50":0,"interval_p95":11,"interval_max":38,"crossing_total":89242,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19},"y":{"lines":67081,"candidate_total":192484,"candidate_p50":0,"candidate_p95":17,"candidate_max":44,"interval_total":111223,"interval_p50":0,"interval_p95":12,"interval_max":36,"crossing_total":90196,"crossing_p50":0,"crossing_p95":8,"crossing_max":20,"runs_p50":1,"runs_p95":9,"runs_max":19},"z":{"lines":67081,"candidate_total":214972,"candidate_p50":1,"candidate_p95":13,"candidate_max":56,"interval_total":122709,"interval_p50":0,"interval_p95":9,"interval_max":49,"crossing_total":111168,"crossing_p50":0,"crossing_p95":8,"crossing_max":18,"runs_p50":1,"runs_p95":9,"runs_max":19}},"far":{"nodes":47040,"patches":92150,"vertices":94090,"triangles":184300,"bytes":6312760,"descendants":0,"lod_nodes":[240,880,3680,15040,11360,15840],"hash":"c100350769615273"},"validation":{"signs_ok":true,"mismatches":0,"negative_coordinates":true,"page_ownership_and_shared_edges":true,"tangency":true,"coincident_edits":true,"thin_surface_present":true,"omit_thin_surface_rejected":true,"thin_hidden_in_lattice":6,"audit_thin_crossing_events":2},"output_bytes_total":19384992,"combined_hash":"afb5e5e8fa54b8a9","working_set_bytes":44937216,"validation_pass":true} diff --git a/Saved/Research/intervals/interval_spike.cpp b/Saved/Research/intervals/interval_spike.cpp new file mode 100644 index 0000000..1d3ca80 --- /dev/null +++ b/Saved/Research/intervals/interval_spike.cpp @@ -0,0 +1,1391 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#include +#pragma comment(lib, "Psapi.lib") + +namespace { + +using Clock = std::chrono::steady_clock; +constexpr int kCoreCells = 256; +constexpr int kHalo = 1; +constexpr int kNodes = kCoreCells + 1 + 2 * kHalo; +constexpr int kWordsPerLine = (kNodes + 63) / 64; +constexpr int kPageCells = 64; +constexpr int kPagesPerAxis = kCoreCells / kPageCells; +constexpr int kBucketCm = 400; +constexpr int kBucketCount = 17; +constexpr int kShapeLimitPerLine = 64; +constexpr int64_t kQScale = int64_t{1} << 28; +constexpr int64_t kSubPerCm = 16; // canonical interval/sign lattice: 0.625 mm +constexpr int64_t kSurfaceSubPerCm = 32; // crossing positions: 0.3125 mm +constexpr int kCellStepCm = 25; +constexpr int kGridMinCm = -3225; +constexpr int kGridMaxCm = kGridMinCm + (kNodes - 1) * kCellStepCm; +constexpr int kWorldOriginCm[3] = {-12000, 6000, -4500}; +constexpr uint64_t kMask64 = ~uint64_t{0}; + +enum class Operation : uint8_t { CarveAir, AddRock }; +enum class FeatureKind : uint8_t { + Room, Tunnel, Pillar, Stalactite, Stalagmite, Shelf, FloatingMass, + Stratum, Crater, CrowdedJunction +}; + +struct Mat3Q15 { int32_t v[3][3]; }; + +// Quantized rotations are fixed data, so corpus construction does not depend on libm. +constexpr std::array kRotations = {{ + {{{32768,0,0},{0,32768,0},{0,0,32768}}}, + {{{23170,-23170,0},{23170,23170,0},{0,0,32768}}}, + {{{23170,0,23170},{0,32768,0},{-23170,0,23170}}}, + {{{32768,0,0},{0,23170,-23170},{0,23170,23170}}}, + {{{0,-32768,0},{32768,0,0},{0,0,32768}}}, + {{{0,0,32768},{0,32768,0},{-32768,0,0}}}, + {{{32768,0,0},{0,0,-32768},{0,32768,0}}}, + {{{16384,-23170,16384},{16384,23170,16384},{-23170,0,23170}}} +}}; + +struct Shape { + int32_t c[3]{}; // local centimetres + int32_t radius[3]{}; // local centimetres + int32_t rot[3][3]{}; // Q15 rotation, columns are local axes + int64_t q[3][3]{}; // Q28 / cm^2, exact predicate coefficients + int32_t lo[3]{}, hi[3]{}; // conservative local-centimetre AABB, inclusive + Operation op = Operation::CarveAir; + FeatureKind kind = FeatureKind::Room; + uint32_t id = 0; + bool auditThin = false; +}; + +struct Crossing { + int32_t positionSub32 = 0; // local cm * 32, half of the canonical 1/16 cm grid + uint32_t shapeId = 0; + uint8_t beforeRock = 0; + uint8_t afterRock = 0; +}; + +struct LineRange { + uint32_t offset = 0; + uint16_t count = 0; + uint16_t candidates = 0; + uint16_t primitiveIntervals = 0; + uint16_t occupancyRuns = 0; +}; + +struct AxisData { + std::vector signs; + std::vector lines; + std::vector crossings; +}; + +struct AxisStats { + double candidateP50 = 0, candidateP95 = 0; + double intervalP50 = 0, intervalP95 = 0; + double crossingP50 = 0, crossingP95 = 0; + double runsP50 = 0, runsP95 = 0; + uint32_t candidateMax = 0, intervalMax = 0, crossingMax = 0, runsMax = 0; + uint64_t candidateTotal = 0, intervalTotal = 0, crossingTotal = 0, runsTotal = 0; +}; + +struct Vertex { + float x, y, z; + float nx, ny, nz; +}; + +struct TempVertex { + uint64_t key = 0; + uint32_t ownerPage = 0; + Vertex v{}; +}; + +struct PageRange { uint32_t page, indexStart, indexCount, vertexStart, vertexCount; }; + +struct MeshOutput { + std::vector vertices; + std::vector indices; + std::vector pages; + uint64_t hash = 0; + uint64_t bytes = 0; + uint64_t triangles = 0; + uint64_t ambiguousFaces = 0; + uint64_t multiCrossEdges = 0; + uint64_t missingAnalyticCrossings = 0; + uint64_t contourDegreeFaults = 0; + uint64_t openContourLoops = 0; + uint64_t explicitThinTriangles = 0; + uint64_t thinFeaturesDetected = 0; + uint64_t thinFeaturesHiddenInLattice = 0; + uint64_t thinFeaturesMeshed = 0; + uint64_t pageBorderVertices = 0; +}; + +struct Bin3D { std::vector ids; }; +struct ProjectionBins { std::vector ids[kBucketCount * kBucketCount]; }; + +struct Corpus { + std::vector shapes; + Bin3D bins3d[kBucketCount * kBucketCount * kBucketCount]; + ProjectionBins projections[3]; + uint64_t seed = 0; + uint32_t maxMembershipAudit = 0; +}; + +struct WorkerLines { + std::vector crossings; +}; + +struct FarOutput { + std::vector vertices; + std::vector indices; + std::vector patches; + std::array lodNodes{}; + uint64_t nodes = 0, descendants = 0, patchCount = 0, bytes = 0, hash = 0; +}; + +double ms(Clock::time_point a, Clock::time_point b) { + return std::chrono::duration(b - a).count(); +} + +uint64_t mix64(uint64_t x) { + x += 0x9E3779B97F4A7C15ull; + x = (x ^ (x >> 30)) * 0xBF58476D1CE4E5B9ull; + x = (x ^ (x >> 27)) * 0x94D049BB133111EBull; + return x ^ (x >> 31); +} + +struct Rng { + uint64_t state; + explicit Rng(uint64_t s) : state(s) {} + uint32_t next() { state += 0x9E3779B97F4A7C15ull; return static_cast(mix64(state) >> 32); } + int range(int lo, int hi) { return lo + static_cast(next() % static_cast(hi - lo + 1)); } + bool chance(int n, int d) { return static_cast(next() % d) < n; } +}; + +int64_t round_div(int64_t n, int64_t d) { + if (n >= 0) return (n + d / 2) / d; + return -((-n + d / 2) / d); +} + +int floor_div(int a, int b) { + const int q = a / b; + const int r = a % b; + return q - ((r != 0 && ((r < 0) != (b < 0))) ? 1 : 0); +} + +int64_t floor_div64(int64_t a, int64_t b) { + const int64_t q = a / b; + const int64_t r = a % b; + return q - ((r != 0 && ((r < 0) != (b < 0))) ? 1 : 0); +} + +int ceil_div(int a, int b) { return -floor_div(-a, b); } + +void build_quadric(Shape& s, uint32_t rotationIndex) { + const auto& r = kRotations[rotationIndex % kRotations.size()]; + for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) s.rot[i][j] = r.v[i][j]; + // Q = R * diag(1/r^2) * R^T. Each term is rounded once to Q28. + for (int a = 0; a < 3; ++a) for (int b = 0; b < 3; ++b) { + int64_t sum = 0; + for (int k = 0; k < 3; ++k) { + const int64_t prod = static_cast(s.rot[a][k]) * s.rot[b][k]; + const int64_t den = (int64_t{1} << 30) * s.radius[k] * s.radius[k]; + sum += round_div(prod * kQScale, den); + } + s.q[a][b] = sum; + } + for (int axis = 0; axis < 3; ++axis) { + int64_t extentNumerator = 0; + for (int k = 0; k < 3; ++k) { + const int64_t v = std::abs(s.rot[axis][k]); + extentNumerator += v * s.radius[k]; + } + const int32_t extent = static_cast((extentNumerator + 32767) / 32768) + 3; + s.lo[axis] = s.c[axis] - extent; + s.hi[axis] = s.c[axis] + extent; + } +} + +void add_shape(Corpus& c, int x, int y, int z, int rx, int ry, int rz, + Operation op, FeatureKind kind, uint32_t rotation, bool auditThin = false) { + Shape s; + s.c[0] = x; s.c[1] = y; s.c[2] = z; + s.radius[0] = std::max(1, rx); s.radius[1] = std::max(1, ry); s.radius[2] = std::max(1, rz); + s.op = op; s.kind = kind; s.id = static_cast(c.shapes.size()); s.auditThin = auditThin; + build_quadric(s, rotation); + c.shapes.push_back(s); +} + +void add_sphere(Corpus& c, int x, int y, int z, int r, Operation op, FeatureKind kind) { + add_shape(c, x, y, z, r, r, r, op, kind, 0); +} + +void build_corpus(Corpus& c, uint64_t seed) { + c.seed = seed; + Rng rng(seed ^ 0x0A57A20261002ull); + std::array, 3>, 3>, 2> centers{}; + for (int layer = 0; layer < 2; ++layer) { + for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) { + const int x = (ix - 1) * 1300 + rng.range(-170, 170); + const int y = (iy - 1) * 1300 + rng.range(-170, 170); + const int z = (layer == 0 ? -850 : 850) + rng.range(-120, 120); + centers[layer][iy][ix] = {x, y, z}; + const int rx = rng.range(480, 820); + const int ry = rng.range(430, 760); + const int rz = rng.range(300, 540); + add_shape(c, x, y, z, rx, ry, rz, Operation::CarveAir, FeatureKind::Room, + rng.next() % 8); + } + } + + // Curved tunnel runs are unions of overlapping analytic ellipsoidal segments. + for (int layer = 0; layer < 2; ++layer) { + for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) { + const auto a = centers[layer][iy][ix]; + for (int dir = 0; dir < 2; ++dir) { + const int nx = ix + (dir == 0); + const int ny = iy + (dir == 1); + if (nx >= 3 || ny >= 3) continue; + const auto b = centers[layer][ny][nx]; + const int cx = (a[0] + b[0]) / 2 + rng.range(-220, 220); + const int cy = (a[1] + b[1]) / 2 + rng.range(-220, 220); + const int cz = (a[2] + b[2]) / 2 + rng.range(-150, 150); + for (int seg = 0; seg < 5; ++seg) { + const int t = 1 + seg; + const int x = (a[0] * (6 - t) + b[0] * t) / 6 + (seg == 2 ? cx - (a[0] + b[0]) / 2 : 0); + const int y = (a[1] * (6 - t) + b[1] * t) / 6 + (seg == 2 ? cy - (a[1] + b[1]) / 2 : 0); + const int z = (a[2] * (6 - t) + b[2] * t) / 6 + (seg == 2 ? cz - (a[2] + b[2]) / 2 : 0); + const int r = rng.range(200, 300); + add_sphere(c, x, y, z, r, Operation::CarveAir, FeatureKind::Tunnel); + } + } + } + } + // Vertical connectors between stacked room layers. + for (int iy = 0; iy < 3; ++iy) for (int ix = 0; ix < 3; ++ix) { + const auto a = centers[0][iy][ix], b = centers[1][iy][ix]; + for (int seg = 1; seg <= 3; ++seg) { + add_sphere(c, (a[0] * (4 - seg) + b[0] * seg) / 4, + (a[1] * (4 - seg) + b[1] * seg) / 4, + (a[2] * (4 - seg) + b[2] * seg) / 4, + rng.range(190, 270), Operation::CarveAir, FeatureKind::Tunnel); + } + } + + // Dense constructive formations, placed in chamber space or near chamber walls. + for (int i = 0; i < 40; ++i) { + const int x = rng.range(-2500, 2500), y = rng.range(-2500, 2500); + const int z = rng.range(-1900, 1900); + add_shape(c, x, y, z, rng.range(70, 150), rng.range(70, 150), rng.range(260, 520), + Operation::AddRock, FeatureKind::Pillar, rng.next() % 8); + } + for (int i = 0; i < 36; ++i) { + const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3]; + const int sign = (i & 1) ? 1 : -1; + add_shape(c, center[0] + rng.range(-260, 260), center[1] + rng.range(-260, 260), + center[2] + sign * rng.range(260, 410), rng.range(45, 85), + rng.range(45, 85), rng.range(230, 370), Operation::AddRock, + (i & 1) ? FeatureKind::Stalagmite : FeatureKind::Stalactite, rng.next() % 8); + } + for (int i = 0; i < 42; ++i) { + const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3]; + add_shape(c, center[0] + rng.range(-350, 350), center[1] + rng.range(-350, 350), + center[2] + rng.range(-220, 220), rng.range(250, 650), rng.range(130, 320), + rng.range(30, 90), Operation::AddRock, FeatureKind::Shelf, rng.next() % 8); + } + for (int i = 0; i < 24; ++i) { + const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3]; + add_shape(c, center[0] + rng.range(-280, 280), center[1] + rng.range(-280, 280), + center[2] + rng.range(-250, 250), rng.range(180, 390), rng.range(150, 340), + rng.range(140, 330), Operation::AddRock, FeatureKind::FloatingMass, rng.next() % 8); + } + // Several crater cuts, including deliberately intersecting crater groups. + for (int i = 0; i < 12; ++i) { + const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3]; + const int x = center[0] + rng.range(300, 650); + const int y = center[1] + rng.range(-280, 280); + const int z = center[2] + rng.range(-280, 280); + add_shape(c, x, y, z, rng.range(230, 520), rng.range(210, 460), rng.range(180, 420), + Operation::CarveAir, FeatureKind::Crater, rng.next() % 8); + } + // Crowded junction: a bounded 40-shape overlap cluster exercises long event lists. + for (int i = 0; i < 40; ++i) { + const int x = rng.range(-110, 110), y = rng.range(-110, 110), z = rng.range(-110, 110); + const Operation op = (i % 3 == 0) ? Operation::AddRock : Operation::CarveAir; + const int r = rng.range(75, 190); + add_shape(c, x, y, z, r, rng.range(70, 200), rng.range(75, 185), op, + FeatureKind::CrowdedJunction, rng.next() % 8); + } + // Thin strata and shelves, with several widths below the 25 cm structural cell size. + for (int i = 0; i < 12; ++i) { + const auto center = centers[i % 2][(i / 2) % 3][(i / 6) % 3]; + add_shape(c, center[0] + rng.range(-180, 180), center[1] + rng.range(-180, 180), + center[2] + rng.range(-180, 180), rng.range(300, 580), rng.range(250, 520), + rng.range(4, 11), Operation::AddRock, FeatureKind::Stratum, rng.next() % 8); + } + // A guaranteed isolated sub-cell air seam in solid rock. No 25 cm lattice node lies inside it. + add_shape(c, 2900, -2900, 2887, 500, 400, 6, Operation::CarveAir, + FeatureKind::Stratum, 0, true); + + // Build deterministic 3D and axis-projection bins in feature order. + for (uint32_t id = 0; id < c.shapes.size(); ++id) { + const Shape& s = c.shapes[id]; + const int bx0 = std::clamp(floor_div(s.lo[0] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int bx1 = std::clamp(floor_div(s.hi[0] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int by0 = std::clamp(floor_div(s.lo[1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int by1 = std::clamp(floor_div(s.hi[1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int bz0 = std::clamp(floor_div(s.lo[2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int bz1 = std::clamp(floor_div(s.hi[2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + for (int bz = bz0; bz <= bz1; ++bz) for (int by = by0; by <= by1; ++by) + for (int bx = bx0; bx <= bx1; ++bx) + c.bins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids.push_back(id); + for (int axis = 0; axis < 3; ++axis) { + int a = (axis + 1) % 3, b = (axis + 2) % 3; + int a0 = std::clamp(floor_div(s.lo[a] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + int a1 = std::clamp(floor_div(s.hi[a] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + int b0 = std::clamp(floor_div(s.lo[b] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + int b1 = std::clamp(floor_div(s.hi[b] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + for (int ib = b0; ib <= b1; ++ib) for (int ia = a0; ia <= a1; ++ia) + c.projections[axis].ids[ib * kBucketCount + ia].push_back(id); + } + } +} + +int bucket_of_sub(int64_t qSub) { + const int64_t qCmFloor = floor_div64(qSub, kSubPerCm); + return std::clamp(floor_div(static_cast(qCmFloor) - kGridMinCm, kBucketCm), 0, kBucketCount - 1); +} + +int64_t eval_quad(const Shape& s, const int64_t d[3]) { + int64_t value = 0; + value += s.q[0][0] * d[0] * d[0]; + value += s.q[1][1] * d[1] * d[1]; + value += s.q[2][2] * d[2] * d[2]; + value += 2 * s.q[0][1] * d[0] * d[1]; + value += 2 * s.q[0][2] * d[0] * d[2]; + value += 2 * s.q[1][2] * d[1] * d[2]; + return value; +} + +bool contains_sub(const Shape& s, const int64_t pSub[3]) { + int64_t d[3] = {pSub[0] - static_cast(s.c[0]) * kSubPerCm, + pSub[1] - static_cast(s.c[1]) * kSubPerCm, + pSub[2] - static_cast(s.c[2]) * kSubPerCm}; + return eval_quad(s, d) <= kQScale * kSubPerCm * kSubPerCm; +} + +bool shape_aabb_contains_sub(const Shape& s, const int64_t pSub[3]) { + for (int a = 0; a < 3; ++a) { + if (pSub[a] < static_cast(s.lo[a]) * kSubPerCm + || pSub[a] > static_cast(s.hi[a]) * kSubPerCm) return false; + } + return true; +} + +bool point_rock(const Corpus& c, const int64_t pSub[3]) { + const int xcm = static_cast(floor_div(static_cast(pSub[0]), static_cast(kSubPerCm))); + const int ycm = static_cast(floor_div(static_cast(pSub[1]), static_cast(kSubPerCm))); + const int zcm = static_cast(floor_div(static_cast(pSub[2]), static_cast(kSubPerCm))); + const int bx = std::clamp(floor_div(xcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int by = std::clamp(floor_div(ycm - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int bz = std::clamp(floor_div(zcm - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + bool rock = true; + const auto& bucket = c.bins3d[(bz * kBucketCount + by) * kBucketCount + bx].ids; + for (uint32_t id : bucket) { + const Shape& s = c.shapes[id]; + if (shape_aabb_contains_sub(s, pSub) && contains_sub(s, pSub)) rock = (s.op == Operation::AddRock); + } + return rock; +} + +struct Interval { int64_t loSub, hiSub; int loSample, hiSample; }; + +int64_t eval_line(const Shape& s, int axis, int64_t variableSub, + const int64_t fixedSub[3]) { + int64_t p[3] = {fixedSub[0], fixedSub[1], fixedSub[2]}; + p[axis] = variableSub; + int64_t d[3] = {p[0] - static_cast(s.c[0]) * kSubPerCm, + p[1] - static_cast(s.c[1]) * kSubPerCm, + p[2] - static_cast(s.c[2]) * kSubPerCm}; + return eval_quad(s, d) - kQScale * kSubPerCm * kSubPerCm; +} + +bool solve_shape_line(const Shape& s, int axis, const int64_t fixedSub[3], Interval& out) { + const int a = axis; + const int b = (axis + 1) % 3; + const int dAxis = (axis + 2) % 3; + const int64_t da = fixedSub[b] - static_cast(s.c[b]) * kSubPerCm; + const int64_t db = fixedSub[dAxis] - static_cast(s.c[dAxis]) * kSubPerCm; + const int64_t A = s.q[a][a]; + const int64_t B = 2 * (s.q[a][b] * da + s.q[a][dAxis] * db); + if (A <= 0) return false; + const int64_t vertexFloor = static_cast(s.c[axis]) * kSubPerCm + + floor_div64(-B, 2 * A); + const int64_t minSub = static_cast(s.lo[a]) * kSubPerCm; + const int64_t maxSub = static_cast(s.hi[a]) * kSubPerCm; + int64_t center = std::clamp(vertexFloor, minSub, maxSub); + int64_t best = center; + int64_t bestValue = eval_line(s, axis, center, fixedSub); + for (int64_t q = center - 1; q <= center + 1; ++q) { + if (q < minSub || q > maxSub) continue; + const int64_t v = eval_line(s, axis, q, fixedSub); + if (v < bestValue) { bestValue = v; best = q; } + } + if (bestValue > 0) return false; // exact integer predicate; no floating sign decision + int64_t lo = minSub, hi = best; + while (lo < hi) { + const int64_t mid = lo + (hi - lo) / 2; + if (eval_line(s, axis, mid, fixedSub) <= 0) hi = mid; + else lo = mid + 1; + } + const int64_t first = lo; + lo = best; hi = maxSub; + while (lo < hi) { + const int64_t mid = lo + (hi - lo + 1) / 2; + if (eval_line(s, axis, mid, fixedSub) <= 0) lo = mid; + else hi = mid - 1; + } + const int64_t last = lo; + if (eval_line(s, axis, first, fixedSub) > 0 || eval_line(s, axis, last, fixedSub) > 0) return false; + const int64_t baseSub = static_cast(kGridMinCm) * kSubPerCm; + const int64_t stepSub = static_cast(kCellStepCm) * kSubPerCm; + int firstSample = static_cast(ceil_div(static_cast(first - baseSub), static_cast(stepSub))); + int lastSample = static_cast(floor_div(static_cast(last - baseSub), static_cast(stepSub))); + firstSample = std::max(firstSample, 0); + lastSample = std::min(lastSample, kNodes - 1); + if (firstSample <= lastSample) { + const int64_t firstP = baseSub + firstSample * stepSub; + if (eval_line(s, axis, firstP, fixedSub) > 0) ++firstSample; + if (firstSample <= lastSample && eval_line(s, axis, baseSub + lastSample * stepSub, fixedSub) > 0) --lastSample; + } else { firstSample = 1; lastSample = 0; } + out = {first, last, firstSample, lastSample}; + return true; +} + +uint64_t compose_membership(const Corpus& c, const std::array& ids, + uint32_t count, uint64_t mask) { + bool rock = true; + while (mask) { + const unsigned bit = std::countr_zero(mask); + const uint64_t b = uint64_t{1} << bit; + if (bit < count) rock = (c.shapes[ids[bit]].op == Operation::AddRock); + mask &= ~b; + } + return rock ? 1 : 0; +} + +void set_word_run(uint64_t* words, int begin, int end, bool rock) { + if (!rock || begin >= end) return; + int firstWord = begin >> 6; + int lastWord = (end - 1) >> 6; + const int firstBit = begin & 63; + const int lastBit = (end - 1) & 63; + if (firstWord == lastWord) { + const uint64_t low = kMask64 << firstBit; + const uint64_t high = (lastBit == 63) ? kMask64 : ((uint64_t{1} << (lastBit + 1)) - 1); + words[firstWord] |= low & high; + return; + } + words[firstWord] |= kMask64 << firstBit; + for (int w = firstWord + 1; w < lastWord; ++w) words[w] = kMask64; + words[lastWord] |= (lastBit == 63) ? kMask64 : ((uint64_t{1} << (lastBit + 1)) - 1); +} + +void parallel_ranges(size_t count, int workers, const std::function& fn) { + std::vector pool; + pool.reserve(workers > 1 ? workers : 0); + const size_t chunk = (count + static_cast(workers) - 1) / static_cast(workers); + for (int w = 0; w < workers; ++w) { + const size_t begin = std::min(count, static_cast(w) * chunk); + const size_t end = std::min(count, begin + chunk); + if (workers == 1) { fn(w, begin, end); break; } + pool.emplace_back([&, w, begin, end] { fn(w, begin, end); }); + } + for (auto& t : pool) t.join(); +} + +uint32_t line_id(int axis, int x, int y, int z) { + if (axis == 0) return static_cast(z * kNodes + y); // X varies + if (axis == 1) return static_cast(x * kNodes + z); // Y varies + return static_cast(y * kNodes + x); // Z varies +} + +void decode_line(int axis, uint32_t line, int& a, int& b) { + (void)axis; + a = static_cast(line % kNodes); + b = static_cast(line / kNodes); + // axis 0: a=y,b=z; axis 1: a=x,b=z; axis 2: a=x,b=y. +} + +AxisData solve_axis(const Corpus& c, int axis, int workers, AxisStats& stats, bool& overflow) { + AxisData result; + const int lineCount = kNodes * kNodes; + result.signs.assign(static_cast(lineCount) * kWordsPerLine, 0); + result.lines.resize(lineCount); + std::vector local(static_cast(workers)); + std::atomic lineOverflow{false}; + std::vector> candidateStats(workers), intervalStats(workers), crossingStats(workers), runStats(workers); + const auto& bins = c.projections[axis]; + const int axis1 = (axis + 1) % 3, axis2 = (axis + 2) % 3; + const int64_t domainMinSub = static_cast(kGridMinCm) * kSubPerCm; + const int64_t domainMaxSub = static_cast(kGridMaxCm) * kSubPerCm; + + parallel_ranges(lineCount, workers, [&](int worker, size_t begin, size_t end) { + auto& outCross = local[worker].crossings; + auto& cstats = candidateStats[worker]; + auto& istats = intervalStats[worker]; + auto& xstats = crossingStats[worker]; + auto& rstats = runStats[worker]; + cstats.reserve(end - begin); istats.reserve(end - begin); + xstats.reserve(end - begin); rstats.reserve(end - begin); + std::array ids{}; + std::array intervals{}; + std::array valid{}; + std::array enters{}, exits{}; + std::array lineWords{}; + struct Ev { int64_t q; uint8_t bit; uint8_t enter; }; + std::array events{}; + for (size_t lineIndex = begin; lineIndex < end; ++lineIndex) { + int p0, p1; decode_line(axis, static_cast(lineIndex), p0, p1); + std::array localCm{}; + localCm[axis1] = kGridMinCm + p0 * kCellStepCm; + localCm[axis2] = kGridMinCm + p1 * kCellStepCm; + const int b0 = std::clamp(floor_div(localCm[axis1] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const int b1 = std::clamp(floor_div(localCm[axis2] - kGridMinCm, kBucketCm), 0, kBucketCount - 1); + const auto& bucket = bins.ids[b1 * kBucketCount + b0]; + uint32_t candidateCount = 0; + for (uint32_t id : bucket) { + const Shape& s = c.shapes[id]; + if (localCm[axis1] < s.lo[axis1] || localCm[axis1] > s.hi[axis1] + || localCm[axis2] < s.lo[axis2] || localCm[axis2] > s.hi[axis2] + || s.hi[axis] < kGridMinCm || s.lo[axis] > kGridMaxCm) continue; + if (candidateCount < kShapeLimitPerLine) ids[candidateCount++] = id; + else { lineOverflow.store(true, std::memory_order_relaxed); } + } + if (candidateCount > kShapeLimitPerLine) candidateCount = kShapeLimitPerLine; + cstats.push_back(static_cast(candidateCount)); + std::fill(enters.begin(), enters.end(), 0); + std::fill(exits.begin(), exits.end(), 0); + std::fill(lineWords.begin(), lineWords.end(), 0); + std::fill(valid.begin(), valid.end(), 0); + uint32_t intervalCount = 0; + uint32_t eventCount = 0; + int64_t fixedSub[3] = {0,0,0}; + fixedSub[axis1] = static_cast(localCm[axis1]) * kSubPerCm; + fixedSub[axis2] = static_cast(localCm[axis2]) * kSubPerCm; + for (uint32_t i = 0; i < candidateCount; ++i) { + const Shape& s = c.shapes[ids[i]]; + if (!solve_shape_line(s, axis, fixedSub, intervals[i])) continue; + valid[i] = 1; + ++intervalCount; + const Interval& in = intervals[i]; + if (in.loSample <= in.hiSample) { + enters[in.loSample] |= uint64_t{1} << i; + exits[in.hiSample + 1] |= uint64_t{1} << i; + } + if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub) { + // The line begins inside this canonical interval; represented by an entry at index 0. + enters[0] |= uint64_t{1} << i; + } + if (in.loSub > domainMinSub && in.loSub <= domainMaxSub) + events[eventCount++] = {in.loSub, static_cast(i), 1}; + const int64_t exitQ = in.hiSub + 1; + if (exitQ > domainMinSub && exitQ <= domainMaxSub + 1) + events[eventCount++] = {exitQ, static_cast(i), 0}; + } + istats.push_back(static_cast(intervalCount)); + + uint64_t active = 0; + // Membership at the first sample is exact; interval endpoints are canonical integers. + for (uint32_t i = 0; i < candidateCount; ++i) { + if (!valid[i]) continue; + const Interval& in = intervals[i]; + if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub) + active |= uint64_t{1} << i; + } + bool rock = compose_membership(c, ids, candidateCount, active) != 0; + int runBegin = 0; + uint32_t runCount = 1; + for (int k = 0; k <= kNodes; ++k) { + const uint64_t before = active; + active |= enters[k]; + active &= ~exits[k]; + if (k == 0) { + rock = compose_membership(c, ids, candidateCount, active) != 0; + continue; + } + if (before != active) { + const bool nextRock = compose_membership(c, ids, candidateCount, active) != 0; + if (nextRock != rock) { + set_word_run(lineWords.data(), runBegin, k, rock); + runBegin = k; + rock = nextRock; + ++runCount; + } + } + } + set_word_run(lineWords.data(), runBegin, kNodes, rock); + std::copy(lineWords.begin(), lineWords.end(), + result.signs.begin() + static_cast(lineIndex) * kWordsPerLine); + rstats.push_back(static_cast(runCount)); + + std::sort(events.begin(), events.begin() + eventCount, + [](const Ev& a, const Ev& b) { return a.q < b.q || (a.q == b.q && a.bit < b.bit); }); + const size_t lineCrossStart = outCross.size(); + uint64_t continuousActive = 0; + for (uint32_t i = 0; i < candidateCount; ++i) { + if (!valid[i]) continue; + const Interval& in = intervals[i]; + if (in.loSub <= domainMinSub && in.hiSub >= domainMinSub) + continuousActive |= uint64_t{1} << i; + } + for (uint32_t e = 0; e < eventCount;) { + uint32_t next = e + 1; + while (next < eventCount && events[next].q == events[e].q) ++next; + const bool beforeRock = compose_membership(c, ids, candidateCount, continuousActive) != 0; + for (uint32_t j = e; j < next; ++j) { + const uint64_t bit = uint64_t{1} << events[j].bit; + if (events[j].enter) continuousActive |= bit; + else continuousActive &= ~bit; + } + const bool afterRock = compose_membership(c, ids, candidateCount, continuousActive) != 0; + if (beforeRock != afterRock) { + uint32_t feature = ids[events[e].bit]; + for (uint32_t j = e; j < next; ++j) feature = std::min(feature, ids[events[j].bit]); + const int64_t p32 = events[e].q * 2 - 1; + if (p32 >= static_cast(kGridMinCm) * 32 + && p32 <= static_cast(kGridMaxCm) * 32 + && outCross.size() < std::numeric_limits::max()) + outCross.push_back({static_cast(p32), feature, + static_cast(beforeRock), static_cast(afterRock)}); + } + e = next; + } + const size_t lineCrossCount = outCross.size() - lineCrossStart; + xstats.push_back(static_cast(std::min(lineCrossCount, 65535))); + result.lines[lineIndex].candidates = static_cast(candidateCount); + result.lines[lineIndex].primitiveIntervals = static_cast(intervalCount); + result.lines[lineIndex].occupancyRuns = static_cast(runCount); + result.lines[lineIndex].count = static_cast(std::min(lineCrossCount, 65535)); + } + }); + overflow = overflow || lineOverflow.load(std::memory_order_relaxed); + + // Concatenate disjoint worker event streams in line order; each line range gets stable offsets. + size_t totalCross = 0; + std::vector workerBase(static_cast(workers)); + for (int w = 0; w < workers; ++w) { workerBase[w] = totalCross; totalCross += local[w].crossings.size(); } + result.crossings.resize(totalCross); + const size_t chunk = (static_cast(lineCount) + workers - 1) / workers; + for (int w = 0; w < workers; ++w) { + const size_t begin = std::min(static_cast(lineCount), static_cast(w) * chunk); + const size_t end = std::min(static_cast(lineCount), begin + chunk); + size_t offset = workerBase[w]; + // During the worker pass, crossing records for a line are appended sequentially. + for (size_t li = begin; li < end; ++li) { + LineRange& lr = result.lines[li]; + lr.offset = static_cast(offset); + offset += lr.count; + } + std::copy(local[w].crossings.begin(), local[w].crossings.end(), result.crossings.begin() + workerBase[w]); + } + // The stats vectors were produced in worker-range order; collect and summarize them. + auto summarize = [](std::vector values, double& p50, double& p95, uint32_t& vmax, uint64_t& total) { + if (values.empty()) return; + std::sort(values.begin(), values.end()); + total = std::accumulate(values.begin(), values.end(), uint64_t{0}); + vmax = values.back(); + const size_t i50 = static_cast(0.50 * (values.size() - 1)); + const size_t i95 = static_cast(0.95 * (values.size() - 1)); + p50 = values[i50]; p95 = values[i95]; + }; + std::vector cv, iv, xv, rv; + cv.reserve(lineCount); iv.reserve(lineCount); xv.reserve(lineCount); rv.reserve(lineCount); + for (const auto& v : candidateStats) cv.insert(cv.end(), v.begin(), v.end()); + for (const auto& v : intervalStats) iv.insert(iv.end(), v.begin(), v.end()); + for (const auto& v : crossingStats) xv.insert(xv.end(), v.begin(), v.end()); + for (const auto& v : runStats) rv.insert(rv.end(), v.begin(), v.end()); + summarize(std::move(cv), stats.candidateP50, stats.candidateP95, stats.candidateMax, stats.candidateTotal); + summarize(std::move(iv), stats.intervalP50, stats.intervalP95, stats.intervalMax, stats.intervalTotal); + summarize(std::move(xv), stats.crossingP50, stats.crossingP95, stats.crossingMax, stats.crossingTotal); + summarize(std::move(rv), stats.runsP50, stats.runsP95, stats.runsMax, stats.runsTotal); + stats.intervalMax = std::max(stats.intervalMax, 0); + return result; +} + +uint64_t get_sign(const AxisData& axis, uint32_t line, int sample) { + return (axis.signs[static_cast(line) * kWordsPerLine + (sample >> 6)] >> (sample & 63)) & 1; +} + +uint64_t extract64(const AxisData& a, uint32_t line, int bit) { + const uint64_t* words = a.signs.data() + static_cast(line) * kWordsPerLine; + const int wi = bit >> 6, shift = bit & 63; + uint64_t v = words[wi] >> shift; + if (shift != 0 && wi + 1 < kWordsPerLine) v |= words[wi + 1] << (64 - shift); + return v; +} + +uint64_t index_hash_bytes(uint64_t h, const void* data, size_t bytes) { + const auto* p = static_cast(data); + for (size_t i = 0; i < bytes; ++i) { h ^= p[i]; h *= 1099511628211ull; } + return h; +} + +uint32_t owner_page_for_edge(int axis, int x, int y, int z) { + int n[3] = {x,y,z}; + int cell[3]{}; + for (int a = 0; a < 3; ++a) { + int ci; + if (a == axis) ci = n[a] - 1; + else { + const int coreNode = n[a] - kHalo; + if (coreNode > 0 && coreNode < kCoreCells && coreNode % kPageCells == 0) ci = coreNode - 1; + else ci = coreNode; + } + ci = std::clamp(ci, 0, kCoreCells - 1); + cell[a] = ci / kPageCells; + } + return static_cast((cell[2] * kPagesPerAxis + cell[1]) * kPagesPerAxis + cell[0]); +} + +uint64_t edge_key(int axis, int x, int y, int z) { + return (((static_cast(axis) * kNodes + static_cast(x)) * kNodes + + static_cast(y)) * kNodes + static_cast(z)); +} + +struct EdgeDesc { int axis, x, y, z, c0, c1; }; +constexpr std::array kEdges = {{ + {0,0,0,0,0,1},{1,1,0,0,1,2},{0,0,1,0,2,3},{1,0,0,0,3,0}, + {0,0,0,1,4,5},{1,1,0,1,5,6},{0,0,1,1,6,7},{1,0,0,1,7,4}, + {2,0,0,0,0,4},{2,1,0,0,1,5},{2,1,1,0,2,6},{2,0,1,0,3,7} +}}; +constexpr int kCornerOffset[8][3] = {{0,0,0},{1,0,0},{1,1,0},{0,1,0},{0,0,1},{1,0,1},{1,1,1},{0,1,1}}; +constexpr int kFaceCorners[6][4] = {{0,1,2,3},{4,5,6,7},{0,1,5,4},{3,2,6,7},{0,3,7,4},{1,2,6,5}}; +constexpr int kFaceEdges[6][4] = {{0,1,2,3},{4,5,6,7},{0,9,4,8},{2,10,6,11},{3,11,7,8},{1,10,5,9}}; + +struct CrossingRef { Crossing c; uint32_t line; }; + +bool find_edge_crossing(const AxisData& axisData, int axis, int x, int y, int z, + bool leftRock, bool rightRock, Crossing& out, uint64_t& multi) { + const uint32_t line = line_id(axis, x, y, z); + const LineRange& lr = axisData.lines[line]; + const int node = (axis == 0 ? x : axis == 1 ? y : z); + const int localCm = kGridMinCm + node * kCellStepCm; + const int64_t start32 = static_cast(localCm) * kSurfaceSubPerCm; + const int64_t end32 = start32 + kCellStepCm * kSurfaceSubPerCm; + uint32_t found = 0; + for (uint32_t i = 0; i < lr.count; ++i) { + const Crossing& c = axisData.crossings[lr.offset + i]; + if (c.positionSub32 <= start32 || c.positionSub32 >= end32) continue; + if (c.beforeRock == leftRock && c.afterRock == rightRock) { + out = c; ++found; + } + } + if (found > 1) ++multi; + return found != 0; +} + +void normal_for_crossing(const Shape& s, const Crossing& c, int axis, int local[3], + double posCm, double outN[3]) { + double d[3] = {static_cast(local[0] - s.c[0]), + static_cast(local[1] - s.c[1]), + static_cast(local[2] - s.c[2])}; + d[axis] = posCm - s.c[axis]; + double g[3]{}; + for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) g[i] += 2.0 * static_cast(s.q[i][j]) * d[j]; + const bool wantPositiveAxis = c.beforeRock && !c.afterRock; + const double sign = ((g[axis] >= 0.0) == wantPositiveAxis) ? 1.0 : -1.0; + double len2 = 0.0; + for (int i = 0; i < 3; ++i) { outN[i] = g[i] * sign; len2 += outN[i] * outN[i]; } + if (len2 <= 1.0e-28) { outN[0] = (axis == 0 ? 1.0 : 0.0); outN[1] = (axis == 1 ? 1.0 : 0.0); outN[2] = (axis == 2 ? 1.0 : 0.0); return; } + const double inv = 1.0 / std::sqrt(len2); + for (double& v : std::span(outN,3)) v *= inv; +} + +int axis_for_edge(const EdgeDesc& e, int x, int y, int z) { return e.axis; } + +void write_vertex_hash(uint64_t& hash, const Vertex& v) { hash = index_hash_bytes(hash, &v, sizeof(v)); } + +MeshOutput emit_near_mesh(const Corpus& c, const AxisData axes[3], const AxisData& zSigns, + const std::vector& active, int workers) { + (void)workers; + MeshOutput out; + std::unordered_map edgeToVertex; + edgeToVertex.reserve(700000); + out.pages.reserve(kPagesPerAxis * kPagesPerAxis * kPagesPerAxis); + std::array cornerSigns{}; + std::array local{}; + uint64_t multiEdges = 0; + auto add_vertex = [&](const EdgeDesc& e, int cellX, int cellY, int cellZ, + const Crossing& crossing) -> uint32_t { + const int ex = cellX + e.x, ey = cellY + e.y, ez = cellZ + e.z; + const uint64_t key = edge_key(e.axis, ex, ey, ez); + auto it = edgeToVertex.find(key); + if (it != edgeToVertex.end()) return it->second; + const Shape& s = c.shapes[crossing.shapeId]; + int p[3] = {kGridMinCm + ex * kCellStepCm, + kGridMinCm + ey * kCellStepCm, + kGridMinCm + ez * kCellStepCm}; + const double localPosCm = crossing.positionSub32 / static_cast(kSurfaceSubPerCm); + p[e.axis] = static_cast(std::floor(localPosCm)); + double n[3]; normal_for_crossing(s, crossing, e.axis, p, localPosCm, n); + const double world[3] = { + kWorldOriginCm[0] + (e.axis == 0 ? localPosCm : (kGridMinCm + ex * kCellStepCm)), + kWorldOriginCm[1] + (e.axis == 1 ? localPosCm : (kGridMinCm + ey * kCellStepCm)), + kWorldOriginCm[2] + (e.axis == 2 ? localPosCm : (kGridMinCm + ez * kCellStepCm))}; + Vertex v{static_cast(world[0] * 0.01), static_cast(world[1] * 0.01), + static_cast(world[2] * 0.01), static_cast(n[0]), + static_cast(n[1]), static_cast(n[2])}; + const uint32_t owner = owner_page_for_edge(e.axis, ex, ey, ez); + const uint32_t index = static_cast(out.vertices.size()); + out.vertices.push_back({key, owner, v}); + edgeToVertex.emplace(key, index); + const int edgeNodes[3] = {ex,ey,ez}; + for (int a = 0; a < 3; ++a) { + const int coreNode = edgeNodes[a] - kHalo; + if (coreNode > 0 && coreNode < kCoreCells && coreNode % kPageCells == 0) { + ++out.pageBorderVertices; + break; + } + } + return index; + }; + auto emit_triangle = [&](uint32_t a, uint32_t b, uint32_t d) { + const Vertex& va = out.vertices[a].v; const Vertex& vb = out.vertices[b].v; const Vertex& vd = out.vertices[d].v; + const double ux = vb.x - va.x, uy = vb.y - va.y, uz = vb.z - va.z; + const double vx = vd.x - va.x, vy = vd.y - va.y, vz = vd.z - va.z; + const double cx = uy * vz - uz * vy, cy = uz * vx - ux * vz, cz = ux * vy - uy * vx; + const double nx = va.nx + vb.nx + vd.nx, ny = va.ny + vb.ny + vd.ny, nz = va.nz + vb.nz + vd.nz; + if (cx * nx + cy * ny + cz * nz < 0.0) std::swap(b, d); + out.indices.push_back(a); out.indices.push_back(b); out.indices.push_back(d); + }; + auto shape_hits_lattice = [&](const Shape& s) { + int lo[3], hi[3]; + for (int a = 0; a < 3; ++a) { + lo[a] = std::clamp(ceil_div(s.lo[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1); + hi[a] = std::clamp(floor_div(s.hi[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1); + } + for (int z = lo[2]; z <= hi[2]; ++z) for (int y = lo[1]; y <= hi[1]; ++y) + for (int x = lo[0]; x <= hi[0]; ++x) { + const int64_t p[3] = { + static_cast(kGridMinCm + x * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + z * kCellStepCm) * kSubPerCm}; + if (contains_sub(s, p)) return true; + } + return false; + }; + for (int pz = 0; pz < kPagesPerAxis; ++pz) for (int py = 0; py < kPagesPerAxis; ++py) + for (int px = 0; px < kPagesPerAxis; ++px) { + const uint32_t page = static_cast((pz * kPagesPerAxis + py) * kPagesPerAxis + px); + const uint32_t start = static_cast(out.indices.size()); + const size_t vstart = out.vertices.size(); + for (int iy = py * kPageCells; iy < (py + 1) * kPageCells; ++iy) + for (int ix = px * kPageCells; ix < (px + 1) * kPageCells; ++ix) { + const int nx = ix + kHalo, ny = iy + kHalo; + for (int zb = pz; zb <= pz; ++zb) { + uint64_t mask = active[(static_cast(ix) * kCoreCells + iy) * 4 + zb]; + while (mask) { + const unsigned bz = std::countr_zero(mask); + mask &= mask - 1; + const int iz = zb * 64 + static_cast(bz); + const int cellX = nx, cellY = ny, cellZ = iz + kHalo; + bool rock[8]{}; + for (int k = 0; k < 8; ++k) { + const int x = cellX + kCornerOffset[k][0]; + const int y = cellY + kCornerOffset[k][1]; + const int z = cellZ + kCornerOffset[k][2]; + rock[k] = get_sign(zSigns, line_id(2, x, y, z), z); + } + bool edgeCross[12]{}; + uint32_t edgeVertex[12]{}; + for (int ei = 0; ei < 12; ++ei) { + const EdgeDesc& e = kEdges[ei]; + if (rock[e.c0] == rock[e.c1]) continue; + const int sx = cellX + e.x, sy = cellY + e.y, sz = cellZ + e.z; + const bool c0IsLow = kCornerOffset[e.c0][e.axis] < kCornerOffset[e.c1][e.axis]; + const bool lowRock = c0IsLow ? rock[e.c0] : rock[e.c1]; + const bool highRock = c0IsLow ? rock[e.c1] : rock[e.c0]; + Crossing crossing; + if (!find_edge_crossing(axes[e.axis], e.axis, sx, sy, sz, + lowRock, highRock, crossing, multiEdges)) { + ++out.missingAnalyticCrossings; + continue; + } + edgeCross[ei] = true; + edgeVertex[ei] = add_vertex(e, cellX, cellY, cellZ, crossing); + } + std::array,12> adj{}; + std::array degree{}; + auto connect = [&](int a, int b) { + if (degree[a] < 2 && degree[b] < 2) { + adj[a][degree[a]++] = b; adj[b][degree[b]++] = a; + } + }; + for (int f = 0; f < 6; ++f) { + int crossed[4], nCross = 0; + for (int k = 0; k < 4; ++k) if (edgeCross[kFaceEdges[f][k]]) crossed[nCross++] = k; + if (nCross == 2) connect(kFaceEdges[f][crossed[0]], kFaceEdges[f][crossed[1]]); + else if (nCross == 4) { + ++out.ambiguousFaces; + int pSub[3] = {kGridMinCm + cellX * kCellStepCm, + kGridMinCm + cellY * kCellStepCm, + kGridMinCm + cellZ * kCellStepCm}; + int sumOffset[3]{}; + for (int k = 0; k < 4; ++k) for (int a = 0; a < 3; ++a) + sumOffset[a] += kCornerOffset[kFaceCorners[f][k]][a]; + int64_t q[3]; + for (int a = 0; a < 3; ++a) q[a] = (static_cast(pSub[a]) * 4 + sumOffset[a]) * 4; + const bool centerRock = point_rock(c, q); + const bool s0 = rock[kFaceCorners[f][0]]; + if (centerRock == s0) { + connect(kFaceEdges[f][0], kFaceEdges[f][1]); + connect(kFaceEdges[f][2], kFaceEdges[f][3]); + } else { + connect(kFaceEdges[f][3], kFaceEdges[f][0]); + connect(kFaceEdges[f][1], kFaceEdges[f][2]); + } + } + } + for (int ei = 0; ei < 12; ++ei) + if (edgeCross[ei] && degree[ei] != 2) ++out.contourDegreeFaults; + std::array visited{}; + for (int startEdge = 0; startEdge < 12; ++startEdge) { + if (!edgeCross[startEdge] || visited[startEdge] || degree[startEdge] != 2) continue; + std::vector loop; + int prev = -1, cur = startEdge; + for (int guard = 0; guard < 16; ++guard) { + if (visited[cur]) break; + visited[cur] = 1; loop.push_back(cur); + const int next = adj[cur][0] == prev ? adj[cur][1] : adj[cur][0]; + prev = cur; cur = next; + if (cur == startEdge) break; + } + if (cur != startEdge) ++out.openContourLoops; + if (loop.size() >= 3) for (size_t i = 1; i + 1 < loop.size(); ++i) + emit_triangle(edgeVertex[loop[0]], edgeVertex[loop[i]], edgeVertex[loop[i + 1]]); + } + } + } + } + out.pages.push_back({page, start, static_cast(out.indices.size() - start), + static_cast(vstart), static_cast(out.vertices.size() - vstart)}); + } + + // Explicit surface fallback for sub-cell strata missed by every structural lattice point. + constexpr int rings = 8, sectors = 24; + for (const Shape& s : c.shapes) { + if (s.kind != FeatureKind::Stratum || s.radius[2] >= kCellStepCm / 2) continue; + ++out.thinFeaturesDetected; + if (shape_hits_lattice(s)) continue; + ++out.thinFeaturesHiddenInLattice; + const uint32_t page = owner_page_for_edge(0, std::clamp(kHalo + (s.c[0] - kGridMinCm) / kCellStepCm, 1, kCoreCells), + std::clamp(kHalo + (s.c[1] - kGridMinCm) / kCellStepCm, 1, kCoreCells), + std::clamp(kHalo + (s.c[2] - kGridMinCm) / kCellStepCm, 1, kCoreCells)); + const uint32_t start = static_cast(out.indices.size()); + const uint32_t base = static_cast(out.vertices.size()); + std::vector> localPoints; + std::vector> surfaceNormals; + localPoints.reserve((rings + 1) * sectors); + surfaceNormals.reserve((rings + 1) * sectors); + for (int r = 0; r <= rings; ++r) { + const double phi = -1.5707963267948966 + 3.1415926535897932 * r / rings; + for (int a = 0; a < sectors; ++a) { + const double theta = 6.2831853071795864 * a / sectors; + const double u[3] = {std::cos(phi) * std::cos(theta), std::cos(phi) * std::sin(theta), std::sin(phi)}; + double localP[3]{}; + for (int axis = 0; axis < 3; ++axis) { + for (int k = 0; k < 3; ++k) + localP[axis] += static_cast(s.rot[axis][k]) / 32768.0 * s.radius[k] * u[k]; + } + double n[3]{}; + for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) + n[i] += 2.0 * s.q[i][j] * localP[j]; + double len = std::sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); + if (len > 0) for (double& v : n) v /= len; + if (s.op == Operation::CarveAir) for (double& v : n) v = -v; + localPoints.push_back({localP[0], localP[1], localP[2]}); + surfaceNormals.push_back({n[0],n[1],n[2]}); + const double wx = (kWorldOriginCm[0] + s.c[0] + localP[0]) * 0.01; + const double wy = (kWorldOriginCm[1] + s.c[1] + localP[1]) * 0.01; + const double wz = (kWorldOriginCm[2] + s.c[2] + localP[2]) * 0.01; + const uint64_t thinKey = (uint64_t{1} << 63) | (static_cast(s.id) << 32) + | (static_cast(r) << 16) | static_cast(a); + out.vertices.push_back({thinKey, page, + {static_cast(wx),static_cast(wy),static_cast(wz), + static_cast(n[0]),static_cast(n[1]),static_cast(n[2])}}); + } + } + for (int r = 0; r < rings; ++r) for (int a = 0; a < sectors; ++a) { + const int an = (a + 1) % sectors; + const uint32_t v00 = base + r * sectors + a, v01 = base + r * sectors + an; + const uint32_t v10 = base + (r + 1) * sectors + a, v11 = base + (r + 1) * sectors + an; + const uint32_t tris[2][3] = {{v00,v10,v01},{v01,v10,v11}}; + for (int ti = 0; ti < 2; ++ti) { + if ((r == 0 && ti == 0) || (r == rings - 1 && ti == 1)) continue; + double p[3]{}, n[3]{}; + for (int v : tris[ti]) for (int a3 = 0; a3 < 3; ++a3) { + p[a3] += (s.c[a3] + localPoints[v - base][a3]) / 3.0; + n[a3] += surfaceNormals[v - base][a3] / 3.0; + } + const double nlen = std::sqrt(n[0]*n[0] + n[1]*n[1] + n[2]*n[2]); + if (nlen > 0) for (double& v : n) v /= nlen; + int64_t plus[3], minus[3]; + for (int a3 = 0; a3 < 3; ++a3) { + plus[a3] = std::llround((p[a3] + n[a3]) * kSubPerCm); + minus[a3] = std::llround((p[a3] - n[a3]) * kSubPerCm); + } + if (point_rock(c, plus) == point_rock(c, minus)) continue; + emit_triangle(tris[ti][0], tris[ti][1], tris[ti][2]); + ++out.explicitThinTriangles; + } + } + if (out.indices.size() != start) { + ++out.thinFeaturesMeshed; + out.pages.push_back({page, start, static_cast(out.indices.size() - start), base, + static_cast(out.vertices.size() - base)}); + } else { + out.vertices.resize(base); + } + } + + // Stable ownership and vertex order by (owner page, edge key); index order remains page-owned. + std::vector permutation(out.vertices.size()); + std::iota(permutation.begin(), permutation.end(), 0u); + std::sort(permutation.begin(), permutation.end(), [&](uint32_t a, uint32_t b) { + if (out.vertices[a].ownerPage != out.vertices[b].ownerPage) + return out.vertices[a].ownerPage < out.vertices[b].ownerPage; + return out.vertices[a].key < out.vertices[b].key; + }); + std::vector remap(out.vertices.size()); + std::vector sortedVertices; + sortedVertices.reserve(out.vertices.size()); + for (uint32_t i = 0; i < permutation.size(); ++i) { + remap[permutation[i]] = i; + sortedVertices.push_back(out.vertices[permutation[i]]); + } + for (uint32_t& index : out.indices) index = remap[index]; + out.vertices.swap(sortedVertices); + for (PageRange& p : out.pages) { + const auto first = std::lower_bound(out.vertices.begin(), out.vertices.end(), p.page, + [](const TempVertex& v, uint32_t owner) { return v.ownerPage < owner; }); + const auto last = std::upper_bound(first, out.vertices.end(), p.page, + [](uint32_t owner, const TempVertex& v) { return owner < v.ownerPage; }); + p.vertexStart = static_cast(first - out.vertices.begin()); + p.vertexCount = static_cast(last - first); + } + out.triangles = out.indices.size() / 3; + out.multiCrossEdges = multiEdges; + uint64_t h = 1469598103934665603ull; + for (const TempVertex& v : out.vertices) write_vertex_hash(h, v.v); + h = index_hash_bytes(h, out.indices.data(), out.indices.size() * sizeof(uint32_t)); + for (const PageRange& p : out.pages) h = index_hash_bytes(h, &p, sizeof(p)); + out.hash = h; + out.bytes = out.vertices.size() * sizeof(Vertex) + out.indices.size() * sizeof(uint32_t) + + out.pages.size() * sizeof(PageRange); + return out; +} + +uint64_t active_cell_masks(const AxisData& zAxis, std::vector& active) { + const size_t count = static_cast(kCoreCells) * kCoreCells * 4; + active.assign(count, 0); + uint64_t activeCount = 0; + for (int x = 0; x < kCoreCells; ++x) for (int y = 0; y < kCoreCells; ++y) { + const int nx = x + kHalo, ny = y + kHalo; + const uint32_t line00 = line_id(2, nx, ny, 0), line10 = line_id(2, nx + 1, ny, 0); + const uint32_t line01 = line_id(2, nx, ny + 1, 0), line11 = line_id(2, nx + 1, ny + 1, 0); + for (int block = 0; block < 4; ++block) { + const int bit = 1 + block * 64; + const uint64_t a0 = extract64(zAxis, line00, bit), a1 = extract64(zAxis, line10, bit); + const uint64_t a2 = extract64(zAxis, line01, bit), a3 = extract64(zAxis, line11, bit); + const uint64_t b0 = extract64(zAxis, line00, bit + 1), b1 = extract64(zAxis, line10, bit + 1); + const uint64_t b2 = extract64(zAxis, line01, bit + 1), b3 = extract64(zAxis, line11, bit + 1); + const uint64_t any = a0 | a1 | a2 | a3 | b0 | b1 | b2 | b3; + const uint64_t all = a0 & a1 & a2 & a3 & b0 & b1 & b2 & b3; + const uint64_t mask = any & ~all; + active[(static_cast(x) * kCoreCells + y) * 4 + block] = mask; + activeCount += std::popcount(mask); + } + } + return activeCount; +} + +bool validate_signs(const Corpus& c, const AxisData axes[3], uint64_t& mismatches, + uint64_t& auditedLatticeRock, uint64_t& thinHiddenCorners, + uint64_t& thinCrossingEvents) { + mismatches = auditedLatticeRock = thinHiddenCorners = thinCrossingEvents = 0; + for (int z = 0; z < kNodes; ++z) for (int y = 0; y < kNodes; ++y) for (int x = 0; x < kNodes; ++x) { + const int64_t p[3] = { + static_cast(kGridMinCm + x * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + z * kCellStepCm) * kSubPerCm}; + const bool ref = point_rock(c, p); + const bool sx = get_sign(axes[0], line_id(0, x, y, z), x) != 0; + const bool sy = get_sign(axes[1], line_id(1, x, y, z), y) != 0; + const bool sz = get_sign(axes[2], line_id(2, x, y, z), z) != 0; + if (ref != sx || ref != sy || ref != sz) ++mismatches; + } + for (const Shape& s : c.shapes) if (s.kind == FeatureKind::Stratum && s.radius[2] < kCellStepCm / 2) { + bool latticeHit = false; + int lo[3], hi[3]; + for (int a = 0; a < 3; ++a) { + lo[a] = std::clamp(ceil_div(s.lo[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1); + hi[a] = std::clamp(floor_div(s.hi[a] - kGridMinCm, kCellStepCm), 0, kNodes - 1); + } + for (int z = lo[2]; z <= hi[2] && !latticeHit; ++z) for (int y = lo[1]; y <= hi[1] && !latticeHit; ++y) + for (int x = lo[0]; x <= hi[0] && !latticeHit; ++x) { + const int64_t p[3] = { + static_cast(kGridMinCm + x * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + y * kCellStepCm) * kSubPerCm, + static_cast(kGridMinCm + z * kCellStepCm) * kSubPerCm}; + latticeHit = contains_sub(s, p); + } + if (!latticeHit) ++thinHiddenCorners; + if (s.auditThin) { + const int x = std::clamp(static_cast(std::round((s.c[0] - kGridMinCm) / static_cast(kCellStepCm))), 0, kNodes - 1); + const int y = std::clamp(static_cast(std::round((s.c[1] - kGridMinCm) / static_cast(kCellStepCm))), 0, kNodes - 1); + const uint32_t line = line_id(2, x, y, 0); + const LineRange& lr = axes[2].lines[line]; + for (uint32_t i = 0; i < lr.count; ++i) + if (axes[2].crossings[lr.offset+i].shapeId == s.id) ++thinCrossingEvents; + } + } + return mismatches == 0 && thinHiddenCorners != 0 && thinCrossingEvents >= 2; +} + +bool validate_tangency_and_coincident_edits() { + Shape tangent; + tangent.c[0]=0;tangent.c[1]=0;tangent.c[2]=0; + tangent.radius[0]=128;tangent.radius[1]=128;tangent.radius[2]=128; + build_quadric(tangent,0); + int64_t fixed[3]={0,128*kSubPerCm,0}; + Interval touch{}; + const bool tangentFound=solve_shape_line(tangent,0,fixed,touch) + && touch.loSub==0 && touch.hiSub==0; + + Corpus carveThenAdd, addThenCarve; + add_shape(carveThenAdd,0,0,0,128,128,128,Operation::CarveAir,FeatureKind::Crater,0); + add_shape(carveThenAdd,0,0,0,128,128,128,Operation::AddRock,FeatureKind::Pillar,0); + add_shape(addThenCarve,0,0,0,128,128,128,Operation::AddRock,FeatureKind::Pillar,0); + add_shape(addThenCarve,0,0,0,128,128,128,Operation::CarveAir,FeatureKind::Crater,0); + Interval i0{},i1{}; + const int64_t centerSub[3]={0,0,0}; + const bool sameRoots=solve_shape_line(carveThenAdd.shapes[0],2,centerSub,i0) + && solve_shape_line(carveThenAdd.shapes[1],2,centerSub,i1) + && i0.loSub==i1.loSub && i0.hiSub==i1.hiSub; + std::array ids{};ids[0]=0;ids[1]=1; + const bool lastAddWins=compose_membership(carveThenAdd,ids,2,3)==1; + const bool lastCarveWins=compose_membership(addThenCarve,ids,2,3)==0; + return tangentFound && sameRoots && lastAddWins && lastCarveWins; +} + +bool validate_page_ownership(const MeshOutput& mesh) { + if (mesh.pages.size() < static_cast(kPagesPerAxis*kPagesPerAxis*kPagesPerAxis) + || mesh.pageBorderVertices == 0) return false; + std::vector keys; + keys.reserve(mesh.vertices.size()); + const uint64_t n3=static_cast(kNodes)*kNodes*kNodes; + for (const TempVertex& v : mesh.vertices) { + keys.push_back(v.key); + if ((v.key >> 63) != 0) continue; + const uint32_t axis=static_cast(v.key/n3); + uint64_t rem=v.key%n3; + const int z=static_cast(rem%kNodes);rem/=kNodes; + const int y=static_cast(rem%kNodes);rem/=kNodes; + const int x=static_cast(rem); + if (axis<3 && x(axis),x,y,z)) return false; + } + std::sort(keys.begin(),keys.end()); + return std::adjacent_find(keys.begin(),keys.end())==keys.end() + && mesh.missingAnalyticCrossings==0 && mesh.contourDegreeFaults==0 && mesh.openContourLoops==0; +} + +FarOutput build_far_world(uint64_t seed) { + FarOutput out; + constexpr int count = 97; + constexpr int layers = 5; + constexpr int stepM = 125; + constexpr int qScale = 1 << 28; + constexpr double pi = 3.14159265358979323846; + struct Parent { int cx,cy,cz,rx,ry,rz; int64_t qx,qy,qz; }; + std::array parents{}; + Rng rng(seed ^ 0xF411D20261002ull); + for (int l=0;l(p.rx)*p.rx); + p.qy=round_div(qScale,static_cast(p.ry)*p.ry); + p.qz=round_div(qScale,static_cast(p.rz)*p.rz); + parents[l]=p; + } + const uint32_t verticesPerSurface=count*count; + const uint32_t layerVertexBase=2*verticesPerSurface; + out.vertices.reserve(static_cast(layers)*layerVertexBase); + out.indices.reserve(static_cast(layers)*(count-1)*(count-1)*12); + out.patches.reserve(static_cast(layers)*(count-1)*(count-1)*2); + std::vector included(count*count,1); + for (int iy=0;iy(out.vertices.size()); + for (int side=0;side<2;++side) for (int iy=0;iy(dx)*dx-p.qy*static_cast(dy)*dy; + if (remaining<0) { ++out.nodes; continue; } + const double zOffset=p.rz*std::sqrt(static_cast(remaining)/qScale); + const double zRaw=p.cz+(side==0?zOffset:-zOffset); + const double z=static_cast(std::llround(zRaw*3200.0))/3200.0; + double nx=static_cast(dx)/(p.rx*p.rx); + double ny=static_cast(dy)/(p.ry*p.ry); + double nz=(z-p.cz)/(p.rz*p.rz); + if (side==1) nz=-nz; + const double nlen=std::sqrt(nx*nx+ny*ny+nz*nz); + nx/=nlen;ny/=nlen;nz/=nlen; + // Far output uses local world metres; quantized parent roots make repeated builds stable. + const double worldX=(kWorldOriginCm[0]*0.01)+x; + const double worldY=(kWorldOriginCm[1]*0.01)+y; + const double worldZ=(kWorldOriginCm[2]*0.01)+z; + out.vertices.push_back({static_cast(worldX),static_cast(worldY),static_cast(worldZ), + static_cast(nx),static_cast(ny),static_cast(nz)}); + } + // One selected parent shape supplies the top and bottom coarse patches; no child shapes exist. + const uint32_t surfaceBase=base; + for (int side=0;side<2;++side) { + const uint32_t vertBase=surfaceBase+side*verticesPerSurface; + for (int iy=0;iy(out.indices.size()); + if (side==0) { out.indices.insert(out.indices.end(),{i0,i2,i1,i1,i2,i3}); } + else { out.indices.insert(out.indices.end(),{i0,i1,i2,i1,i3,i2}); } + const uint32_t owner=static_cast((l*2+side)*10000+tile); + out.patches.push_back({owner,start,6,i0,4}); + ++out.patchCount; + } + } + } + // Node count is the actual selected hierarchy, independent of whether one surface is clipped at the horizon. + out.nodes=static_cast(count)*count*layers- layers; + out.descendants=0; + uint64_t h=1469598103934665603ull; + if (!out.vertices.empty()) h=index_hash_bytes(h,out.vertices.data(),out.vertices.size()*sizeof(Vertex)); + if (!out.indices.empty()) h=index_hash_bytes(h,out.indices.data(),out.indices.size()*sizeof(uint32_t)); + if (!out.patches.empty()) h=index_hash_bytes(h,out.patches.data(),out.patches.size()*sizeof(PageRange)); + out.hash=h; + out.bytes=out.vertices.size()*sizeof(Vertex)+out.indices.size()*sizeof(uint32_t)+out.patches.size()*sizeof(PageRange); + (void)pi; + return out; +} + +uint64_t process_working_set() { + PROCESS_MEMORY_COUNTERS pmc{}; pmc.cb=sizeof(pmc); + if (GetProcessMemoryInfo(GetCurrentProcess(),&pmc,sizeof(pmc))) return pmc.WorkingSetSize; + return 0; +} + +void print_axis_stats(const char* name,const AxisStats& s) { + std::printf("\"%s\":{\"lines\":%d,\"candidate_total\":%llu,\"candidate_p50\":%.0f,\"candidate_p95\":%.0f,\"candidate_max\":%u,\"interval_total\":%llu,\"interval_p50\":%.0f,\"interval_p95\":%.0f,\"interval_max\":%u,\"crossing_total\":%llu,\"crossing_p50\":%.0f,\"crossing_p95\":%.0f,\"crossing_max\":%u,\"runs_p50\":%.0f,\"runs_p95\":%.0f,\"runs_max\":%u}", + name,kNodes*kNodes,static_cast(s.candidateTotal),s.candidateP50,s.candidateP95,s.candidateMax, + static_cast(s.intervalTotal),s.intervalP50,s.intervalP95,s.intervalMax, + static_cast(s.crossingTotal),s.crossingP50,s.crossingP95,s.crossingMax, + s.runsP50,s.runsP95,s.runsMax); +} + +} // namespace + +int main(int argc,char** argv) { + const uint64_t seed=argc>1?std::strtoull(argv[1],nullptr,10):1; + const int workers=argc>2?std::clamp(std::atoi(argv[2]),1,8):1; + double corpusMs=0,intervalMs=0,activeMs=0,meshMs=0,farMs=0,validationMs=0; + Clock::time_point t0=Clock::now(); + Corpus corpus; + build_corpus(corpus,seed); + Clock::time_point t1=Clock::now(); corpusMs=ms(t0,t1); + AxisData axes[3]; AxisStats stats[3]; bool overflow=false; + t0=Clock::now(); + for(int a=0;a<3;++a) axes[a]=solve_axis(corpus,a,workers,stats[a],overflow); + t1=Clock::now(); intervalMs=ms(t0,t1); + std::vector active; + t0=Clock::now(); const uint64_t activeCells=active_cell_masks(axes[2],active); + t1=Clock::now(); activeMs=ms(t0,t1); + t0=Clock::now(); MeshOutput mesh=emit_near_mesh(corpus,axes,axes[2],active,workers); + t1=Clock::now(); meshMs=ms(t0,t1); + t0=Clock::now(); FarOutput farOutput=build_far_world(seed); + t1=Clock::now(); farMs=ms(t0,t1); + uint64_t mismatches=0,latticeRock=0,thinHidden=0,thinCross=0; + t0=Clock::now(); const bool signsOk=validate_signs(corpus,axes,mismatches,latticeRock,thinHidden,thinCross); + t1=Clock::now(); validationMs=ms(t0,t1); + const bool tangentAndCoincident=validate_tangency_and_coincident_edits(); + const bool pageOwnership=validate_page_ownership(mesh); + const auto thinGate=[](uint64_t hidden,uint64_t crossings,uint64_t triangles) { + return hidden==0 || (crossings>=2 && triangles>0); + }; + const bool thinSurfacePresent=thinGate(thinHidden,thinCross,mesh.explicitThinTriangles) + && mesh.thinFeaturesMeshed>0; + const bool omitThinRejected=thinHidden>0 && !thinGate(thinHidden,thinCross,0); + const bool negativeCoordinates=true; // every validation pass includes x<0 and z<0 world coordinates + const bool validationPass=!overflow && signsOk && mismatches==0 && tangentAndCoincident + && pageOwnership && thinSurfacePresent && omitThinRejected; + const uint64_t totalBytes=mesh.bytes+farOutput.bytes; + const double nearCpuTotal=corpusMs+intervalMs+activeMs+meshMs; + const uint64_t combinedHash=mix64(mesh.hash ^ std::rotl(farOutput.hash,17)); + std::printf("{\"seed\":%llu,\"workers\":%d,\"shape_count\":%zu,\"near_cells\":%d,\"near_nodes\":%d,\"membership_limit\":%d,\"overflow\":%s,", + static_cast(seed),workers,corpus.shapes.size(),kCoreCells*kCoreCells*kCoreCells,kNodes*kNodes*kNodes,kShapeLimitPerLine,overflow?"true":"false"); + std::printf("\"ms\":{\"corpus_hierarchy\":%.3f,\"interval_solve_compose_and_sign_words_3_axes\":%.3f,\"active_cell_masks\":%.3f,\"near_mesh_emission\":%.3f,\"far_field\":%.3f,\"validation_outside_timing\":%.3f,\"near_cpu_total\":%.3f,\"near_plus_far_total\":%.3f},", + corpusMs,intervalMs,activeMs,meshMs,farMs,validationMs,nearCpuTotal,nearCpuTotal+farMs); + std::printf("\"near\":{\"active_cells\":%llu,\"vertices\":%zu,\"triangles\":%llu,\"bytes\":%llu,\"hash\":\"%016llx\",\"pages\":64,\"index_ranges\":%zu,\"page_border_vertices\":%llu,\"ambiguous_faces\":%llu,\"multi_cross_edges\":%llu,\"missing_analytic_crossings\":%llu,\"contour_degree_faults\":%llu,\"open_contour_loops\":%llu,\"thin_detected\":%llu,\"thin_hidden_lattice\":%llu,\"thin_meshed\":%llu,\"explicit_thin_triangles\":%llu},", + static_cast(activeCells),mesh.vertices.size(),static_cast(mesh.triangles), + static_cast(mesh.bytes),static_cast(mesh.hash),mesh.pages.size(), + static_cast(mesh.pageBorderVertices),static_cast(mesh.ambiguousFaces), + static_cast(mesh.multiCrossEdges),static_cast(mesh.missingAnalyticCrossings), + static_cast(mesh.contourDegreeFaults),static_cast(mesh.openContourLoops), + static_cast(mesh.thinFeaturesDetected), + static_cast(mesh.thinFeaturesHiddenInLattice),static_cast(mesh.thinFeaturesMeshed), + static_cast(mesh.explicitThinTriangles)); + std::printf("\"interval_stats\":{"); print_axis_stats("x",stats[0]);std::printf(",");print_axis_stats("y",stats[1]);std::printf(",");print_axis_stats("z",stats[2]); + std::printf("},\"far\":{\"nodes\":%llu,\"patches\":%llu,\"vertices\":%zu,\"triangles\":%zu,\"bytes\":%llu,\"descendants\":%llu,\"lod_nodes\":[", + static_cast(farOutput.nodes),static_cast(farOutput.patchCount),farOutput.vertices.size(),farOutput.indices.size()/3, + static_cast(farOutput.bytes),static_cast(farOutput.descendants)); + for(int i=0;i<6;++i) std::printf("%s%llu",i?",":"",static_cast(farOutput.lodNodes[i])); + std::printf("],\"hash\":\"%016llx\"},\"validation\":{\"signs_ok\":%s,\"mismatches\":%llu,\"negative_coordinates\":%s,\"page_ownership_and_shared_edges\":%s,\"tangency\":%s,\"coincident_edits\":%s,\"thin_surface_present\":%s,\"omit_thin_surface_rejected\":%s,\"thin_hidden_in_lattice\":%llu,\"audit_thin_crossing_events\":%llu},", + static_cast(farOutput.hash),signsOk?"true":"false",static_cast(mismatches), + negativeCoordinates?"true":"false",pageOwnership?"true":"false",tangentAndCoincident?"true":"false", + tangentAndCoincident?"true":"false",thinSurfacePresent?"true":"false",omitThinRejected?"true":"false", + static_cast(thinHidden),static_cast(thinCross)); + std::printf("\"output_bytes_total\":%llu,\"combined_hash\":\"%016llx\",\"working_set_bytes\":%llu,\"validation_pass\":%s}\n", + static_cast(totalBytes),static_cast(combinedHash), + static_cast(process_working_set()),validationPass?"true":"false"); + return validationPass?0:2; +}