A C++23 3D scene editor for the .mc3.xml format — a lightweight XML-based scene description used by the OpenEggbert project.
MeshCraft lets you build and edit 3D scenes from primitive shapes, groups, materials, CSG boolean operations, and external OBJ meshes. Scenes are saved as .mc3.xml files and can be exported to glTF/GLB for use in games and real-time applications.
MC3 (MeshCraft 3D) is an XML-based source format (.mc3.xml). It describes scenes as editable constructive objects rather than raw triangle meshes.
Key capabilities:
- primitive shapes: box, sphere, cylinder, cone, plane, torus, capsule, disk, grid, icosphere
- hierarchical groups and transforms (position, rotation, scale, pivot)
- materials: PBR (base color, roughness, metallic, emissive, normal/occlusion textures)
- CSG operations: union, difference, intersection (via Manifold)
- extrude along path (line, arc, helix, polyline, bezier)
- animation channels: position, rotation, scale, visibility, material color, deform
- reusable definitions (prefabs / instances)
- layers, tags, collision hints
- fog and environment settings
- export: glTF/GLB via
mc3togltf; binary MCB viamc3tomcb
See MC3_FORMAT.md for the full specification and
docs/CAPABILITY_MATRIX.md for the current
format/editor/exporter/platform capability boundary.
| Component | Description |
|---|---|
mc3/ |
Core library — Mc3Document data model, XML parser/writer, no graphics dependency |
mcb/ |
MCB binary format read/write (fast runtime loading) |
mc3tomcb/ |
CLI: convert .mc3.xml ↔ .mcb |
mc3togltf/ |
CLI: export .mc3.xml → .gltf / .glb |
MeshCraft |
Editor application — Dear ImGui UI, orbit camera, gizmos, CSG, timeline |
../cna/ |
XNA-like C++ runtime (SDL3 + OpenGL ES 3, sibling repo) |
- CMake 3.21 or newer
- C++23-capable compiler (GCC 13+, Clang 16+, MSVC 2022+)
- Python 3 +
lxml(for XSD validation tests) - Sibling repositories checked out next to
mesh-craft/:cna/sharp-runtime/
cmake -S . -B cmake-build-debug -G Ninja
ninja -C cmake-build-debugMESH_CRAFT_GRAPHICS_BACKEND selects the CNA engine backend
(EASYGL | SDL_RENDERER | BGFX | VULKAN | WEBGPU, default EASYGL).
The editor's ImGui renderer consumes CNA draw primitives rather than native GL
calls. EASYGL is the fully supported source-GLSL route; the current runtime
gate also permits the separately qualified Vulkan editor path, while other
backends remain deliberately rejected until they receive real screenshot
qualification. Android and Emscripten force EASYGL: that selects GLES 3.0 /
WebGL 2 rather than the non-qualified SDL renderer. The CNA-free CLI tools
(mc3togltf, mc3tomcb) do not depend on the backend at all.
Android is a supported target at the source-configuration level, but no APK or
on-device run is claimed yet: this workspace has no Android NDK and CNA's
current Android cross-build is blocked in the sibling sharp-runtime before
graphics code compiles. See plan.md AUD-042 for the exact remaining
validation and packaging work.
# Working editor (default):
cmake -S . -B cmake-build-debug -G Ninja -DMESH_CRAFT_GRAPHICS_BACKEND=EASYGL
ninja -C cmake-build-debugNote: sources are collected with
file(GLOB_RECURSE), so CMake does not automatically notice a newly added.cppfile. After adding one (or a new CMakeLists.txt-registered test executable), re-run thecmake -S . -B cmake-build-debugconfigure step before building —ninjaalone will not pick it up.
The release component installs Mc3 and Mcb with
MeshCraft::Mc3/MeshCraft::Mcb CMake targets and both conversion tools:
cmake --install cmake-build-debug --prefix "$PWD/meshcraft-install" --component release
cmake --build cmake-build-debug --target meshcraft_cli_release -j2The latter writes a relocatable MeshCraft-<version>-<system>-cli.tar.gz in
the build directory. An external CMake project can use the installed libraries
with find_package(Mc3 CONFIG REQUIRED) and find_package(Mcb CONFIG REQUIRED),
then link MeshCraft::Mc3 and MeshCraft::Mcb.
Requires the x86_64-w64-mingw32-gcc/g++ toolchain (Debian/Ubuntu:
apt install g++-mingw-w64-x86-64). Write a toolchain file:
# mingw-toolchain.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)cmake -S . -B b-mingw -G Ninja -DCMAKE_TOOLCHAIN_FILE=mingw-toolchain.cmake -DBUILD_TESTING=OFF
cmake --build b-mingw -j4Known blocker (as of this writing): configure succeeds and the
build progresses through ~73% of the object graph (all of CNA's
XNA-compatibility layer, Mc3, Manifold, most of ImGui), then fails on
imgui_impl_opengl3.cpp: fatal error: GLES3/gl3.h: No such file or directory — CNA's build configures -DIMGUI_IMPL_OPENGL_ES3
unconditionally for the EASYGL backend regardless of target platform,
and no GLES-for-Windows headers are vendored. This is a CNA-side
graphics-backend decision, not something this repo can fix on its own —
see cna's own issue tracker. SQLite3/OpenSSL/LibXml2 all gracefully
disable (stubbed at compile time) rather than blocking configure when
absent for the target, and the produced build.ninja correctly copies
SDL3/SDL3_image/SDL3_mixer/libwinpthread-1 DLLs next to
MeshCraft.exe and statically links libgcc/libstdc++.
Requires the Emscripten SDK
(source emsdk_env.sh puts emcmake/emcc/em++ on PATH):
emcmake cmake -S . -B b-web -G Ninja -DBUILD_TESTING=OFF
cmake --build b-web -j4
cd b-web && python3 -m http.server 8765 # then open http://localhost:8765/MeshCraft.htmlBuilds and links cleanly, and the page loads and initializes (WebGL 2.0
context, SDL_CreateWindow succeeds, scene creation logs) — but the app then
crashes on the first window-resize event before rendering a frame, so the
viewport never appears. The crash is a std::runtime_error thrown inside CNA
(GameWindow::queryClientBoundsFromSDL() → SDL_GetWindowSize() reports the
video subsystem uninitialized); it is out of this repo's scope to fix. See
NEXT.md §4 for the full traced root cause. CLI tools (mc3togltf,
mc3tomcb) build the same way and run correctly under Node.
Every third-party dependency this repo fetches is pinned to an exact
version tag, so a fully offline build is possible without any code
change — CMake's built-in FETCHCONTENT_SOURCE_DIR_<NAME> cache
variable (uppercase of the FetchContent_Declare name) redirects that
dependency to a local directory instead of cloning it, verified working
in this repo directly (cmake -DFETCHCONTENT_SOURCE_DIR_NLOHMANN_JSON=/path/to/local/checkout ...
logs "Using the multi-header code from /path/to/local/checkout/include/",
no network access attempted). Pre-populate each directory (a plain git clone <repo> --branch <tag> works) and pass the matching variable at
configure time:
| Dependency | Repository | Tag | CMake variable |
|---|---|---|---|
| tinyxml2 | leethomason/tinyxml2 |
10.0.0 |
FETCHCONTENT_SOURCE_DIR_TINYXML2 |
| nlohmann/json | nlohmann/json |
v3.11.3 |
FETCHCONTENT_SOURCE_DIR_NLOHMANN_JSON |
| Dear ImGui | ocornut/imgui |
v1.91.6 |
FETCHCONTENT_SOURCE_DIR_IMGUI |
| Manifold | elalish/manifold |
v3.0.0 |
FETCHCONTENT_SOURCE_DIR_MANIFOLD |
| tinyobjloader | tinyobjloader/tinyobjloader |
v2.0.0rc13 |
FETCHCONTENT_SOURCE_DIR_TINYOBJLOADER |
| tinygltf | syoyo/tinygltf |
v2.9.3 |
FETCHCONTENT_SOURCE_DIR_TINYGLTF |
| cpp-httplib (optional, AI feature) | yhirose/cpp-httplib |
v0.18.3 |
FETCHCONTENT_SOURCE_DIR_HTTPLIB |
| Lua (scripting, SYS-W14-18) | lua/lua |
v5.4.7 |
FETCHCONTENT_SOURCE_DIR_LUA |
| sol2 (scripting, SYS-W14-18) | ThePhD/sol2 |
v3.3.0 |
FETCHCONTENT_SOURCE_DIR_SOL2 |
cmake -S . -B b-offline -G Ninja \
-DFETCHCONTENT_SOURCE_DIR_TINYXML2=/path/to/tinyxml2 \
-DFETCHCONTENT_SOURCE_DIR_NLOHMANN_JSON=/path/to/json \
-DFETCHCONTENT_SOURCE_DIR_IMGUI=/path/to/imgui \
-DFETCHCONTENT_SOURCE_DIR_MANIFOLD=/path/to/manifold \
-DFETCHCONTENT_SOURCE_DIR_TINYOBJLOADER=/path/to/tinyobjloader \
-DFETCHCONTENT_SOURCE_DIR_TINYGLTF=/path/to/tinygltf \
-DFETCHCONTENT_SOURCE_DIR_HTTPLIB=/path/to/cpp-httplib \
-DFETCHCONTENT_SOURCE_DIR_LUA=/path/to/lua \
-DFETCHCONTENT_SOURCE_DIR_SOL2=/path/to/sol2Omit any variable for a dependency you're fine fetching normally — they
mix freely. ../cna/../sharp-runtime are never fetched at all (plain
sibling-directory add_subdirectory, see Prerequisites above), so they
need no override.
Not implemented, and not possible from this repo alone:
"package-first discovery" (trying find_package(CNA) before falling
back to add_subdirectory(../cna)) needs cna's own CMakeLists.txt
to install()/export a package config first — it doesn't today (no
install(TARGETS ...), no generated CNAConfig.cmake anywhere in that
tree). Adding that is a change to cna itself, out of bounds per this
project's own boundary rules (CLAUDE.md: "No CNA changes without
owner permission").
./cmake-build-debug/MeshCraft path/to/scene.mc3.xmlMeshCraft also supports three headless (no window shown, no interactive
loop) one-shot modes, each rendering/measuring the scene and exiting:
./cmake-build-debug/MeshCraft scene.mc3.xml --screenshot out.png # or out.ppm — see note below
./cmake-build-debug/MeshCraft scene.mc3.xml --export out.glb
./cmake-build-debug/MeshCraft scene.mc3.xml --benchmark # timing breakdown to stdout--screenshot's output format is chosen by the path's extension: .png
writes a real PNG; any other extension (e.g. .ppm) writes raw PPM (P6)
bytes regardless of what the extension says.
./cmake-build-debug/mc3togltf/mc3togltf scene.mc3.xml scene.glb
./cmake-build-debug/mc3togltf/mc3togltf --stats scene.mc3.xml scene.glb # print export statisticsFor safety with untrusted scenes, mc3togltf rejects texture/mesh paths that
are absolute or escape the input document's directory by default (so a
malicious .mc3 can't read arbitrary local files into the output GLB). Pass
--allow-external-resources for trusted scenes that legitimately reference
files outside their own directory.
./cmake-build-debug/mc3tomcb/mc3tomcb scene.mc3.xml scene.mcbctest -V --test-dir cmake-build-debugRe-run only the tests that failed last time (useful after fixing a handful of failures without re-running the whole ~40s suite):
ctest --test-dir cmake-build-debug --rerun-failed --output-on-failureTests are also grouped by label (format/export/render/registry/
ai/commands) — run just one group with ctest --test-dir cmake-build-debug -L export. See TESTING.md for the full test
reference.
- Full
.mc3.xmlscene load/save with XML roundtrip - All primitive types rendered in 3D viewport
- Transform gizmos (move / rotate / scale), local/world space
- Object hierarchy panel with selection, multi-select, drag-and-drop
- Properties panel (geometry, material, transform), multi-edit
- Material editor (PBR: base color, roughness, metallic, emissive)
- CSG boolean operations (union, difference, intersection via Manifold)
- Extrude along path (arc, helix, polyline, bezier cross-sections)
- Animation timeline: keyframes, channels, cubic/step/linear interpolation
- Named layers, object tags, collision hints
- Asset-definition LOD for Instances: authored near/mid/far definitions, hysteresis, maximum-distance culling, and selected-instance debug state; this is separate from primitive tessellation LOD
- Fog, environment background, emissive bloom post-process
- First-person walk mode
- GLB export settings UI; headless screenshot mode
- Undo/redo (exact 20-step deep-copy stack) plus a separate local, memory-budgeted session-history/checkpoint review dialog
- Auto-save with rotating backups
- MCB binary format (
mc3tomcb) - glTF/GLB export (
mc3togltf) with geometry reuse — repeated identical primitives, OBJ meshes, extrude shapes, and<instance>nodes share one glTF mesh buffer per unique geometry+material combination
- Bloom post-process uses CNA
RenderTarget2D+ShaderEffectblur/composite passes; visibility still depends on authored emissive brightness. Custom text-shader effects are disabled on Vulkan until CNA exposes a complete cross-backend effect contract. - Walk mode always retains the y=0 ground plane and collides with scene
primitives explicitly marked
collision="box", uniformcollision="sphere"/IcoSphere, or compatible uprightcollision="capsule"(including parent transforms).mesh,convex, incompatible shapes, and over-budget proxies are reported and ignored rather than being approximated silently. - Preferences dialog: auto-save interval, snap (translate/rotate/scale), grid spacing, and theme are all persisted (
savePrefsAlg/loadPrefsAlg) - Headless screenshot: a
.pngpath writes a real PNG (stbi_write_png); every other extension (e.g..ppm) writes raw PPM (P6) bytes regardless of what the extension actually says - MCB: as of
SYS-W14-25(2026-07-20), compression is implemented —MeshCraft::Mcb::saveToBinary/saveToFiletake an opt-incompressparameter (defaultfalse, unchanged output) that zlib-deflates the document payload;loadFromBinary/loadFromFiletransparently detect and decompress it. Requires this build to have been compiled with zlib available (system package, optional — seeTHIRD_PARTY.md); degrades to a clear "requires zlib"/"compiled without zlib support" error rather than misparsing a compressed file or silently ignoring thecompressrequest. No editor UI toggle — seeMC3_FORMAT.md's MCB section for the full header layout - CSG export/preview: evaluated by Manifold (union/difference/intersection). Unsupported child types inside a CSG node (Plane, Disk, Grid, Mesh, Extrude) cause the export to fail with a clear error in default mode; the editor retains a named child-by-child fallback. Real CSG produces smooth normals with sharp creases, generated box/default or root-selected UV projection, and child-material ranges in both glTF and the live viewport when no CSG-root material overrides them; operand UV unwraps themselves are not preserved through cuts
- No full end-to-end UI-interaction-simulation harness (nothing scripts a sequence of real user clicks/drags through the live application window) — but this understates real coverage: 37
render-labeled tests include real headless--screenshotchecks (fog, light/camera gizmos, look-through-camera, embedded GLB, procedural LOD, authored asset LOD/culling, CSG preview cache, etc. — seeTESTING.md), and a handful ofunit-labeled tests drive real ImGui widget frames (drag/click gestures, undo-snapshot timing, scene-hierarchy drag-drop) directly against production widget code with no GL context needed - AI Assistant: not available on Emscripten or Android builds (
cpp-httplib/OpenSSL are only fetched/linked whenNOT EMSCRIPTEN AND NOT ANDROID). The API key is never persisted to disk — it lives only in the in-memory UI text buffer for the session, pre-filled from theANTHROPIC_API_KEYenvironment variable if set, and is not part of the saved preferences file. The Model field (defaultclaude-sonnet-5) and Max tokens (4096–64000, default 32000) are both user-editable in the AI panel and control the outgoing API request directly — there is no fixed/hardcoded model. Scope selects what's sent as context: "Full scene" serializes the entire document; "Selection only" serializes just the currently-selected objects (plus all materials/definitions, so references still resolve) and is disabled when nothing is selected. Applying a response that would drastically shrink the scene's object count (more than half, on a scene of 10+ objects) requires an explicit second "Confirm Replace" click rather than applying immediately. Response validation is structural, not semantic: it checks the XML is well-formed, has a<mc3>root, isn't empty, and conforms tomc3.xsd(element order, attribute types/patterns, ID/IDREF cross-references) — butmc3.xsdhas no numeric range constraints (nominInclusive/minExclusiveanywhere), so a geometrically nonsensical response (e.g. negativesize/radius) passes validation and applies to the scene as-is - Model Registry: desktop SQLite entries have deterministic cached 64×64 catalog tiles (visual identities, not GPU-rendered scene previews), metadata/tag filters, license/provenance details, material-health reporting, and an explicit local asset-pack export with resolved library files. Database browsing/saving requires the system SQLite3 library and is stubbed on Emscripten/Android; the pure local pack helper remains testable there. There is no sync between registry database files, no account, no upload, and no automatic backup/share — the database remains local at
~/.meshcraft/modelregistry.sqlite3(seem1m2m3.md). - SVG textures (
<texture type="svg">): external.svgfiles and inline CDATA markup are rasterized by NanoSVG (maximum output dimension 2048px) for both the live editor viewport and glTF export. The live cache has compact content-hash keys and automatically re-rasterizes an external SVG when its file changes; malformed input is warned once per unchanged source.wrap_u,wrap_v, andfilterround-trip and affect both the viewport sampler and glTF sampler;mip_mapsaffects glTF, while the live CNA texture remains level-zero because its available API cannot generate a mip chain..gltfexports write a generated PNG beside the document;.glbembeds it. Unsupported or malformed SVG is skipped with a named warning rather than dropping the material silently. - Embedded GLB (
<mesh src="embed:id"/>): external self-contained.glbfiles and inline base64 GLB both resolve inmc3togltfand in the live viewport. The asset's default-scene transforms are flattened into the Mesh object's local geometry; MC3 retains authority over the material. The loader rejects loose companion-file.gltf, non-triangle primitives, animation/skin/morph data, malformed paths/data, and assets over the documented 64 MiB/300,000-triangle limits with a named warning instead of importing an unsafe partial asset. - Editable GLB/glTF import (
SYS-W14-36): File → Import GLB / glTF... maps a bounded self-contained triangle GLB into native MC3 hierarchy, material/inline-image, camera, and punctual-light records. Mesh children preserve their source primitive with strict metadata selectors, so later preview/export does not flatten them again. The separate, unchecked Trusted external .gltf import option accepts only source-directory companions under 16 MiB JSON/48 MiB payload limits, converts them immediately to the same portable inline GLB, and never persists external paths. Inline image data has a 16 MiB encoded/16-million-pixel viewport decode cap. Skins/morphs/animations are explicitly reported as lossy, while non-triangle primitives reject before mutation. - N3–N7 scene data (scripts, sounds, music, triggers, scene states, meta) and event bindings are fully round-tripped (XML/JSON/MCB/XSD) and editable. Scripts (
type="lua") run through the sandboxedLuaScriptRunner; explicit Triggers Fire and States Apply State actions perform their real effects. The Events tab's explicit Preview / Play events mode delivers timers, Walk Mode Area enter/exit, and picked-object clicks while respecting enabled/cooldown/one-shot/dispatch limits. Trigger, state, and script work runs as one isolated, validated document transaction: a failed script rolls back the complete batch, selection/undo history stay unchanged, and pending audio/action effects are suppressed. Normal editing does not dispatch bindings; this is a bounded preview, not a general game runtime. glTF warns once and omits bindings. SeeMC3_FORMAT.mdandplan.mdfor the exact scope. <library>/<imports>(R101/R110,Mc3ImportResolver):SYS-W14-28completes the editor workflow around the existing format APIs. File ▸ Open Library and Save as Library use the dedicated.mc3lib.xml/.mc3lib.jsonloaders and writers; Save refreshes the content hash and requires a valid namespace plusmajor.minor.patchversion. A selected scene object can become a named definition, and any definition can be published with its referenced materials/textures as a self-contained library. Imports resolve automatically after load and via the Imports tab; its health view reports the resolved file, declared namespace/version, effective SHA-256 hash, direct definition count, and missing/hash/cycle/identity/collision errors. Imported definitions remain external when a scene is saved, the picker can place them as editable Instances with text/category/semantic/style filters, and an edit that turns one into a local override is warned before saving rather than silently shadowing its source library.<texture mip_maps="...">: as ofSYS-W14-22(2026-07-20), honored bymc3togltf—falsemakes the exporter emit a plain (non-mipmap) glTF samplerminFilterinstead of unconditionally requesting a mipmapped one. Not honored by the live editor viewport — CNA'sTexture2Dasset-loading path has no mipmap-generation option (confirmed in CNA's own OpenGL backend: it explicitly does not generate mipmaps by default for the filter that path uses), and closing that would need a CNA-side API change, out of scope per this repo's CNA boundary. SeeMC3_FORMAT.md's Textures section for the full writeup.<texture color_space="...">: as ofSYS-W14-23(2026-07-20),mc3togltfstill doesn't re-encode pixels at export time (glTF 2.0's per-slot encoding — baseColor/emissive sRGB, normal/metallic-roughness/occlusion linear — is spec-mandated and has no per-texture override), but now warns when a texture's declaredcolor_spaceconflicts with its slot's mandated encoding (e.g. a normal map declaredcolor_space="srgb"), naming the material, texture, slot, and both the declared and required values, instead of silently doing nothing with the mismatch.<uv_mapping projection="box"/"sphere">:mc3togltfuses exporter-compatible box/triplanar and equirectangular sphere projection. As ofSYS-W14-32(2026-07-26), the live viewport applies the same default/box/sphere projection plus scale, rotation, and offset to ordinary normal/UV meshes; its separately generated CSG mapping cache remains intentionally independent. SeeMC3_FORMAT.mdfor the precise mapping rules.<light brightness="...">: as ofSYS-W14-26(2026-07-20),mc3togltfconverts per light type instead of writing the same raw number into every type's glTFintensityregardless of its physically different mandated unit (directional = lux, point/spot = candela). Directional passes through unconverted (matches Blender's own glTF exporter's Sun-lamp convention); point/spot convert viabrightness / (4π) × 683(683 lm/W is the CIE luminous-efficacy constant, matching Blender's Point/Spot-lamp formula). A deliberate, documented scale factor per light type, not a claim of full physical calibration —brightnessstill has no editor-side lux/candela input mode. SeeMC3_FORMAT.md's Lights section for the full writeup.<ambient>light: glTF 2.0 core +KHR_lights_punctualhave no ambient-light concept at all (a real spec gap). As ofSYS-W14-27(2026-07-20), instead of being dropped outright it's approximated — every<ambient>light'scolor × brightnessin the document is summed and baked into every material's ownemissiveFactor, tinted by that material'sbase_colorand clamped to[0,1], so a glTF-conformant viewer isn't fully unlit wherever an ambient fill was authored. A lossy approximation, not real global illumination — seeMC3_FORMAT.md's Lights section for the full formula.- Live-viewport light shading (
AUD-077,SYS-W14-33, 2026-07-26): directional (up to three) and first ambient lights use CNABasicEffect; on source-GLSL-capable CNA backends, the first eight authoredpoint/spotlights also preview throughShaderEffect, with inverse-square attenuation, range cutoff, spotlight cone/falloff, color, and authored brightness. Backends without that capability log a clear BasicEffect-plus-gizmo fallback. This is an unshadowed editor preview;mc3togltfremains the ground truth for exported lighting. SeeMC3_FORMAT.md's Lights section for details.
docs/CAPABILITY_MATRIX.md is the authoritative
current capability matrix. The table below is retained as dated build/runtime
evidence and must not be read as a competing live status source.
Status as of the S16 (Cross-Platform Stability) stabilization pass, re-verified
2026-07-07 against a fresh MinGW cross-compile attempt (with the
CNA_ENABLE_NET=OFF fix — see plan.md's "Post-650 Follow-Up Findings" — applied).
Web-column rows re-diagnosed 2026-07-11: the blank canvas is a downstream
symptom of a CNA-side crash on the first resize event, not a canvas-sizing
config bug — full trace in NEXT.md §4. The earlier sizing theory is archived
in docs/history/web_issues.md.
Legend: ✅ verified working 🟡 partially verified / known gap
❌ not available on this platform ❓ not yet attempted (no toolchain
available to test with).
| Feature | Linux | Windows (MinGW) | Web (Emscripten) | Android |
|---|---|---|---|---|
| Full desktop/native build | ✅ | 🟡 the full GUI editor (MeshCraft.exe) still fails, but on the same pre-existing CNA-side GLES3/gl3.h header gap as before (imgui_impl_opengl3.cpp — CNA configures -DIMGUI_IMPL_OPENGL_ES3 unconditionally for the EASYGL backend regardless of target platform), plus 3 separate ../sharp-runtime-side -Werror build failures in its System.Net.Sockets/System.Xml namespaces (afunix.h's ADDRESS_FAMILY on this MinGW version, an unused-function warning, a sign-compare warning) — none of these are in this project's own source, all out of scope to fix without CNA/sharp-runtime maintainer involvement. New finding: the two CNA-free CLI tools (mc3togltf.exe, mc3tomcb.exe) build and link successfully as real Windows PE32+ executables, since neither links SHARP_RUNTIME or CNA at all — confirmed by running file on the actual output binaries, not just a partial object count |
🟡 update 2026-07-09: the last-known-good build (2026-07-06 artifacts) still builds/runs fine, but a fresh rebuild now fails — ../sharp-runtime gained a new Emscripten-only regression since then (16 -Werror failures + 1 hard std::chrono::clock_cast compile error). See docs/history/web_issues.md (archived). |
🟡 source selects GLES/EASYGL; no NDK configure, APK package, or on-device test yet. CNA's current NDK cross-build is blocked in sibling sharp-runtime before graphics compile |
| App launches / runs | ✅ | ❌ (build doesn't complete) | ❌ loads and initializes (SDL_CreateWindow, WebGL2 context, scene creation) but then crashes on the first resize event, before any frame renders — see below |
❓ |
| 3D viewport rendering | ✅ | ❌ (build doesn't complete) | ❌ never reached. Root cause (re-diagnosed 2026-07-11, supersedes the earlier "canvas 0×0" theory): an uncaught std::runtime_error from CNA's GameWindow::queryClientBoundsFromSDL() — SDL_GetWindowSize() reports "Video subsystem has not been initialized" on the first SDL_EVENT_WINDOW_RESIZED, killing the wasm module. 100% inside CNA; not fixable from this repo. Full trace in NEXT.md §4. |
❓ |
| Shaders (GLSL ES 3.00 / WebGL2) | ✅ (desktop GL) | ❌ (build doesn't complete) | ✅ all 7 CNA EasyGL 3D shader programs are #version 300 es and compile/link cleanly |
❓ |
| Config/prefs/recent-files/keybindings persistence | ✅ ~/.config/meshcraft |
✅ %APPDATA%\meshcraft (code-verified; no working build to run it against yet) |
🟡 the web pre-JS mounts IDBFS at Emscripten's $HOME and syncs it on startup/unload, so the intended persistent filesystem route is implemented. End-to-end persistence remains unverified because the CNA resize crash prevents a usable editor session. |
❓ (falls through to the same non-Windows $HOME-based logic as Linux; not verified on-device) |
| SQLite Model Registry | ✅ (or gracefully stubbed if SQLite3 dev package absent) | 🟡 gracefully stubbed when SQLite3 absent (code-verified; no working build to run it against yet) | ❌ always stubbed (MESHCRAFT_HAS_SQLITE3 never defined) |
❌ always stubbed (same guard as Web) |
| AI Assistant (Claude API) | ✅ (or gracefully stubbed if OpenSSL absent) | 🟡 same as SQLite3 above | ❌ always stubbed (MESHCRAFT_HAS_AI never defined) |
❌ always stubbed (same guard as Web) |
| File dialogs (text-path-field fallback) | ✅ | ✅ (no native OS dialog anywhere — plain ImGui::InputText, platform-agnostic by construction) |
✅ | ✅ |
| Path handling (UTF-8, spaces, separators) | ✅ (round-trip tested) | ✅ (std::filesystem::path used consistently; not executable-tested on real Windows) |
✅ (std::filesystem::path; not executable-tested) |
✅ (same code path) |
| Static runtime linking / DLL bundling | N/A | ✅ -static-libgcc -static-libstdc++, plus SDL3/SDL3_image/SDL3_mixer/libwinpthread-1 DLLs copied next to the .exe (build-graph verified) |
N/A (single .wasm, no separate runtime DLLs) |
❓ |
| wasm exceptions / preloaded test assets | N/A | N/A | ✅ -fwasm-exceptions on compile+link; --preload-file test@/test packages all test/ assets into MeshCraft.data |
N/A |
| CI | ✅ active GitHub Actions: standalone component matrix plus full EASYGL editor build/tests and Vulkan editor configure/build | ❌ no Windows runner | ❌ no Web runner | ❌ no Android runner |
MeshCraft has no built-in crash reporter — if it crashes, capture a backtrace with the OS-standard tools below and attach it to a GitHub Issue along with the scene file (if any) and the exact command line that triggered it.
Linux:
# Debug build already has debug info (see "Build (Linux)" above)
ulimit -c unlimited # enable core dumps for this shell
./cmake-build-debug/MeshCraft scene.mc3.xml
# after it crashes, find the core file (verified: named core.<pid> by
# default on this system — check `cat /proc/sys/kernel/core_pattern`
# if yours differs) and load it:
gdb ./cmake-build-debug/MeshCraft core.<pid>
(gdb) bt full # full backtrace — this is what to attachIf it hangs instead of crashing, attach a backtrace from a running
process instead: gdb -p $(pgrep MeshCraft) -batch -ex "bt full".
Windows: no build/test verification of this exists in this
environment (Linux-only dev setup) — the standard approach is to let
Windows Error Reporting generate a .dmp file (Control Panel → System
→ Advanced → check "Windows Error Reporting" settings, or trigger one
directly via Task Manager → right-click the hung/crashed process →
"Create dump file") and open it in WinDbg or Visual Studio for a
backtrace. Treat this as unverified guidance, not a tested procedure.
Auto-save and backups live next to the scene file itself — there is no separate cache/config directory for them.
- Auto-save: while a file
scene.mc3.xmlis open, the editor periodically writes the current in-memory state toscene.mc3.xml.autosave(interval configurable in Preferences, default 60s). This file is deleted automatically on the next explicit Save — it's a crash-recovery net, not a permanent artifact. - Recovering after a crash: reopen
scene.mc3.xmlnormally. If its.autosavesibling is newer, the editor opens Recover Unsaved Changes. Choose Recover to load the autosave while retaining the original file as the save target, or Discard Autosave to remove it. Recovered content is marked modified and the autosave remains as a safety net until an explicit successful Save. - Backup rotation on every explicit Save: before writing, the
previous on-disk content is preserved as
scene.mc3.xml.backup.1(most recent prior version); if a.backup.1already existed, it's first renamed toscene.mc3.xml.backup.2(previous-to-that version) — a fixed 2-slot ring buffer, nothing older than 2 saves back is kept. To recover from a bad save (e.g. accidentally saved over good work with something wrong), copy.backup.1(or.backup.2for one save further back) overscene.mc3.xml.
MIT — see LICENSE.
