| … | … | @@ -506,11 +506,24 @@ int main(int argc, char** argv) { |
| 506 | 506 | pts.push_back(0.0f); pts.push_back(0.0f); |
| 507 | 507 | } |
| 508 | 508 | |
| 509 | | HeightGridPack g = erosion_heightmap_pack_tiles(tiles, STUB); |
| 509 | // Tile B, on the far side of the hole -- the tile the mutability probe adds. |
| 510 | const HeightTileSrc* B = by_coord[(long long)hole_tx * 100000 + (hole_ty + 1)]; |
| 511 | const int I_B = I_EXACT + N_EXACT; |
| 512 | pts.push_back(tile_centre_x(*B)); |
| 513 | pts.push_back(tile_centre_z(*B)); |
| 514 | pts.push_back(0.0f); pts.push_back(0.0f); |
| 515 | |
| 516 | // THE WORLD, from map_dc_diffusion.gd: root span = 28 * 2^(OCTREE_DEPTH-1) |
| 517 | // * VOXEL_SIZE with OCTREE_DEPTH 16, and a tile is 256 px * 30 m. |
| 518 | const HeightWorld world = erosion_heightmap_world(28.0 * (1 << 15) * 1.0, |
| 519 | (double)payload * mpp); |
| 520 | HeightGridPack g = erosion_heightmap_pack_tiles(tiles, STUB, world); |
| 510 | 521 | ok("pack accepted the tile set", g.ok); |
| 511 | 522 | if (!g.ok) { printf("\nFAIL cannot continue\n"); return 1; } |
| 512 | | printf(" grid %dx%d cells from (%d,%d), %d tiles, %.1f MiB\n\n", |
| 513 | | g.nx, g.ny, g.tx0, g.ty0, g.tiles, g.data.size() * 4.0 / 1048576.0); |
| 523 | printf(" world %dx%d cells from (%d,%d) = %d, table %.0f KiB\n", |
| 524 | g.nx, g.ny, g.tx0, g.ty0, g.nx * g.ny, g.nx * g.ny * 4.0 / 1024.0); |
| 525 | printf(" %d tiles packed, %d refused, %.1f MiB total\n\n", |
| 526 | g.tiles, g.rejected, g.data.size() * 4.0 / 1048576.0); |
| 514 | 527 | |
| 515 | 528 | std::vector<float> ref = run_sampler(c, single_tile_consts(*A), false, A->h, pts, "reference"); |
| 516 | 529 | std::vector<float> got = run_sampler(c, grid_consts(g), true, g.data, pts, "paged"); |
| … | … | @@ -584,7 +597,7 @@ int main(int argc, char** argv) { |
| 584 | 597 | |
| 585 | 598 | printf("\n CONTROL C: swap tx/ty when packing -> probe 1 must FAIL\n"); |
| 586 | 599 | { |
| 587 | | HeightGridPack sw = erosion_heightmap_pack_tiles(tiles, STUB, true); |
| 600 | HeightGridPack sw = erosion_heightmap_pack_tiles(tiles, STUB, world, true); |
| 588 | 601 | if (!sw.ok) { ok("swapped pack built", false); } |
| 589 | 602 | else { |
| 590 | 603 | std::vector<float> sg = run_sampler(c, grid_consts(sw), true, sw.data, pts, "swapped"); |
| … | … | @@ -595,11 +608,60 @@ int main(int argc, char** argv) { |
| 595 | 608 | } |
| 596 | 609 | } |
| 597 | 610 | |
| 598 | | // REPORTED, NOT ASSERTED. hblk_sample_level clamps world->texel to |
| 599 | | // HBLK_SIDE[0] before the fetch runs, so a point far outside the grid |
| 600 | | // resolves to the bbox edge instead of the stub -- a real divergence from |
| 601 | | // HeightfieldNode::sample_height, and work this gate's scope excludes. |
| 602 | | printf("\n3. KNOWN GAP (reported, not asserted)\n"); |
| 611 | printf("\n4. A TILE ARRIVES AT RUNTIME -- and the SHADER DOES NOT CHANGE\n"); |
| 612 | { |
| 613 | // The same set minus B, which is what the world looks like before B is |
| 614 | // fetched from a peer. |
| 615 | std::vector<HeightTileSrc> without; |
| 616 | for (const auto& t : tiles) |
| 617 | if (!(t.tx == B->tx && t.ty == B->ty)) without.push_back(t); |
| 618 | HeightGridPack g0 = erosion_heightmap_pack_tiles(without, STUB, world); |
| 619 | const std::string c0 = grid_consts(g0); |
| 620 | const std::string c1 = grid_consts(g); |
| 621 | |
| 622 | // ★ THE ASSERTION THIS WHOLE STEP EXISTS FOR. If the emitted constants |
| 623 | // differ, adding a tile means recompiling the density shader, and a |
| 624 | // world that recompiles on every fetched tile is not a mutable tileset. |
| 625 | snprintf(det, sizeof(det), "%zu bytes, %d vs %d tiles packed", |
| 626 | c0.size(), g0.tiles, g.tiles); |
| 627 | ok("emitted constants are IDENTICAL with and without B", c0 == c1, det); |
| 628 | |
| 629 | std::vector<float> before = run_sampler(c, c0, true, g0.data, pts, "pre-add"); |
| 630 | snprintf(det, sizeof(det), "%.1f", before[I_B]); |
| 631 | ok("B reads the stub before it arrives", before[I_B] == STUB, det); |
| 632 | snprintf(det, sizeof(det), "%.2f m", got[I_B]); |
| 633 | ok("B reads real terrain after it arrives", got[I_B] != STUB && |
| 634 | fabsf(got[I_B]) < 9000.0f, det); |
| 635 | // Removing B shifts every later tile's index in the packed buffer, so |
| 636 | // this also asserts the TABLE absorbed the reshuffle rather than A |
| 637 | // silently following a stale index. |
| 638 | ok(" and A is untouched by the addition", before[0] == got[0]); |
| 639 | } |
| 640 | |
| 641 | printf("\n CONTROL: a tile outside the world window is REFUSED, not wrapped\n"); |
| 642 | { |
| 643 | std::vector<HeightTileSrc> stray = tiles; |
| 644 | stray[0].tx = world.tx0 + world.nx + 5; |
| 645 | HeightGridPack gs = erosion_heightmap_pack_tiles(stray, STUB, world); |
| 646 | snprintf(det, sizeof(det), "%d packed, %d refused", gs.tiles, gs.rejected); |
| 647 | ok("the out-of-window tile is dropped", gs.rejected == 1 && |
| 648 | gs.tiles == (int)tiles.size() - 1, det); |
| 649 | } |
| 650 | |
| 651 | // REPORTED, NOT ASSERTED, AND NOW LATENT RATHER THAN LIVE. |
| 652 | // |
| 653 | // hblk_sample_level still clamps world->texel to HBLK_SIDE[0] BEFORE the |
| 654 | // fetch runs, so a point outside the world resolves to the edge cell rather |
| 655 | // than missing. With a footprint-sized table that edge cell was loaded and |
| 656 | // the probe returned -3871 m of somebody else's terrain. With the |
| 657 | // WORLD-sized table it is one of 14,161 cells of which 12 are loaded, so |
| 658 | // the clamp almost always lands on an absent cell and the stub comes back |
| 659 | // anyway -- masking, not fixing. |
| 660 | // |
| 661 | // It becomes live again the moment tiles reach the world edge. Left |
| 662 | // reported rather than asserted because this cache cannot demonstrate it: |
| 663 | // no tile sits at tx/ty 59. |
| 664 | printf("\n5. KNOWN GAP (reported, not asserted -- currently MASKED)\n"); |
| 603 | 665 | { |
| 604 | 666 | // FAR ON X ONLY, and on tile A's own Z row. Going far on BOTH axes is |
| 605 | 667 | // not a test: the second axis's bounds check returns the stub and the |
| … | … | @@ -610,9 +672,10 @@ int main(int argc, char** argv) { |
| 610 | 672 | far_pts.push_back(tile_centre_z(*A)); |
| 611 | 673 | far_pts.push_back(0.0f); far_pts.push_back(0.0f); |
| 612 | 674 | std::vector<float> fg = run_sampler(c, grid_consts(g), true, g.data, far_pts, "far"); |
| 613 | | printf(" far outside the grid returns %.2f m (stub is %.1f)\n", fg[0], STUB); |
| 614 | | printf(" hblk_sample_level's clamp runs BEFORE the fetch; the CPU twin\n"); |
| 615 | | printf(" returns the stub here. Out of scope for level-0 addressing.\n"); |
| 675 | printf(" far outside the world returns %.2f m (stub is %.1f)\n", fg[0], STUB); |
| 676 | printf(" The clamp still runs before the fetch; it lands on an ABSENT\n"); |
| 677 | printf(" cell here, so the stub masks it. Live again once tiles reach\n"); |
| 678 | printf(" the world edge. Out of scope for level-0 addressing.\n"); |
| 616 | 679 | } |
| 617 | 680 | |
| 618 | 681 | printf("\n%s (%d failure%s)\n\n", g_fails == 0 ? "PASS" : "FAIL", g_fails, |