BytorioGet started

DOCUMENTATION / experiments/voxel-resolution/README.md

Voxel resolution study

The riverside scene also has console GPU qualification cartridges in both SDKs. Use mise run //experiments/voxel-resolution:build:console to package them and mise run //experiments/voxel-resolution:benchmark:console to verify and measure native/browser execution.

Two independent scenes compare the same geometry at 320 × 180, 640 × 360 and 1920 × 1080. The build task renders both; the comparison pages link to each other.

Winter diorama

Preserved procedural winter diorama: a bundled character, fox, snow-covered bench, bare tree, and enlarged snowman on the front-right drift. All snow uses one base color; directional lighting, shadows and local occlusion produce its shading. Broad drifts, a raised right bank and a lower winding path shape the terrain. There is no mailbox. Its source is render.mjs and index.html.

Riverside world

river.mjs and river.html recreate a cottage, fenced lawn, leafy tree, irregular oval cobbles, humanoid rabbit adventurer and arched red bridge over a stream. Curving banks step down to the water, hills vary in height and ragged grass lips overhang exposed soil. The rabbit has long ears, a pale muzzle and a tail, with a tunic, sword and shield. The scene fills the viewport, with no scoreboard or exposed platform boundary. Terrain is evaluated procedurally at world X/Z coordinates beyond the camera; a bounded cache stores only the placed props, not the world terrain. The camera sees a window into that terrain rather than an isolated volume. Every output pixel must intersect scene geometry or terrain; CPU generation asserts this independently at each resolution. This does not implement chunk streaming in the console.

The interactive page uses river-live.js, a standalone WebGL 2 viewer. The build also calls river-mesh.mjs to bake the same voxel surfaces and per-face lighting into separate world and rabbit meshes. A 384 × 384-cell terrain patch extends beyond all allowed camera views; walking and camera panning are bounded to keep its edges out of view. This live mesh is not an infinite streamed world.

Interactive supersampling comparison

Select 320 × 180 or 640 × 360 and toggle Supersampling from 1080p. Direct mode renders one sample per output pixel. Supersampling renders the identical scene and camera into a 1920 × 1080 color/depth target, then averages each 6 × 6 or 3 × 3 block in linear color space before encoding the final image to sRGB. Both modes produce a canvas at the selected output resolution, with nearest sampling for final enlargement. WebGL context antialiasing is disabled, so the direct comparison does not include hidden hardware MSAA. At 1920 × 1080 there is no downsampling, and the supersampling control is disabled.

Click the view to focus it. WASD walks and turns the rabbit, with basic terrain, water and obstacle collision. Arrow keys pan the camera and Q/E orbit it. Rotate camera continuously orbits the view. Follow rabbit tracks movement, and Gentle camera motion adds a repeatable fractional pan. Pause motion freezes rabbit and camera movement while quality and resolution controls remain active. Reset position and camera restores the starting pose. Losing focus clears held keys.

The Rendering mode selector adds two stabilization comparisons alongside the original continuous 3D path:

The sprite stores premultiplied linear color and fractional coverage. Compositing happens in linear space before sRGB encoding. Per-pixel depth is compared against the world, so the sprite can pass behind geometry. Depth uses the nearest covered sample in both images; this is conservative around subpixel occluders, not exact per-sample transparency/intersection handling. The sprite is not a flat overlay.

These modes target translation stability. Orbiting the camera or turning the rabbit changes the projected shapes and can still change pixel clusters. They do not reproduce an undisclosed external renderer. The separate rotation research note evaluates persistent surface sampling and its limitations.

Smooth pan presentation is independent of these modes and available for snapped, sprite and surface rendering. It renders one extra game pixel on each edge, resolves at the chosen game pixel pitch, then nearest-samples into an integer-scale presentation canvas. The residual camera translation is rounded to presentation pixels. The integer scale follows the displayed device-pixel width, capped at 6; resizing can change the scale. Native-size view disables it, and the actual-1920 presentation gives integer CSS enlargement at normal browser zoom. Fractional CSS fitting can still introduce uneven screen blocks. It adds presentation resolution, not 3D scene detail or a console display mode. At scale 1 it has no finer motion steps. Camera rotation still changes geometry. At a camera snap boundary the scene is re-rasterized: rare edge or depth ties can change even though the presentation pass itself is an exact nearest copy.

Rabbit surface samples (experimental) replaces only the rabbit mesh with fixed object-local samples, spaced at 0.25 world units on every exposed voxel face. Each array record is a persistent sample identity containing a position, normal and baked face color. This array and its GPU buffer are uploaded once; camera and actor rotations only transform the samples. Back-facing samples are rejected, projected centers snap to the game grid, and screen-aligned square splats depth-test against the world and each other. SSAA filters the result at the same output pitch. The world remains the ordinary triangle mesh.

This isolates persistent color/sample identity from changing view visibility. It is not a guarantee of stable silhouettes or identical pixels through rotation: square footprints overlap, may leave gaps, inflate silhouettes, and approximate surface depth with center depth. Equal-depth overlaps use fixed draw order; grazing angles can change visible samples abruptly. Baked face colors do not exercise fine textures or dynamic lighting, so this scene alone cannot establish how much stability improves on more detailed assets. No temporal filtering or view-angle cache is involved. Use Q/E and WASD to compare the modes.

Whole-scene surface samples (experimental) also replaces the static world mesh with a persistent world-space sample cloud. Terrain, water, cottage, tree, bridge, fences and cobbles all use the same square-splat renderer as the rabbit. The world spacing is 0.5 units, versus 0.25 for the rabbit, to limit storage and overdraw. The world has 682,064 samples (23.4 MiB of records on each of the CPU and GPU, excluding meshes, textures, driver overhead and temporary generation allocations). Generation allocates one typed record array instead of a large intermediate JavaScript number array. The cloud is created on first selection; switching resolutions, rotating and switching modes reuse it. No view-dependent resampling or world triangle fallback is used. Existing modes remain unchanged.

Use Rotate camera or Q/E to compare this mode against Rabbit surface samples, Pixel-snapped 3D and Continuous 3D, with matching resolution and supersampling. Smooth pan presentation also works with the sampled world. The sample positions and baked colors stay fixed, but projected placement, coverage and visibility change. Expect more obvious silhouette and depth artifacts on fine scenery than in the rabbit-only experiment. This is a visual prototype, not evidence of a frame-rate target or a console memory budget.

Lighting, shadows and local occlusion are baked into mesh face colors. The rabbit moves independently, but its lighting and world shadows are not updated dynamically. Scene colors use a linear RGBA8 intermediate target, so this is not an HDR-quality rendering pipeline. The original CPU PNGs and detail crops remain available as static references rather than screenshots of the interactive view.

Its page is generated at build/compact-3d/resolution/river/index.html, or http://127.0.0.1:8081/resolution/river/index.html on the existing demo server.

Regenerate and view

From the repository root:

mise run //experiments/voxel-resolution:build
mise run //experiments/voxel-resolution:serve
# Verify GPU filtering, output sizes and movement controls:
mise run test:voxel-resolution

Open the printed player URL, or http://127.0.0.1:8080/index.html with the default port. Set PORT to select another port. When the existing 3D demo server is running on port 8081, the same generated page remains available at http://127.0.0.1:8081/resolution/index.html.

The source files in this directory are the canonical scene and comparison page. The task regenerates PNGs, live meshes, browser code, the page and a diagnostic report under the ignored build/compact-3d/resolution/ directory. No reference image or external code is required to regenerate the scene. Rendered images are generated artifacts and are not committed.

The winter images use the same 144 × 100 × 144 voxel grid, orthographic camera, lighting and deterministic snow particles. The riverside images share their procedural world, props, camera and lighting. Output sizes are 320 × 180, 640 × 360 and 1920 × 1080, with one center ray per pixel and no antialiasing. The first two are also enlarged 6× and 3× with nearest sampling for an equal-size 1920 × 1080 comparison. Fixed-size detail crops preserve these exact scales; fit-to-window presentation can introduce additional fractional scaling.

The riverside page also offers Native size (no enlargement). This displays the live canvas at its intrinsic output dimensions: 320 × 180, 640 × 360 or 1920 × 1080 CSS pixels. A readout lists the render size, internal scene dimensions, final canvas dimensions and displayed size. Native size and the enlarged 1920-pixel presentation are mutually exclusive. Browser zoom and high-DPI screens can map a CSS pixel to multiple physical device pixels; this control does not change the generated image resolution.

These are standalone CPU/GPU experiments for evaluating visual style. They do not run through either cartridge SDK or the console renderer. Their shadows and local face occlusion are not evidence of implemented console API support, and 1080p is not a new console display mode. Timings in report.json include image generation and are not console benchmarks. The live viewer helps assess motion shimmer, but does not qualify console GPU performance or promise a frame rate.

The browser verifiers check five continuous and ten stabilized resolution/quality combinations, native dimensions, complete frame coverage, fixed-pose toggling, movement, camera controls and focus/pause/reset behavior. It independently recomputes filtered scene pixels and tests a synthetic black/white texture: the even mix must resolve to approximately 188 in sRGB rather than 128. Results and captures are retained in build/compact-3d/resolution/river/live-checks.json and adjacent PNGs. Automated browser execution may use a software graphics driver; these checks establish correctness, not hardware performance.

