Add tests for scenery surface caching and preview behavior

- Introduced `test_scenery_brick_preview.gd` to validate focused placement and CPU draw-cost for large brick walls.
- Added `test_scenery_surface_cache.gd` to assess texture budget, visual equivalence, and cache invalidation.
- Created `test_scenery_surface_preview.gd` to evaluate adaptive preview selection and placement behavior.
- Implemented various helper functions to measure drawing performance and validate expected behaviors during tests.
- Added unique identifiers for new test files to ensure proper tracking and management.
This commit is contained in:
2026-10-21 11:06:57 -04:00
parent 80ba83b703
commit c1758efd3b
54 changed files with 5941 additions and 526 deletions
@@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
<title id="title">Erasing changes rectangles while sharing the same background texture</title>
<desc id="desc">A rectangular cut turns one wall into top, bottom, left and right fragments. All fragments sample the existing image using their pattern coordinates. The image itself is not painted over or copied.</desc>
<defs>
<pattern id="brick" width="80" height="48" patternUnits="userSpaceOnUse"><rect width="80" height="48" fill="#d2cbaf"/><rect x="2" y="2" width="36" height="19" fill="#25b8df"/><rect x="42" y="2" width="36" height="19" fill="#39c7ee"/><rect x="-18" y="26" width="36" height="19" fill="#32c2e8"/><rect x="22" y="26" width="36" height="19" fill="#21b4da"/><rect x="62" y="26" width="36" height="19" fill="#32c2e8"/></pattern>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Erase the wall geometry. Keep the shared image.</text>
<text x="40" y="86" class="body">Fragments crop their own rectangles and use the existing pattern coordinates.</text>
<text x="50" y="136" class="heading">Before: one wall rectangle</text>
<g transform="translate(50 160)">
<rect width="460" height="300" fill="url(#brick)" stroke="#94a3b8" stroke-width="2"/>
<rect x="155" y="90" width="150" height="120" fill="#fff" fill-opacity=".3" stroke="#b45309" stroke-width="4" stroke-dasharray="10 6"/>
<rect x="174" y="131" width="112" height="36" rx="6" fill="#fff7ed"/>
<text x="230" y="155" class="body" text-anchor="middle">Cut here</text>
</g>
<path d="M532 310H648" class="arrow"/>
<text x="590" y="283" class="small" text-anchor="middle">Erase area</text>
<text x="680" y="136" class="heading">After: up to four fragments</text>
<g transform="translate(680 160)">
<rect width="460" height="300" fill="#dbeafe"/>
<g fill="url(#brick)" stroke="#0f766e" stroke-width="3">
<rect width="460" height="90"/>
<rect x="0" y="210" width="460" height="90"/>
<rect x="0" y="90" width="155" height="120"/>
<rect x="305" y="90" width="155" height="120"/>
</g>
<g fill="#fff"><rect x="195" y="26" width="70" height="36" rx="6"/><rect x="182" y="242" width="96" height="36" rx="6"/><rect x="43" y="131" width="70" height="36" rx="6"/><rect x="342" y="131" width="78" height="36" rx="6"/></g>
<text x="230" y="51" class="body" text-anchor="middle">Top</text>
<text x="230" y="267" class="body" text-anchor="middle">Bottom</text>
<text x="78" y="155" class="body" text-anchor="middle">Left</text>
<text x="381" y="155" class="body" text-anchor="middle">Right</text>
<text x="230" y="143" class="body" text-anchor="middle">Empty area</text>
<text x="230" y="173" class="small" text-anchor="middle">Scene behind</text>
</g>
<rect x="40" y="510" width="1120" height="95" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="62" y="546" class="heading">All remaining pieces still use the same cached texture.</text>
<text x="62" y="580" class="body">Cancel restores the original geometry. Commit keeps the fragments. Neither paints into the image.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,39 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1200" height="630" viewBox="0 0 1200 630" role="img" aria-labelledby="title desc">
<title id="title">Zoom uses smaller copies already stored in the material texture</title>
<desc id="desc">The brick material starts at 1600 by 608 bitmap pixels. Its mip chain includes 800 by 304, 400 by 152 and 200 by 76, continuing down to one pixel. These copies are prepared once. Brick world dimensions stay fixed.</desc>
<defs>
<pattern id="brick" width="100" height="38" patternUnits="userSpaceOnUse"><rect width="100" height="38" fill="#d2cbaf"/><rect x="2" y="2" width="46" height="15" fill="#26b8df"/><rect x="52" y="2" width="46" height="15" fill="#39c7ee"/><rect x="-23" y="21" width="46" height="15" fill="#32c2e8"/><rect x="27" y="21" width="46" height="15" fill="#21b4da"/><rect x="77" y="21" width="46" height="15" fill="#32c2e8"/></pattern>
<pattern id="half" xlink:href="#brick" patternTransform="scale(.5)"/>
<pattern id="quarter" xlink:href="#brick" patternTransform="scale(.25)"/>
<pattern id="eighth" xlink:href="#brick" patternTransform="scale(.125)"/>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#7c3aed"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#7c3aed;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="630" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Zoom samples ready-made sizes of the same image.</text>
<text x="40" y="86" class="body">Mipmaps are generated once with the material. The GPU selects and blends them during rendering.</text>
<rect x="60" y="150" width="400" height="152" fill="url(#brick)" stroke="#94a3b8"/>
<path d="M471 228H508" class="arrow"/>
<rect x="530" y="188" width="200" height="76" fill="url(#half)" stroke="#94a3b8"/>
<path d="M741 228H778" class="arrow"/>
<rect x="800" y="207" width="100" height="38" fill="url(#quarter)" stroke="#94a3b8"/>
<path d="M911 228H948" class="arrow"/>
<rect x="970" y="217" width="50" height="19" fill="url(#eighth)" stroke="#94a3b8"/>
<text x="1043" y="234" class="heading">…</text>
<text x="260" y="342" class="heading" text-anchor="middle">1600 × 608</text>
<text x="260" y="373" class="small" text-anchor="middle">Base image: 2 pixels / world unit</text>
<text x="630" y="342" class="heading" text-anchor="middle">800 × 304</text>
<text x="630" y="373" class="small" text-anchor="middle">Half size</text>
<text x="850" y="342" class="heading" text-anchor="middle">400 × 152</text>
<text x="850" y="373" class="small" text-anchor="middle">Quarter size</text>
<text x="1000" y="342" class="heading" text-anchor="middle">200 × 76</text>
<text x="1000" y="373" class="small" text-anchor="middle">Eighth size</text>
<text x="60" y="414" class="small">The actual chain continues down to 1 × 1. Diagram sizes are scaled for illustration.</text>
<rect x="40" y="450" width="1120" height="143" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
<text x="62" y="486" class="heading">World size, image size and screen size are different things.</text>
<text x="62" y="521" class="body">A brick stays 100 world units wide. At 40% zoom, it covers about 40 screen pixels.</text>
<text x="62" y="556" class="body">Camera zoom changes sampling detail; it does not bake another image or resize the brick geometry.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,51 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="640" viewBox="0 0 1200 640" role="img" aria-labelledby="title desc">
<title id="title">Background rendering: prepare a style once, reuse it while moving the camera</title>
<desc id="desc">A visible surface requests its finish and tint from the scene cache. A cache hit goes directly to the GPU repeat sampler. A cache miss uses one temporary baker, captures an image and mipmaps, and releases vector commands.</desc>
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#475569"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#475569;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="640" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Prepare a material once. Reuse it as the camera moves.</text>
<text x="40" y="86" class="body">Only visible, committed patterned scenery requests a cached material.</text>
<rect x="40" y="140" width="250" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="60" y="177" class="heading">Visible surface</text>
<text x="60" y="210" class="body">Finish + tint</text>
<text x="60" y="239" class="small">Example: blue brick</text>
<path d="M292 203H370" class="arrow"/>
<rect x="390" y="140" width="260" height="126" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<text x="410" y="177" class="heading">Scene style cache</text>
<text x="410" y="210" class="body">Already have this image?</text>
<text x="410" y="239" class="small">Shared by all matching surfaces</text>
<path d="M652 203H740" class="arrow"/>
<text x="674" y="182" class="small">Hit</text>
<rect x="760" y="140" width="400" height="126" rx="12" fill="#ecfdf5" stroke="#0f766e" stroke-width="2"/>
<text x="782" y="177" class="heading">GPU draws the visible rectangle</text>
<text x="782" y="210" class="body">Repeat texture + sample its mipmaps</text>
<text x="782" y="239" class="small">Pan / zoom updates sampling coordinates</text>
<path d="M520 268V324" class="arrow"/>
<text x="540" y="304" class="small">Miss: prepare this style</text>
<rect x="390" y="344" width="260" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
<text x="410" y="379" class="heading">One temporary baker</text>
<text x="410" y="412" class="body">Draw one pattern period</text>
<text x="410" y="441" class="body">Wait for GPU; capture image</text>
<text x="410" y="476" class="small">Then clear vector commands</text>
<path d="M652 423H740" class="arrow"/>
<rect x="760" y="344" width="400" height="158" rx="12" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2"/>
<text x="782" y="379" class="heading">Store the reusable texture</text>
<text x="782" y="412" class="body">Crop overscan; generate mipmaps</text>
<text x="782" y="441" class="body">Keep the image, not the vector artwork</text>
<text x="782" y="476" class="small">Ready styles stay warm within the cache budget</text>
<path d="M960 342V282" class="arrow"/>
<text x="980" y="309" class="small">Ready</text>
<rect x="40" y="548" width="540" height="56" rx="10" fill="#dbeafe"/>
<text x="59" y="582" class="body">Blue route: reuse during ordinary pan and zoom</text>
<rect x="604" y="548" width="556" height="56" rx="10" fill="#ede9fe"/>
<text x="623" y="582" class="body">Purple route: bake only when a style is missing</text>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="650" viewBox="0 0 1200 650" role="img" aria-labelledby="title desc">
<title id="title">Many wall rectangles share one material image</title>
<desc id="desc">One blue-brick image supplies Wall A, Wall B, and an erased fragment. Different sizes, positions, and camera views do not create more images. A different tint or finish is a different cached style.</desc>
<defs>
<pattern id="brick" width="64" height="40" patternUnits="userSpaceOnUse">
<rect width="64" height="40" fill="#d2cbaf"/>
<g><rect x="2" y="2" width="28" height="15" fill="#25b8df"/><rect x="34" y="2" width="28" height="15" fill="#39c7ee"/><rect x="-14" y="22" width="28" height="15" fill="#32c2e8"/><rect x="18" y="22" width="28" height="15" fill="#21b4da"/><rect x="50" y="22" width="28" height="15" fill="#32c2e8"/></g>
</pattern>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto"><path d="M0 0L10 5L0 10Z" fill="#2563eb"/></marker>
<style>text{font-family:Segoe UI,Arial,sans-serif;fill:#172033}.title{font-size:30px;font-weight:700}.heading{font-size:22px;font-weight:700}.body{font-size:18px}.small{font-size:16px}.arrow{fill:none;stroke:#2563eb;stroke-width:3;marker-end:url(#arrow)}</style>
</defs>
<rect width="1200" height="650" rx="16" fill="#f8fafc"/>
<text x="40" y="53" class="title">Wall area can grow. The number of style images stays the same.</text>
<text x="40" y="86" class="body">Position, wall size, camera position and zoom are not part of the style key.</text>
<rect x="400" y="117" width="400" height="163" rx="12" fill="#eff6ff" stroke="#2563eb" stroke-width="2"/>
<rect x="420" y="140" width="160" height="112" fill="url(#brick)" stroke="#94a3b8"/>
<text x="602" y="162" class="heading">One shared image</text>
<text x="602" y="194" class="body">Blue brick</text>
<text x="602" y="224" class="small">Varied brick faces</text>
<text x="602" y="250" class="small">Repeats both ways</text>
<path d="M600 282V311H210V341" class="arrow"/>
<path d="M600 311V341" class="arrow"/>
<path d="M600 311H990V341" class="arrow"/>
<rect x="40" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="62" y="396" class="heading">Wall A</text>
<rect x="62" y="416" width="296" height="103" fill="url(#brick)"/>
<rect x="430" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="452" y="396" class="heading">Wall B</text>
<rect x="452" y="416" width="190" height="103" fill="url(#brick)"/>
<text x="659" y="455" class="small">Same</text>
<text x="659" y="480" class="small">style</text>
<rect x="820" y="360" width="340" height="192" rx="12" fill="#fff" stroke="#cbd5e1" stroke-width="2"/>
<text x="842" y="396" class="heading">Erased fragment</text>
<rect x="842" y="416" width="90" height="103" fill="url(#brick)"/>
<text x="952" y="455" class="small">Only the visible</text>
<text x="952" y="480" class="small">rectangle changes</text>
<text x="40" y="591" class="body">A different finish or tint is a separate style. A matching warm style reuses the existing image.</text>
<text x="40" y="621" class="small" fill="#64748b">Patterns shown here are schematic; the actual brick image contains 8 columns × 8 rows of varied artwork.</text>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB