Analysis date: 2026-07-19 (refreshed from the 2026-07-18 analysis — see
"Resolved since 2026-07-18" below for what closed same-day, after this
file's own last edit; "Resolved since 2026-07-10" further down for the
earlier round). Scope:
what the MeshCraft editor's interactive GUI can create/edit/view, versus
what the mc3 format (mc3.xsd, Mc3Document and friends) actually
supports. This is not about mc3togltf export fidelity — that surface is
covered by plan.md's AUD-###/SYS-W7-## rows. This is specifically about
editor UI gaps: format features that parse/round-trip correctly but have no
interactive editing surface, or have one that's incomplete.
One exception to that scope is recorded deliberately: the "Backend gap" section immediately below documents a CNA Vulkan renderer defect found while qualifying the VULKAN backend. It is kept here so it is not lost, and is marked as out-of-scope where it appears.
Findings are grouped by severity: still-open total gaps (zero UI, XML-hand-edit only) first, then still-open partial gaps (UI exists but incomplete or buggy), then non-gaps worth noting for context, then two resolved-history lists (2026-07-18, then the earlier 2026-07-10 round) for historical credit, then a summary table. As of this refresh, sections 1 and 2 below have no remaining open findings — everything that was open as of 2026-07-18 closed the same day.
Scope note: everything else in this file is about mc3 format coverage in the
editor UI. This section is a deliberate exception — it records a rendering
defect found while qualifying the VULKAN backend, so it is not lost. It is a
CNA-side bug (cna/modules/renderers/vulkan), not a MeshCraft one, and
per this repo's CLAUDE.md ("No CNA changes without owner permission") it is
reported here rather than fixed.
Found 2026-08-20 against CNA develop @ 1bb2145d9, on real hardware
(Intel Iris Xe Graphics, ADL GT2; Mesa, Vulkan 1.4.305).
Building MeshCraft with -DMESH_CRAFT_GRAPHICS_BACKEND=VULKAN works and the
editor runs, but lighting on some objects flickers frame to frame in
scenes with many lit objects. It is not the whole scene — only a tail of the
objects drawn in each frame is affected. Observed interactively on
test/medieval_castle.mc3.xml (272 instances, 202 boxes, 89 cylinders,
31 spheres).
The application does not crash; a timeout 20 run had to be killed, it never
exited on its own.
The Vulkan validation layer reports, every frame:
vkCmdBindDescriptorSets(): pDynamicOffsets[0] is 131072, which when added to
the buffer descriptor's range (256) and offset (0) is greater than the size
of the buffer (131072) in descriptorSet #0 binding #1 descriptor[0].
VUID-vkCmdBindDescriptorSets-pDescriptorSets-01979
Reported offsets run 131072 → 133376 in steps of 256, i.e. ring-buffer slots 512–521: ten draws per frame past the end of a 512-slot buffer.
cna/modules/renderers/vulkan/src/VulkanRenderer.cpp:9210 (the lit-textured
path, the one that carries lighting constants):
const uint32_t slot = litTexturedUBOSlot++;
const uint32_t uboOff = slot * kLitTexturedUBOStride;
if (uboOff + 256 <= kLitTexturedUBOStride * kLitTexturedUBOMaxDraws) {
std::memcpy(static_cast<uint8_t*>(litTexturedUBOPtr_[currentFrame_]) + uboOff,
draw.litUboData, 256); // <-- bounds-checked
}
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS,
pipelineLayoutLitTextured3D_, 0, 1,
&draw.litTexturedDescSet, 1, &uboOff); // <-- NOT checkedThe ring buffer is kLitTexturedUBOStride (256) * kLitTexturedUBOMaxDraws (512)
= 131072 bytes. Once a frame issues more than 512 lit-textured draws, the guard
correctly skips the host-side write — but the descriptor bind still happens at
the out-of-range offset. Those draws therefore read whatever memory follows the
buffer instead of their own lighting constants, and since that memory changes
between frames, the affected objects flicker. That the guard covers only the
memcpy is exactly why the failure is a visual one rather than a host-memory
corruption.
The bind sits outside the bounds guard in every one of them, so this is a pattern to fix once, not a single site:
| Path | VulkanRenderer.cpp |
Slots (stride × maxDraws) |
|---|---|---|
| DualTexFog | 9101 | 256 × 512 |
| EnvMap | 9116 | 256 × 512 |
| Skinned | 9132 | 4608 × 32 |
| SkinnedFog | 9146 | 256 × 32 |
| LitTextured | 9212 | 256 × 512 |
| FogTex3D | 9231 | 256 × 512 |
Skinned/SkinnedFog cap at 32 draws, so they break far earlier than the
512-slot paths — a scene with more than 32 skinned draws in a frame is already
past the end.
- Grow the ring buffer on demand, appending chunks as the cursor runs past
the current allocation. This is the approach the compiled-effect path in the
same file already takes (
EnsureCompiledEffectUniformChunkEXT,VulkanRenderer.cpp:6865), so it is a pattern the renderer already carries rather than a new mechanism. Preferred: it is the only option that keeps rendering correct at any scene size. - Move the bind inside the guard, skipping the draw entirely when the slot is out of range. Correctness-safe but visibly lossy — affected objects disappear instead of flickering — and silently so.
Whichever is chosen, an overflow should not be silent: at minimum log once per frame that N draws exceeded the capacity.
For the record, so the scope of this defect is not overstated:
- MeshCraft configures, builds and links clean against
-DMESH_CRAFT_GRAPHICS_BACKEND=VULKAN(zero compile errors). MeshCraft --versionruns;isBackendSupportedAlg()already admits VULKAN.- A headless
--screenshotrender oftest/all_primitives.mc3.xmlis pixel-correct and produces zero validation errors — a single frame never reaches slot 512, which is precisely why the defect hides in one-shot renders and only shows up in sustained interactive use. - Source-GLSL ShaderEffects (Bloom, SSAO, skybox shading, material preview)
are unavailable on this backend and disable themselves with a startup
message. That is a separate, already-documented limitation
(
supportsTextShaderEffectsAlg()ininclude/MeshCraft/GraphicsBackendCheck.hpp), not part of this defect.
All 4 "total gap" findings (N8/N9/N10/N11), plus the texture file-browse
gap and the coordinate_system/Area partial gaps, closed the same day as
this file's own last edit (SYS-W14-10 through SYS-W14-17,
git log --oneline --grep="SYS-W14-1"):
- N8 Object
scriptIdattachment — a "Script" combo added toPropertiesPanel.cpp, mirroring the existing Material combo's structure (none-sentinel, mixed-selection handling, pushUndo/ markModified on change) (SYS-W14-10,643b238). - N9 Library metadata / imports (
.mc3lib) — new "Library (.mc3lib)" section (namespace/version + a hash-recompute button) and a new "Imports" tab (an index-based row editor for the ordereddoc.importsvector) in the Scene Properties panel (SYS-W14-13,8a2d02c). - N10 Semantic JSON file I/O (
.mc3.json) — Open/Save/Save As now dispatch on extension (.mcb/.json/else) via one sharedloadSceneFileDispatched(), replacing 3 independently-drifted copies of that dispatch; also fixed a related pre-existing bug whereSavealways wrote XML regardless of the file's real extension (SYS-W14-11,166a712). - N11 Asset metadata (
assetMetadata) — all 23 fields, in a collapsible "Asset Metadata" section on the Defs tab (definitions, not placed instances — matches the field's own "present only on definitions" documented scope) (SYS-W14-12,0634aa8). - Texture file-browse dialog — a "..." Browse button next to each of
the 5 material texture-slot fields, using CNA's own
FileDialogdevice (previously fully implemented but never called from this repo) (SYS-W14-15,7229b63). coordinate_system— fully implemented (SYS-W14-14, 2026-07-26). A shared CNA-free helper drives Z-up ↔ Y-up conversion for rendering, picking, gizmos, walk collision, cameras/lights and glTF export. Existing Z-up documents can be explicitly normalized to Y-up from Scene Properties; there is no silent load-time rewrite.- Area properties panel — investigated whether Area objects carry
distinct data beyond size, or whether
doc.triggerslinks back to a specific object; confirmed neither (noObjectType::Areaparser case exists — Area is just a Box-shaped primitive with a different type tag;Mc3Triggerhas no field referencing anMc3Objectat all). Confirmed the existing labeled-generic-Box editor is the complete, correct UI given the current data model, not a partial one — no code change needed (SYS-W14-17,784a222). - Undo/redo coverage — a fresh independent 2-agent audit found and
fixed 27 further real gaps (6 missing
pushUndo()entirely, 21 more instances of theAUD-036-style nested-IsItemActivated()dead pattern) acrossPropertiesPanel.cpp/MeshCraftApplication_UiLeftPanel.cpp(SYS-W14-16,4c2d535). Still accurately a manual discipline, not a structural guarantee — explicitly confirmed intentional scope, not an oversight, whenSYS-W9-01was closed the same day: "no formal transaction-abstraction class was built; the manual pushUndo()-before-every-mutation discipline remains, now backed by three audit rounds rather than a structural guarantee" (dc69b7a).
All 7 of the original "zero UI" extension-namespace findings, plus 6 of 12
"partial gap" findings, were closed in a follow-up session
(STAB-0703..STAB-0721, git log --oneline --grep="STAB-070" /
--grep="STAB-071") that this file's original analysis predates:
- N1 SVG textures — full add/remove/edit UI, External-vs-Inline toggle
(
STAB-0703,MeshCraftApplication_UiLeftPanel.cpp's "Tex" tab). - N2 Embedded glTF — full add/remove UI for
doc.embeds, External(.glb path)-vs-Inline(base64) toggle, shows theembed:<id>reference string to paste into a Mesh's Source field (STAB-0704, same file's "Embeds" tab). - N3 Scripts — full add/remove/edit UI (id/type/plain-text source) for
doc.scriptsitself (STAB-0705, "Scripts" tab) — but see the new N8 finding below: the object-levelscriptIdfield that attaches a script to an object still has no UI. - N4 Sounds and Music — full add/remove UI for both, with playback
preview (▶/■ buttons wired to real
SoundEffect/SoundEffectInstanceplayback) — goes beyond what the original finding expected (STAB-0706, "Audio" tab). - N5 Triggers — full add/remove UI with a per-trigger step editor (type
combo + ref-id field) (
STAB-0707, "Triggers" tab). - N6 Scene States — full add/remove UI with per-state object-override
editing (independent visible/position/rotation/material override toggles)
(
STAB-0708, "States" tab). - N7 Meta — key/value editor (rename/edit/remove/add) for
doc.meta(STAB-0709,PropertiesPanel.cpp). Note:doc.metadata— a separate, legacy pass-through map — is explicitly still out of scope per that commit's own comment. - IcoSphere subdivision level — now a live slider (1-4) instead of a
hardcoded label (
STAB-0711). - Primitive
axisfield (Cylinder/Plane/Disk/Capsule) — shareddrawAxisCombo()UI added; Cone intentionally excluded since its mesh generation is Y-axis-only (STAB-0712). - Animation
autoplay— checkbox added next to the existingloopcheckbox (STAB-0714). - New Deform/Material animation channels seeding hardcoded 0.0 — now
read the object's actual current value via
resolveObjectPropertyValueAlg(STAB-0715). - Camera orthographic single-scalar model —
Mc3Camera::orthoAspectadded end-to-end (XSD, XML/JSON parse+write, exporter'sxmag/ymag, and a UI drag-float), soorthoSize+orthoAspectcan represent any xmag/ymag pair (STAB-0695, closed). - Whole-scene Import menu — File → Import OBJ... adds a new top-level
object from an OBJ file (
STAB-0717). - Non-glTF export — File → Export OBJ... added alongside the existing
GLB/Selection/Template export actions (
STAB-0718). - Group missing from Add menu — "Group" menu item added alongside Box/Sphere/.../CSG in the Add menu.
None remaining — N8/N9/N10/N11 (the 4 findings originally in this section) were all closed 2026-07-18; see "Resolved since 2026-07-18" above.
None remaining. rotation_units and euler_order now have Scene Properties
controls and are honored by rendering, picking, gizmos, cameras, Walk Mode and
animated transforms (SYS-W5-06). Static rotations can explicitly normalize
to degrees/XYZ; the action refuses animated Euler channels rather than change
their motion with a lossy conversion.
None remaining as open code gaps — coordinate_system, the texture
file-browse dialog, undo/redo coverage, and Area's properties panel (the
4 findings originally in this section) were all resolved or formally
closed 2026-07-18; see "Resolved since 2026-07-18" above for exactly what
changed and what's still intentionally not a structural guarantee
(undo/redo). coordinate_system is fully implemented, not merely
declared — see the Summary table below and SYS-W14-14.
- Materials: still essentially complete — all scalar fields and all 5 texture slots editable.
- CSG: still fully supported — type-switching, cutter-role toggling, nesting.
- Animation interpolation: still all 3 modes (Step/Linear/CubicBezier) selectable per-keyframe.
- Lights, Cameras, Extrude, Groups/Definitions/Instances,
model/unit/default_camera: not re-verified this pass (no signal they changed); treat as still complete per the 2026-07-10 analysis unless contradicted by something you find.
| Area | Status |
|---|---|
| CNA Vulkan renderer: per-draw UBO overrun (lighting flicker) | 🔴 open, CNA-side — see "Backend gap" section above |
| N1-N7 (SVG textures, embeds, scripts, audio, triggers, scene states, meta) | ✅ resolved (STAB-0703..0709) |
N8 Object scriptId attachment |
✅ resolved (SYS-W14-10) |
N9 Library metadata / imports (.mc3lib) |
✅ resolved (SYS-W14-13) |
N10 Semantic JSON file I/O (.mc3.json) |
✅ resolved (SYS-W14-11) |
N11 Asset metadata (assetMetadata) |
✅ resolved (SYS-W14-12) |
rotation_units/euler_order |
✅ declared units/order honored across the editor; static degrees/XYZ normalization is explicit, while animated Euler curves remain authored (SYS-W5-06) |
| IcoSphere subdivision level | ✅ resolved (STAB-0711) |
Primitive axis |
✅ resolved (STAB-0712) |
coordinate_system |
✅ right-handed Y-up and Z-up honored in the editor and glTF export; explicit Normalize to Y-up command (SYS-W14-14) |
Animation autoplay |
✅ resolved (STAB-0714) |
| Animation Deform/Material channel initial value | ✅ resolved (STAB-0715) |
Camera orthographic xmag/ymag |
✅ resolved (STAB-0695) |
| Texture import (file-browse dialog) | ✅ resolved (SYS-W14-15) |
| Whole-scene Import (OBJ) | ✅ resolved (STAB-0717) |
| Non-glTF Export | ✅ resolved (STAB-0718, OBJ export) |
| Undo/redo structural guarantee | 🟡 27 further gaps closed (SYS-W14-16); still intentionally a manual discipline, not structural (confirmed scope, not an oversight — see dc69b7a) |
| Group in Add menu | ✅ resolved |
| Area properties panel | ✅ confirmed complete given the current data model, no code change needed (SYS-W14-17) |
| Materials, Lights, Cameras (core), Extrude, CSG, Groups/Definitions/Instances, Interpolation, model/unit/default_camera | ✅ complete |