From d486b5b2172e440b2214210a10ccfac4ed4fe537 Mon Sep 17 00:00:00 2001 From: andrewnakas Date: Tue, 11 Aug 2026 07:12:01 -0600 Subject: [PATCH 1/2] native-scene: don't drop world meshes whose VB size isn't a stride multiple BuildItemFromMesh required vb_bytes to be an exact multiple of the vertex stride and discarded the entire mesh otherwise. Community custom maps (ArenaBuilder-built worlds) ship vertex buffers with a few trailing bytes past the last vertex - e.g. 43968 bytes at stride 36 leaves 12 over - so their terrain was rejected wholesale and the world rendered with no ground. Measured on one such map: 608-656 "world item build FAILED" lines per session (rate-limited, so the real count is higher) against 0 on stock content. Relax the requirement to "there is room for at least one vertex". Every consumer already derives the vertex count as vb_bytes / stride with integer division - BuildMeshBuffers does exactly that and sizes the GPU buffer from it - so the trailing bytes were always ignored. The exact-multiple test was redundant with handling that already existed downstream. Co-Authored-By: Claude Opus 5 --- src/skate3_native_scene.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/skate3_native_scene.cpp b/src/skate3_native_scene.cpp index 5a7672b..77085a8 100644 --- a/src/skate3_native_scene.cpp +++ b/src/skate3_native_scene.cpp @@ -2107,8 +2107,16 @@ bool BuildItemFromMesh(uint8_t* base, uint32_t mesh, DrawItem& item) { item.vb_bytes = BSwap32(vb_words[2]); item.ib_addr = BSwap32(ib_words[0]) & 0xFFFFFFFC; item.ib_count = BSwap32(ib_words[2]); + // vb_bytes need only COVER a whole number of vertices, not equal one + // exactly. Community custom maps (ArenaBuilder-built worlds) ship vertex + // buffers with a few trailing bytes past the last vertex - e.g. 43968 + // bytes at stride 36 leaves 12 over - and requiring an exact multiple + // rejected the entire mesh, so the world rendered with no ground at all. + // Every consumer already derives the vertex count as vb_bytes / stride + // (integer division), so the trailing bytes are simply unused; all that + // is really required is room for at least one vertex. if (item.vb_addr == 0 || item.ib_addr == 0 || item.vb_bytes == 0 || - item.ib_count == 0 || item.vb_bytes % item.stride != 0) { + item.ib_count == 0 || item.stride == 0 || item.vb_bytes < item.stride) { g_rej_geom.fetch_add(1, std::memory_order_relaxed); return false; } From 14371ab94a12f894e878772ba0973b033bf2ce03 Mon Sep 17 00:00:00 2001 From: andrewnakas Date: Tue, 11 Aug 2026 12:55:13 -0600 Subject: [PATCH 2/2] native-scene: decode k_32_32_32_FLOAT texcoords decode_uv handled vertex formats 25, 26, 31, 37 and 38 and fell through to "default: break" for anything else, leaving the UV at (0,0). Community custom maps (ArenaBuilder-built worlds) declare their texcoord as k_32_32_32_FLOAT (xenos enum 57), so every vertex of every world surface got a zero UV, every pixel sampled one texel, and the map rendered as flat untextured colour - the "custom maps have no textures on the native renderer" report. The emulated renderer is unaffected: it replays the guest's own vertex fetch and never re-decodes. x is at +0 and y at +4 exactly as for the other 32-bit float pairs, so 57 joins the existing 37/38 case. Co-Authored-By: Claude Opus 5 --- src/skate3_native_scene_gpu.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/skate3_native_scene_gpu.cpp b/src/skate3_native_scene_gpu.cpp index f2009df..e2fedbc 100644 --- a/src/skate3_native_scene_gpu.cpp +++ b/src/skate3_native_scene_gpu.cpp @@ -856,6 +856,14 @@ bool DecodeMesh(nrhi::Device* device, uint8_t* base, const DrawItem& item, break; case 37: // k_32_32_FLOAT (hair strand-alpha UV; xenos enum 37) case 38: // k_32_32_32_32_FLOAT (use xy) + case 57: // k_32_32_32_FLOAT (use xy). Community custom maps + // (ArenaBuilder-built worlds) declare their texcoord this + // way; without this case the decode fell through to the + // default and left the UV at (0,0), so every pixel sampled + // one texel and the whole surface rendered as a single flat + // colour - the "custom maps have no textures on the native + // renderer" report. x is at +0 and y at +4 exactly as for + // the other float pairs. u = std::bit_cast(SwapU32(*reinterpret_cast(q))); w = std::bit_cast(SwapU32(*reinterpret_cast(q + 4))); break;