stability-checks.json additionally retains pixel-grid invariance, bit-identical sprite reuse during translation, recapture on view/facing changes, fractional coverage, full-turn frame coverage and Q/E controls. Independent red/green depth fixtures check front/behind occlusion against known results.

presentation-checks.json records exact one-presentation-pixel horizontal and vertical motion in all four snapped modes, with and without SSAA at both game sizes. It independently checks every presented texel against the resolved overscan image, including across camera snap boundaries, and records scene raster differences at those boundaries separately. It also checks fixed sample-buffer reuse, deterministic return to a pose, rotation changing the image, and UI exclusions. An independent planar fixture checks the expected sample centers, normals and colors, front/behind world depth and back-face rejection after a half-turn. The sampled-world checks additionally cover eight orbit angles, no uncovered pixels in the initial camera framing, lazy creation, storage size, buffer reuse, deterministic mode switching and independence from the world triangle draw count.

Rendering performance

Run mise run //experiments/voxel-resolution:benchmark with installed Chrome. Set PLAYER_BROWSER_CHANNEL to select another installed Playwright browser channel. The build finishes before measurement begins. The report is retained at build/compact-3d/resolution/river/performance.json, including renderer identity, driver/browser details, source hashes and per-run statistics.

The benchmark uses the actual RiverRenderer in a separate headless browser, with the viewer's competing animation/status loop disabled. It presents at 1920×1080, warms each configuration for 45 frames, then measures 120 frames in each of three rounds. Case order reverses in the middle round. Cases cover both game resolutions, panning/orbiting, direct/1080p rendering, smooth presentation on/off, and the sampled-world comparison. There are 36 runs and 4,320 timed frames, plus warmup and a separate completion diagnostic. Other desktop GPU activity is uncontrolled.

CPU submission is measured independently from asynchronous, paired GPU timer queries around scene drawing and resolve/presentation. A frame's GPU total is the sum of its two query results; component percentiles must not be summed. Disjoint/incomplete GPU query results are reported as unavailable. Browser rAF intervals describe headless scheduling, not physical monitor presentation. A separate 30-frame-per-run diagnostic forces a one-pixel readback after drawing, including synchronization/driver/readback overhead. It is not a proposed runtime operation; see the distinction between submission and blocking readback in WebGL best practices. Startup, shader compilation and sample-cloud construction are excluded.

Measured on 2026-10-20 against renderer revision a2672a6, using hardware WebGL through ANGLE/D3D11 on an RTX 3090, i7-11700K, and Chrome 155.0.8059.41. These are the highest per-run p95 values, combining pan/orbit runs where applicable:

Mode (1080p SSAA and smooth presentation) Game output CPU submission p95 GPU p95 Forced-readback p95
Pixel-snapped 3D 320×180 0.20 ms 2.03 ms 4.40 ms
Pixel-snapped 3D 640×360 0.20 ms 2.02 ms 4.20 ms
Whole-scene surface samples 320×180 0.20 ms 3.62 ms 4.10 ms
Whole-scene surface samples 640×360 0.20 ms 3.54 ms 4.90 ms

All 4,320 GPU frame measurements were complete and nondisjoint. The preferred pixel-snapped/SSAA/smooth configurations cover 1,440 frames: worst observed GPU time was 2.19 ms, highest per-run rAF interval p95 was 8.20 ms, and no interval exceeded 18 ms. Their scene-draw p95 reached about 1.91 ms, while combined resolve/presentation p95 reached 0.19 ms. Run-to-run GPU times varied, so these results use conservative per-run p95 values rather than the fastest run.

This supports a 16.67 ms rendering budget for this scene on this desktop. It does not establish a minimum device or a complete game's 60 fps performance. Both game outputs render roughly 2.1 million internal pixels, including overscan; choosing 320×180 does not make the 1080p render four times cheaper. The current scene submits about 1.03 million triangle vertices with resident static geometry and baked lighting. Cartridge execution, gameplay, dynamic lighting/shadows, asset streaming, larger scenes and the standalone native player are unmeasured.

Keep snapped 3D plus SSAA and smooth presentation as the visual baseline. The next gate is a console GPU backend on native and browser running the same cartridge workload, followed by integrated/mobile GPU measurements. Preserve the CPU renderer as a reference. If those measurements need more headroom, test chunk visibility, compatible face merging and lower internal-resolution quality tiers before changing the preferred visual style. These results do not qualify the current console software rasterizer for this workload.