Last updated: 2026-07-26. This is the bounded, authoritative work queue.
Detailed evidence for completed work and prior audit sessions is archived in
docs/history/; it is useful provenance, not live planning.
MeshCraft is a pre-production C++23 MC3 scene editor with standalone MC3/MCB libraries and MC3→MCB / MC3→glTF tooling. It already covers MC3 XML/JSON, MCB, glTF/GLB export, validation, history, registries and local asset packs, CSG, authored LOD/materials/UVs, animation clips, Lua scripts, triggers, states, event bindings, and automated tests. The codebase is approximately 76,800 C/C++ lines.
The stabilization and SYS-W14-28…SYS-W14-39 roadmaps are complete. The
remaining inherited work is intentionally small:
SYS-W8-05is blocked on CNA backend qualification and owner coordination.AUD-042is blocked on an Android toolchain, sibling-runtime repair, and a real device/emulator.AUD-038is a conscious design limit: undo is a bounded 20-entry, whole-document deep-copy stack.
The next release is about semantic correctness, predictable mutation, performance, truthful documentation, and portability—not widening the feature surface with further formats, importers, skinning, Draco/meshopt, or broad new modelling tools.
TODO— authorized and not started.IN_PROGRESS— currently being implemented.DONE— completed; durable evidence is in Git and the archive.BLOCKED— external dependency or owner action is required.DEFERRED— intentionally not scheduled.
P0 is data loss, invalid output, crash, or unsafe-input risk. P1 is a
wrong semantic result, untruthful capability, or critical test gap. P2 is
architecture, distribution, or measurable performance work. P3 is polish.
- The editor and exporters must agree on every supported MC3 transform convention, including rotation units and Euler order.
- Failed script/event execution must not partially mutate a document.
- Stable rendering of ordinary geometry must not create routine tint or authored-UV GPU buffers per frame.
- User-facing capability documentation must match source and tests.
- CNA-independent format/tooling code must pass sanitizer and bounded fuzz qualification in CI.
- Package installation, Windows standalone qualification, reproducible release artifacts, and Android/alternate-backend work must have either evidence or an explicit release-scope decision.
The user authorized this queue on 2026-07-26. Work proceeds one task at a time; re-evaluate scope and blockers before starting each item.
This queue is currently empty: the standalone-Windows-qualification task that
occupied it is done (see the "Active roadmap" section below for its full
verification evidence). Every other tracked item is [BLOCKED] (external
dependency, sibling-repository owner coordination, or a user decision) or
[DEFERRED] (intentional) — including 2 real third-party bugs found while
verifying the Editor CI jobs this same pass, deliberately not fixed here
(sibling repository, outside this session's authorized scope; user chose to
report and defer rather than fix).
-
SYS-W13-01
[DONE]P0— Restore this file as a bounded active plan. The 2,743-line prior plan was copied verbatim todocs/history/plan_20260726.md; no historical evidence was discarded.plan.mdnow retains only current state, release gates, blockers, and the executable queue;NEXT.mdis the compact handoff. The plan-consistency check continues to protect task identity and queue state. -
SYS-W13-02
[DONE]P1— ReconciledREADME.md,NEXT.md,missing.md,MC3_FORMAT.md, andTESTING.mdwith source anchors.docs/CAPABILITY_MATRIX.mdis now the concise format/editor/exporter/platform authority. The Walk Mode proxy, Web IDBFS, autosave recovery, and ordinary-UV claims are covered by the boundedcapability_documentationlint test; volatile CTest totals are no longer recorded as product truth. -
SYS-W13-03
[DONE]P1— Removed 4 verified documentation-drift claims (not the full external-review list — two of its claims did not hold up on direct read: the "click/timer event" gap it named isSYS-W14-11's pre-existing JSON-only scope, already accurate, andRELEASE.md's "95 tests" mention is already self-caveated as historical, not asserted as current — left both alone):CHANGELOG.mdno longer claims automatic trigger/state-switching events are unimplemented; creditsSYS-W14-40(Event Preview/Play) for automatic timer/Area/click dispatch.missing.mdno longer self-contradicts oncoordinate_system— removed the stale "not read anywhere... by design" clause that contradicted its own Summary table two sections earlier.RELEASE.md's docs checklist no longer references the retired emoji-marker (✅/🟡/🧪/📋/🔴) summary-table format; points attest/validate_plan_consistency.pyinstead, which is the real check.new.mdgained a status note creditingSYS-W14-40for the "event bindings for Areas/triggers" feature it recommends, so the recommendation reads as historical rather than a live gap despite the file's date. Extendedtest/validate_capability_documentation.pywith 6 source-backed assertions covering all four (2 of the 4 needed both aforbidthe stale text is gone and arequirethe replacement is present). Caught and fixed 2 vacuous checks along the way — bothforbidneedles for CHANGELOG.md andmissing.mdfailed to match the actual pre-fix text on the first attempt (a missing 2-space Markdown continuation indent, and a spurious literal\nwhere the source had a space), which would have made the check pass regardless of whether the fix was ever applied; verified each needle againstgit show HEAD:<file>before trusting it. All 6 new checks plus the pre-existing checks pass.
-
SYS-W5-06
[DONE]P1— The CNA-free rotation-convention helper is shared bymc3togltfand the live editor. Rendering/CSG, hierarchy- and pivot-aware picking, gizmos, object/world transforms, cameras, Walk Mode and animated transforms honorrotation_unitsand all six Euler orders. Scene Properties exposes both declarations and a static degrees/XYZ normalizer; it refuses animated Euler channels rather than make a lossy motion change. Regression tests cover radians, every order, nesting, pivots, cameras and animation hand-off; the EASYGL editor and focused tests built successfully. -
SYS-W5-07
[DEFERRED]P2— Revisit forward-compatibility policy only when a concrete compatibility contract is requested. The current accepted policy remains documented lossy handling of unknown XML elements/attributes; do not add an extension bag without deciding between strict rejection, preservation, and lossiness.
- SYS-W1-08
[DONE]P1— Added validation-capturing JSON load APIs.Mc3JsonParsergained the sameg_validation/reportWarning/reportError/reportErrorDocthread_local patternMc3XmlParser.cppalready used (Mc3JsonParser.cpp), wired into: per-field tessellation clamps (clampTess, now threading the owning object's id/name down intotoPrimitive/toCrossSection/toPath/toExtrude— JSON nests these fields under sub-objects with no identity of their own, unlike XML's flat attributes),customPoints/pointscount-cap rejections, everyDocumentBudget::charge*()budget-exceeded rejection, the document byte-budget rejection, resource-path confinement rejections, and a new "unknown object type" warning (JSON silently defaults an unrecognizedtypetogroup— reported now, though still not rejected outright the way XML drops the object entirely; this task is diagnostic parity, not behavioral parity, and changing JSON's accept-vs-drop semantics is out of scope here).parse()/parseString()each now install their ownValidationScope+ real-vs-synthetic source-file tag (refactored the shared parsing body into an internalbuildDocumentFromJson()soparse()no longer delegates toparseString()and stomps its source-file tag with a synthetic in-memory path). AddedMc3Document::loadFromJsonFile()/loadFromJsonString()/loadFromLibraryFile()/loadFromLibraryJsonFile()Mc3Validation&overloads (the last two for BOTH formats — XML's library loader had no validation overload either, not just JSON's) and updatedloadSceneFileDispatched()(FileOps.cpp) so all four load paths populate the editor's validation history consistently. Newmc3_json_validationdifferentially compares JSON againstMc3XmlParser's existing diagnostics for the same tessellation-clamp value (identicalsuggestedRepair), plus invalid enum, document budget, resource confinement, library identity, a clean document (zero entries), and the no-validation-argument non-regression case. All 27 standalonemc3tests passed, as did the full rootMeshCrafteditor target and the registry/undo tests exercisingMc3Documentload paths transitively. Performance note (found and fixed during this task, not a regression from it): computing an object's id/name identity unconditionally on every parsed object — regardless of whether a validation sink was even active — added measurable per-object overhead; made it conditional ong_validation != nullptrso the overwhelmingly common no-validation case pays nothing for it. That fix chased what turned out to be a red herring: confirmed viagit stashisolation thatmc3_json_document_budget's ~150K-object stress scenario already took ~50-55s on the UNMODIFIED, pre-this-task code under this repo'sDebug(-O0) build (vs. ~8-10s under aRelease-flagged standalone build) — this is the SAMEmc3_json_document_budget ... ***Timeout 30.05 secfailure already present in the "Clang ASan+UBSan and bounded fuzz" CI job (which configures-DCMAKE_BUILD_TYPE=Debug) from tonight's CI-red survey, not something this task introduced or fixed. Left alone per the standing decision to leave the general CI-red regressions for a separate pass; the per-object laziness fix above is kept anyway since it's a correct, free micro- optimization, just not the explanation for the timeout.
-
SYS-W2-01
[DONE]P1— Lua scripts now run on a deep, isolated document copy, re-resolve an optional target by a unique stable object ID, apply whole-document validation, and commit only after success. The 16 MiB Lua allocator cap complements the existing 50-million-instruction limit. The editor takes undo/history only at the commit boundary and refreshes selection after the swap; failed scripts leave document, selection, dirty state and undo/history untouched. Focused runner/trigger tests cover partial mutation followed by error, loops, excess allocation, non-finite transforms, invalid material references, placement and trigger-driven execution. -
SYS-W2-06
[DONE]P1— Made the Lua allocator budget accounting exact.budgetedLuaAllocator()always treatedoldSizeas the size of a previously-owned block, even whenpointer == nullptr(a genuinely new allocation), for which Lua'slua_Alloccontract instead passes an object-kind tag (confirmed against the vendored Lua 5.4 source:luaC_newobj→luaM_malloc_(L, size, tag)→frealloc(ud, NULL, tag, size), versusluaM_realloc_/luaM_free_'slua_assert((osize == 0) == (block == NULL)), which holds only for a real resize/free of an existing block). Subtracting that tag frombudget.allocatedon every new allocation silently eroded the tracked total below real usage across many small allocations. Moved the allocator out ofLuaScriptRunner.cpp's anonymous namespace into a new header-onlyinclude/MeshCraft/Editor/LuaMemoryBudget.hpp(no other call site changes needed;LuaScriptRunner.cppis compiled directly into 4 different test targets, not linked as a shared library) and made it branch onpointer == nullptrbefore computingretained. Newtest/lua_memory_budget_test.cpp(lua_memory_budget, header-only, no Lua/sol2 dependency) calls the allocator directly with hand-crafted call sequences matching each ofluaM_malloc_/luaM_realloc_/luaM_free_'s exact(pointer, oldSize, newSize)shapes — deterministic and independent of Lua's own GC scheduling, unlike probing this through real script execution. Covers many small held table/string-shaped allocations (exact tracked-total equality after each), table growth, grow/shrink reallocations, collection followed by reallocation reclaiming real headroom, failed-allocation/failed-resize rollback leaving the tracked total untouched, and a successful transaction at exactly the 16 MiB limit plus rejection at limit+1. Verified discriminating: temporarily reverting just thepointer == nullptrbranch reproduces 4 failing assertions (the exact-equality checks after many small held allocations, and the 15 MiB-held-before-collection check), confirming the fix — not just the new test file's presence — is what the tests depend on.lua_memory_budget,lua_script_runner,automation_workspace,trigger_fire, andevent_preview_runner(the 4 targets that compileLuaScriptRunner.cpp) all built and passed after the move.
-
SYS-W12-03
[DONE]P1— Ordinary tinting now uses effect/material state on the permanent normal/UV buffer; the defensive VPC-only fallback is bounded and persistent. Authored-UV buffers are cached by immutable mesh identity, projection/mapping values, and geometry scale.--benchmarkreports creation/hit counts for its last warm draw; the 100-instancebenchmark_editorfixture requires zero tint/authored-UV creations and an authored-UV cache hit. The focused CTest, UV projection, bloom/emissive, and point/spot-gizmo screenshot regressions passed under EasyGL/Xvfb. -
SYS-W12-04
[DONE]P2—--benchmarknow measures direct CPU primitive mesh generation, CSG cold evaluation and warm-cache lookup, and cache-miss texture decode/rasterization plus GPU upload separately from first/warm frame time. It also prints mesh/triangle, CSG cache/failure, and texture hit/miss/upload counts. The headless 100-instance UV fixture and a real CSG fixture both passed; the former remains an allocation/cache-reuse regression check and neither test relies on a machine-specific time limit. The local baseline is recorded intest/BENCHMARK_BASELINE.md.
-
SYS-W11-04
[DONE]P1— CI now configures, builds, and CTests each CNA-independent component (mc3,mcb,mc3togltf,mc3tomcb) with Clang ASan+UBSan and ccache. Corpus-seeded libFuzzer smoke targets cover XML, JSON, MCB, and self-contained GLB import, each with a 20-second total-time, 5-second per-input, 512 MiB RSS, and 1 MiB-input ceiling. MCB and GLB seeds are generated from checked-in fixtures, while source corpora remain immutable; sanitizer/fuzzer errors fail CI. The GLB importer also exposes a byte-input route, so fuzzing does not write hostile files. Local Clang 19 builds and five-second smoke runs passed for all four formats; the existing GLB import CTest passed. An EASYGL editor sanitizer job remains intentionally deferred until its dependencies are reliable under sanitizers. -
SYS-W11-05
[DONE]P2—Mc3andMcbnow install headers, static libraries, version-compatibleMc3Config.cmake/McbConfig.cmake, and namespacedMeshCraft::Mc3/MeshCraft::Mcbimported targets. The Mc3 package bundles the concretetinyxml2static dependency; Mcb locates Mc3 and zlib through its config.package_consumer_smokeinstalls only thereleasecomponent, configures a separate CMake project withfind_package, links both libraries, and executes an MCB round trip.meshcraft_cli_releasedepends only onmc3tomcb/mc3togltf, installs their runtime requirements plus the package surface, and writes a relocatable.tar.gzarchive. Local Debug verification passed the package smoke plus MC3/MCB and CLI round trips; the installed glTF CLI resolves its bundled Manifold runtime through$ORIGIN/../lib. -
SYS-W11-06
[DONE]P2— Qualify standalone MC3/MCB/tooling builds and tests on a current Windows runner, independent of CNA editor backend blockers. Thewindows-2022CI job configures, builds, and CTests all four standalone components, verifies a fixed MC3→MCB/GLB SHA-256 fixture, then publishes the two Windows CLI executables. This also exposed and fixed all four narrow-string uses ofpath::native()in confinement checks;generic_string()now works on Windows-wide paths. Amended acceptance criteria (2026-07-26 review) verified for real, 2026-07-27, against the actual artifact published by the first fully greenwindows-2022run — not just an in-tree CTest pass. Correction to this file's own prior belief: Wine is not blocked in this sandbox for plain console/test binaries (only the full GUI editor hitsSIGSYS), so this was verified directly rather than deferred:- Downloaded
meshcraft-cli-windowsviagh run download, extracted into a scratch directory outside every build tree and outsidePATH. sha256sumon all 4 manifest entries matchesSHA256SUMS.txtexactly (the manifest itself uses bare filenames rather than thetool/tool.exesubpaths the archive actually has — a cosmetic manifest nit, not a hash mismatch).- Both
mc3tomcb.exe --version/mc3togltf.exe --versionsucceed under Wine withPATHforced to/usr/bin:/binonly (no build-tree fallback possible). - A real MC3→MCB and MC3→GLB conversion of
test/house.mc3.xmlfrom the extracted binaries reproduced the exact existing fixture hashes (4157f107e277a4.../0ef25953c8bce5...) byte-for-byte. - First download attempt (pre-existing artifact) failed outright:
mc3togltf.exewouldn't even start (STATUS_DLL_NOT_FOUNDunder the same Wine repro) — the published artifact had zero DLLs bundled, butmc3togltf.exedynamically linked Manifold, tinyxml2, and the MinGW runtime itself. Root-caused and fixed by statically linking the entire standalone-Windows build (-DBUILD_SHARED_LIBS=OFF+-static -static-libgcc -static-libstdc++inci.yml's shared configure step) rather than trying to enumerate and stage every DLL — re-verified after the fix with the same repro, now passing every check above with zero DLL dependencies at all. SeeNEXT.mdfor the full diagnostic trail and a CMake gotcha this surfaced ($<TARGET_RUNTIME_DLLS:...>expanding to nothing broke the existing DLL-copy POST_BUILD command outright; fixed with the documented$<IF:$<BOOL:...>,copy_if_different,true>workaround).
- Downloaded
-
SYS-W11-07
[DEFERRED]P3— Improve dependency reproducibility with immutable revisions or verified archives, third-party notice/SBOM, and an offline-cache release-build check. 2026-07-26 review note: revisit deferring this onceSYS-W11-08exists — a clean-room release artifact built from movable FetchContent tags is a weaker reproducibility claim than the artifact test alone suggests. LeftDEFERREDhere; changing that is a scope decision for the user, not made in this pass. -
SYS-W11-08
[DONE]P2— Produced and tested clean-room CLI release artifacts, on Linux fully, on Windows partially (see below). Discovered along the way (neither tool had it at all — a real functional gap, not just a testing gap):--versiondid not exist.mc3tomcb --versionprinted the usage text and exited 1;mc3togltf --versiontreated--versionas an input filename and failed with "input file not found". Added it to both (MESHCRAFT_VERSION, a new per-tooltarget_compile_definitionsusing each standalone project's ownPROJECT_VERSION— not yet unified across projects; that isSYS-W11-10's job). This directly affectsSYS-W11-06's own amended acceptance criteria, which already assumed--versionworked.cmake/CreateCliRelease.cmake(used by both themeshcraft_cli_releasetarget and the new test below) now additionally: bundlesLICENSEandTHIRD_PARTY.mdinto the archive, and writes asha256sum -c-compatibleSHA256SUMS.txtmanifest covering every installed file. Found and fixed a real bug in the manifest generation itself while building it:cmake --installnever wipes its destination, so a staleSHA256SUMS.txtleft by a previous run of the script was picked up by the file-listing glob with its OLD hash, then silently overwritten by the new manifest — a self-referential mismatch on every second run. Fixed by removing any existing manifest before globbing. Newclean_room_cli_release_smokeCTest (builds the archive fresh, extracts it intobuild-dir/clean room café— outside the build tree, with a space and a non-ASCII character in the name — then runstest/clean_room_cli_release_test.pyagainst the extracted copy withPATHset to/usr/bin:/binonly, i.e. no build-tree directory in the environment at all) verifies: both notices and the manifest are present; every manifest entry's hash matches the actual extracted file;--versionsucceeds for both tools; a real MC3→MCB and MC3→GLB conversion from the extracted binaries is byte-identical to the existing in-tree fixture hashes; and removinglibmanifold/libtinyobjloaderfrom a second copy of the extracted tree makesmc3togltffail cleanly (non-zero exit, not a hang) rather than silently succeeding or crashing with no diagnostic. Verified on both the root-project build and each tool's own standalone build (mc3tomcb/build,mc3togltf/build); all pass, and the pre-existing 3 Blender/numpyfailures inmc3togltf's standalone suite are unrelated (same environment gap asSYS-W9-06/SYS-W1-08's writeups). Added a "Stage release manifest and notices" step to thewindows-2022CI job (SHA-256 manifest + notices bundled into the uploaded artifact, same shape as the Linux archive). At the time this task was originally done,mc3togltf.exe'sSTATUS_DLL_NOT_FOUNDfailure was deliberately left for a separate pass; that gap (and the Windows clean-room verification this task's own writeup called "unverified — no Wine") is now closed bySYS-W11-06's later evidence — Wine does work in this sandbox for console binaries, and the underlying DLL problem is fixed by statically linking the standalone Windows build. SeeSYS-W11-06for the full verification. -
SYS-W11-09
[IN_PROGRESS]P1— Added first-party editor sanitizer CI, and it immediately found a real bug, confirming the whole point of doing this. RootCMakeLists.txtalready had-DMESHCRAFT_SANITIZE=ONwired up, butmeshcraft_apply_sanitize()was only ever called on the mainMeshCraftbinary — all 51 first-party editor test executables (everyadd_executable(..._test ...)in the rootCMakeLists.txt) silently built unsanitized even with the flag on. Added the call to all 51, right after each target's existingtarget_compile_features(... cxx_std_23)line. Toolchain finding: building the full editor under Clang fails compiling CNA's own source (CNA::Internal::JsonValueused as an incomplete type in astd::pair/std::vectorcontext) — unrelated to sanitizers, a plain compile error. The existing plaineditorCI job avoids this by using GCC, not Clang; the new sanitizer job does too (gcc/g++, matching), rather than touching CNA source (out of bounds here regardless). Locally verified the full build (875 steps) and the complete non-render suite under GCC ASan+UBSan (build-asan/,-DCMAKE_BUILD_TYPE=Debug, pinned CNAd0c21ee6/sharp-runtime5cdaafb2— the same revisions the plaineditorjob uses). First run: 10 of 182 tests failed. Triaged every one before touching anything:- 1 real bug, fixed:
EventPreviewRunner::execute()(include/MeshCraft/Editor/EventPreviewRunner.hpp) capturedscript, an iterator intoworking.scripts, then calledscriptRunner_.run(script->second.source, working, scriptTarget)-- which atomically replacesworkingvia move-assignment on success -- and afterward readscript->second.source.empty()again, a genuine heap-use-after-free ASan caught precisely. The very next lines in the same function already re-derivescriptTargetafter the same call with a comment explicitly naming this exact hazard for stale pointers into the replaced document graph; this one instance of the same pattern was missed. Fixed by capturing the source string by value before the call. - 4 known-shape leaks, fixed:
mc3_roundtrip,object_index,mc3_ai,mc3_commandseach deliberately construct a 2-cycle (or, inmc3_commands, also a 1-cycle self-reference) viashared_ptrchildrento prove a depth guard throws instead of crashing -- unfreeable by construction, an accepted by-design LeakSanitizer finding for the fixture, not a product bug (the SYS-W1-05/06/07 convention this codebase already uses for that class of test). Fixed all 4 occurrences the same way:.clear()the cyclicchildrenright after the assertion so the objects are actually freed, rather than a suppression. - 1 structural incompatibility, guarded out:
package_consumer_smokeproves a plain external consumer canfind_package()the installed Mc3/Mcb libraries with no special flags -- impossible when those.afiles are sanitizer-instrumented (the plain consumer never links__asan_*/__ubsan_*, so it fails to link). Guarded the test's ownadd_test()behindif(NOT MESHCRAFT_SANITIZE)inCMakeLists.txt, with a comment explaining why; registered and required in every other configuration. - 1 narrow third-party/system suppression: the one EASYGL render
smoke test (
smoke_test, under Xvfb) reports leaks that all trace intolibasan.soitself or an unknown stripped module -- Mesa/llvmpipe's software-rasterizer stack, not any first-party/CNA/sharp-runtime symbol anywhere in any of the 4 stacks. ConfirmedASAN_OPTIONS=detect_leaks=0makes it pass cleanly (no other finding hiding underneath); scoped that override to only this one CI step, not the job's leak detection overall. - 4 already-known, left alone: the
mc3_roundtrip/mc3_json_document_budget/3×mc3togltf_*_blender_importfindings from tonight's earlier CI-red survey (a separate cyclic-graph leak already covered above, the pre-existing Debug-build timeout, and the missing-numpyBlender gap) are unaffected by this task and left for the already-agreed separate pass. Re-ran the full non-render suite after all fixes: 181/181 pass (down from 182,package_consumer_smokenow correctly not registered in this configuration) except the 4 already-known failures above, confirmed unrelated. Re-verified the fixed test files build and pass in the non-sanitizer build too (cmake-build-debug,mc3/build) so none of these fixes regressed the normal configuration. Neweditor-sanitizerCI job (.github/workflows/ci.yml): checks out the same pinned CNA/sharp-runtime revisions as the plaineditorjob, builds withMESHCRAFT_SANITIZE=ON+ EASYGL, runsctest -LE render, then the one render smoke test with leak detection scoped off. Note for a future session:build-asan/(a stable, reusable directory per the top-level build-rules convention) is ~5.7 GB on disk after this verification; left in place for incremental reuse rather than deleted, since another session may want to re-verify against it.
Correction (2026-07-27, external review caught this): the paragraph above originally claimed the new CI job builds with
gcc/g++"matching" the plaineditorjob — false. The job usedclang/clang++from its very first commit; only the local verification (build-asan/, the 181/181 result above) actually used GCC. This is now fixed for real (CC: gcc-14/CXX: g++-14inci.yml, not just corrected prose), but downgrading this task's status to[IN_PROGRESS]: theeditor- sanitizerCI job itself has never had a fully green run on real CI — every run through 2026-07-27 failed, first on unrelated environment gaps (fixed separately, see the CI-red session log inNEXT.md), now onSYS-W8-08'sShaderEffectmismatch (originally misattributed tosharp-runtime; actually CNA — seeSYS-W8-08's own entry). Confirmed on the very next CI run after the GCC switch: configure now succeeds with no Clang/CNA incompatibility at all, and the build failed on the exact sameShaderEffect::setWorldProperty/etc. errors as the plaineditorjob'sVULKANentry — consistent, compiler-independent evidence that this wasn't a toolchain issue.SYS-W8-08is now fixed (CNA pin bumped) and confirmed gone on real CI, but a different CNA-internal build failure took its place, specific to this sanitizer job — seeSYS-W8-09. This job remains[IN_PROGRESS]until that's resolved too. The code fixes described above (heap-use-after-free, 4 leak fixtures,package_consumer_smokeguard,meshcraft_apply_sanitize()wiring) are real and independently verified locally; only the CI-job-itself claim was wrong. Re-promote to[DONE]once a realeditor-sanitizerrun is green. - 1 real bug, fixed:
-
SYS-W11-10
[DONE]P2— Wired one authoritative version source: a new root-levelVERSIONfile (currently0.1.0, the existing value — deliberately not changed; deciding the actual first-release version number is a product decision, not made here, seeSYS-W11-11below), read viafile(STRINGS ...)before each of the 5project(... VERSION ...)calls (root,mc3/,mcb/,mc3togltf/,mc3tomcb/— each reads../VERSIONrelative to its ownCMAKE_CURRENT_SOURCE_DIR, which resolves correctly whether built standalone or as a root subdirectory). Found and fixed a real, previously-undetected drift risk while wiring this:mcb/cmake/McbConfig.cmake.inhardcodedfind_dependency(Mc3 0.1.0 CONFIG)— a second, independent copy of the version number thatconfigure_package_config_file()never touched, so it would have silently gone stale the next time the version changed. Now substitutes@PROJECT_VERSION@. Also updatedtest/cmake/package_consumer/CMakeLists.txt(an external-consumer smoke test that would otherwise hardcode its own pin, same drift risk) to read the same shared file.--version(both CLI tools and the main editor), package configs, and the CLI release archive name all confirmed to correctly reflect the shared source with no further changes needed — they already read${PROJECT_VERSION}/MESHCRAFT_VERSIONderived from it.CHANGELOG.md's existing## [Unreleased] — 0.1.0heading already matches; left alone (prose, not a second machine-read source, so it cannot drift the same way). Verified: all 5project()calls configure cleanly (standalonemc3/mcb/mc3togltf/mc3tomcband the root project),mc3tomcb --version/mc3togltf --version/MeshCraft --versionall report0.1.0,package_consumer_smokeandclean_room_cli_release_smokeboth still pass end to end, and the release archive is still namedMeshCraft-0.1.0-Linux-cli.tar.gz. Deliberately not done here — a product decision, not mechanical plumbing: deciding whether the first public release is0.1.0,1.0.0, or another version, and actually executing an RC process end to end. See the newSYS-W11-11below. -
SYS-W11-11
[BLOCKED]P2— Execute the first release candidate, once the version-number decision below is made. Produce an RC artifact set from the now-unified version source (SYS-W11-10), executeRELEASE.md's checklist for real, record the exact supported platform/backend matrix, and distinguish unsupported platforms from temporarily blocked qualification. Blocked on a decision only the user can make: is the first public release0.1.0(honest about remaining platform/qualification gaps —SYS-W8-05,SYS-W8-06,AUD-042,SYS-W11-06's Windows evidence, this session's own left-for-later CI-red survey) or1.0.0(implying those gaps are closed first)? Bumping the rootVERSIONfile once decided updates all 5 projects,--version, package configs, and archive names in one place (SYS-W11-10); nothing else needs to change.
-
SYS-W3-03
[DONE]P2—RegistryWorkspacenow owns the registry database, filters, cached results, AI-save lifecycle, save buffers, and asset-pack export state. It reduced 20 direct registry fields inMeshCraftApplicationto one workspace member. The workspace's ImGui frame receives explicit insertion/status callbacks; the application still owns undo, scene-instance placement, import refresh, and notifications. The new CNA-freeregistry_workspacetest covers AI dialog ownership/lifecycle, while the existing insertion/SQLite/no-SQLite tests preserve registry data behavior. No application-wide context object was introduced. -
SYS-W3-04
[DONE]P3—AutomationWorkspaceowns the four resource tab selections, Lua runner, Preview/Play lifecycle/reports, and scene-state mutation semantics. Scripts, Triggers, States, and Events are all rendered inAutomationWorkspaceUi.cppthrough explicit undo, selection, lookup, playback, and notification callbacks; no application-wide context was added.LeftPanel.cppis reduced from 2,808 to 2,203 lines. The CNA-freeautomation_workspacetest locks workspace lifecycle/state application;trigger_fireadditionally proves a post-Lua trigger step remains safe after the atomic document swap. -
SYS-W3-05
[DONE]P2— SplitEditorAlgorithms.hpp(2,514 lines, CNA-free) into 7 cohesive headers underinclude/MeshCraft/, grouped by verified cross-reference rather than blind guessing:EditorCommandAlgorithms.hpp(857 lines — rename/duplicate/group/ungroup/convert-to-definition/ break-instance, F9/F10 reference cleanup, undo-stack cap, coordinate/rotation normalization; also absorbs the two tree helpersfindParentListAlg/removeFromListAlg, moved here from the originally-proposed Selection group once grep showed every one of their 10 call sites is a Commands- or Transform-group mutator, never Selection),EditorSelectionAlgorithms.hpp(326 — select-children, lock-aware dry-run, STAB-0503 ray-cast picking, hierarchy filter),EditorTransformAlgorithms.hpp(363 — align/scatter/ falloff/vertex-snap/rotate-drag/group-scale/camera presets; depends on Commands forfindParentListAlg+deepCopyObjectAlgviascatterAlongCurveAlg),EditorPersistenceAlgorithms.hpp(479 — autosave/ backup/merge/Save-As/export-selection-or-template-or-material/drag-drop/ recent-files; depends on Commands fordeepCopyObjectAlgviaexportSelectionAlg),EditorEventAlgorithms.hpp(308 — AI panel lifecycle, keyframe insertion, SYS-W3-01 Phase 5 anim-override eval/blend; depends on Utility forresolveObjectPropertyValueAlgviainsertAnimKeyframesAlg),EditorPreferencesAlgorithms.hpp(238 — keybind/prefs/macro persistence formats; the one group with zero Mc3 dependency at all),EditorUtilityAlgorithms.hpp(108 — live object-property resolution, material-color fallback). Every function/struct body is byte-identical to the original (verified with a line-range diff against each new file before deleting it — only intentional blank-line reformatting differs); 2,679 total lines vs. 2,514 (+165, entirely the 7x duplicated#pragma once/doc-comment/#includepreamble, not duplicated logic). Fan-out evidence: of the 30 files the initial grep flagged, 5 were comment-only false positives needing no change (ObjectTypeName.hpp,ObjectIndex.cpp,AiResponseAlgorithms.hpp,registry_insert_undo_test.cpp,MeshCraftPrivate.hpp— each only mentionsEditorAlgorithms.hppin a comment, never#includes it) andMacro.cpp's include was entirely dead (zero symbols used) and removed with no replacement. Of the 25 genuine consumers: 14 need exactly 1 new header, 8 need 2, 1 (coordinate_system_test) needs 3, and 1 (Commands.cpp, the largest consumer) needs 4 — so 60% of real consumers now pull one narrow ~100–860 line module instead of the former 2,514-line monolith, and even the heaviest consumer pulls at most 4 of 7. No facade was left behind;include/MeshCraft/EditorAlgorithms.hppis deleted. Compiler-driven verification: the by-call-site symbol grep against every candidate header was accurate on the first try —MeshCraft(30 sources) built with zero missing-include errors, as did all 13 affected test targets (coordinate_system_test,texture_from_path_test,animation_preview_algorithms_test,delete_reference_integrity_test,registry_insert_undo_test,macro_recorder_test,undo_manager_test,scene_history_test,automation_workspace_test,camera_bookmarks_test,lua_script_runner_test,trigger_fire_test,event_preview_runner_test).ctest -LE render: 181 tests, 163 passed. Failures are exactly the two pre-authorized, unrelated exceptions (mc3_json_document_budgetDebug-build timeout; the 3mc3togltf_*blender_importtests, blocked on this sandbox's Blender lackingnumpy) plus 14mcb_*/mc3togltf_*tests reported "Not Run" solely because their binaries had never been built in this reusedcmake-build-debug— spot-built 3 of them (mc3_invalid_utf8,mcb_corruption,mc3togltf_csg_null_child) and all passed unchanged with no other recompilation triggered, confirming this is a pre-existing build-directory gap (none of the 14 consume anyEditor*Algorithms.hppheader), not a regression; left unbuilt per this task's explicit mc3togltf/mc3tomcb-out-of-scope instruction. Zero real test regressions. -
SYS-W3-06
[PROPOSED]P2— A single-source-of-truth "primitive descriptor" registry (display name, tessellation clamp, MCB enum-count bound) for eachObjectType/PrimitiveType, replacing today's ~4 independently-duplicated switch/magic-number sites. Found 2026-07-27 while researching how much code a brand-new primitive type touches: adding one today requires edits across ~25 sites in 8 subsystems (enum/struct, XML/ JSON/MCB parser+writer, 2 independent mesh tessellators — the exporter'sMeshBuilder.cppand the live-viewport'sSceneRenderer_Builders.cpp— CSG, 6+ editor-UI surfaces, and docs). This is not a hypothetical concern:include/MeshCraft/Editor/ObjectTypeName.hpp's own header comment documents a real incident where two divergent copies ofobjectTypeName()both silently mishandled 5 primitive types (Torus, Capsule, Disk, Grid, IcoSphere), so macro record/replay silently turned a Torus into a Box. Deliberately scoped: a registry for the serialization-adjacent metadata (name/clamp/enum-bound) would eliminate that class of bug; it would NOT and should not try to unify the two independent mesh-tessellation implementations, which genuinely differ per rendering backend and would need a much larger, separately-justified refactor to merge. Not started — needs the usual per-task confirmation before implementation. -
SYS-W14-41
[PROPOSED]P3—MenuBar::drawFileImportObj/drawFileImportGlb(src/MeshCraft/Application/UI/MenuBar.cpp:651-656) open a dialog with a plain ImGui text buffer for the file path (importObjDialogBuf_/importGlbDialogBuf_) — no native OS file-picker "Browse..." button, unlike the material-texture Browse flow elsewhere in the editor (AUD-era F9's native-dialog integration). OS-level drag-and- drop (SDL_EVENT_DROP_FILE) already works as the low-friction path; this task is only about the fallback dialog for when drag-drop isn't convenient (e.g. the file isn't visible in an open file-manager window). Low-risk, UI-only change — reuse the existing native-dialog helper rather than adding a new file-picker dependency. Not started.
-
SYS-W9-05
[DONE]P2— Automatic history and exact undo now retain oneshared_ptr<const Mc3Document>frozen from a single deep copy. Each undo/redo/history restore still creates a fresh writable deep copy before editor mutation, with selection restoration and existing bounds unchanged. The 1,000-object unit fixture verifies shared ownership, independent restoration, and 2,098,753 logical retained bytes versus the prior 4,197,506 duplicate-graph model; it also records an informational attach time without a fragile wall-clock threshold. Undo/redo/history and registry insertion regressions passed. -
SYS-W9-06
[DONE]P0— Added one portable atomic-file replacement primitive,MeshCraft::Mc3::writeFileAtomically()+uniqueSiblingTempPath()(mc3/include/MeshCraft/Mc3/Mc3AtomicFileWriter.hpp,mc3/src/Mc3AtomicFileWriter.cpp), and replaced the four independently duplicated fixed-<destination>.tmp-name write-then-rename call sites (Mc3XmlWriter.cpp,Mc3JsonWriter.cpp,McbWriter.cpp,GltfExporter.cpp's GLB path) with calls to it. Each candidate temp name combines a monotonic atomic counter with a steady-clock reading, so concurrent saves of the same destination never collide, and a caller requesting a fresh name automatically skips past any stale leftover temp file rather than failing. The finalizingrename()retries up to 5 times (20ms apart) to absorb a transient Windows sharing violation; on irrecoverable finalize failure it throwsAtomicFinalizeError(distinct from a writer-stage exception, which propagates unchanged) and always removes the temp file first, leaving any pre-existing destination untouched. Newmc3/test/atomic_write_test.cpp(registered asmc3_atomic_write) covers first save, overwrite-existing, a Unicode destination path, injected writer failure (with and without a pre-existing destination), a genuine finalize-stage failure (renaming onto an existing directory, which throwsAtomicFinalizeErrorspecifically), a stale leftover<dest>.tmpfile not blocking a new save, and two concurrent calls producing distinct temp names. All 26 standalonemc3tests, all 11 standalonemcbtests, and 75/78 standalonemc3togltftests passed (the remaining 3 —mc3togltf_blender_importand its two PBR/release-sample variants — fail only on a pre-existing, unrelated environment gap: this sandbox's Blender lacks thenumpymodule its glTF importer needs; not a regression from this change). The full root-projectMeshCrafteditor target, plus the registry/undo/obj-export-cleanup tests that exerciseMc3Document::saveToFile()/registry save paths transitively, also built and passed. Update (2026-07-27): native Windows overwrite qualification is no longer unverified — Wine does run console binaries in this sandbox after all, and a real MinGW+Wine repro ofmc3_atomic_writepassed cleanly (seeSYS-W11-06's later evidence), plus this primitive's finalize-retry budget was separately widened after a real Windows CI run hit a slower-than-expected transient lock (unrelated Unicode-path bug, also fixed — seeNEXT.md). Editor-side direct-write config files (preferences/keybindings/macros/ recent-files) were surveyed and found to have the same unprotected- direct-write shape, but are intentionally left out of this task's scope — regenerable config is a materially lower-severity gap than scene-file data loss, and folding them in here would have widened one coherent task into an unrelated sweep across the editor. -
SYS-W9-07
[DONE]P1— Added crash recovery for never-saved ("Untitled") documents.performUntitledRecoverySave()writes to one bounded slot,untitledRecoveryPath()(MeshCraftPrivate.hpp,meshcraftConfigDir() / "untitled.recovery.mc3.xml"), on its ownautoSaveTickAlgcountdown (autoSaveUntitledCountdown_,hasCurrentFileinverted) — reuses the existing tested tick function unchanged rather than touchingperformAutoSave()'s own tested "no current file never auto-saves" gating.checkForUntitledRecovery()runs once at startup right after a freshnewScene()(Application.cpp'sLoadContent(), the no-file-argument branch) and offers a new modal dialog ("Recover Unsaved Scene",Overlays.cpp) with Recover / Discard / Not Now.recoverUntitledScene()keepscurrentFile_empty, setsmodified_ = true, and never callsaddRecentFile(). The recovery file is removed on: successful Save As (both the plain dialog and Save-as-Library, since either can be an untitled document's first save), explicit Discard, and ordinary (non-crash) shutdown (~MeshCraftApplication(), unconditional whenevercurrentFile_is still empty — there is no quit-confirmation gate in this app, so reaching a clean exit while modified already means the user chose not to save); a real crash skips the destructor, which is what leaves the file for the next startup to find. Newmc3_untitled_recovery(CNA-free, mirrorsmc3_autosave_recovery's own established "App-level methods aren't headlessly testable, the filesystem+Mc3 mechanism they reduce to is" scope) covers: a real document's content round-tripping through the recovery path, discard's remove()-succeeds postcondition, a crash-marker simulation (a file left behind is detectable), a corrupt recovery file throwing catchably instead of crashing, and coexistence with the existing named-file.autosavesibling (disjoint paths, independent content, verified together in one temp directory). The App-level flow itself (dialog wiring, destructor timing,currentFile_/modified_transitions) was verified by full editor build success and code review only, not a live screenshot — same CNA-coupled-and-not-headlessly-testable class as this session's F20/F21 precedent, not a lower bar invented for this task.
- SYS-W14-40
[DONE]P2— Added explicit bounded Event Preview/Play. It alone dispatches timer bindings, Walk Mode Area enter/exit transitions, and viewport-picked object clicks; ordinary editing remains inert. The CNA-freeEventPreviewRunnerretains enabled/cooldown/one-shot/32-dispatch eligibility and computes Area bounds through the shared nested-transform, pivot, rotation-unit, and Euler-order path. Trigger steps, states, and Lua scripts run on one deep isolated document copy, validate as a batch, and publish through one undo/history boundary. A failed script restores runtime eligibility, leaves document/selection/dirty/undo/history untouched, and suppresses staged action/audio effects.event_preview_runnercovers Area, timer, click, state, successful commit, invalid state, and failed-script rollback; the EasyGL Debug editor built successfully.
-
SYS-W8-05
[BLOCKED]P1— Broad alternate-backend editor qualification requires real backend screenshot/device evidence and CNA owner coordination; it is not a backend-name switch. -
SYS-W8-06
[PROPOSED]P1— Web editor and IDBFS end-to-end qualification, split out fromSYS-W8-05because it has its own specific blocker and persistence contract rather than a generic backend-name gap. Confirmed: the Emscripten pre-JS mount/syncfs IDBFS implementation exists and is linked (-lidbfs.js), butdocs/CAPABILITY_MATRIX.mdandREADME.mdboth already record that real persistence and editor usability are unverified because the CNA web resize failure prevents a stable session — this task tracks removing that caveat with evidence, not re-implementing IDBFS. Unblock requirements: a CNA revision that survives initial resize; successful Emscripten configure/build; headless- browser editor startup; a persisted preference across reload; recovery-file persistence; and one GLB export/download smoke test. -
SYS-W8-07
[BLOCKED]P2—sharp-runtime's own source fails to compile under the Editor (VULKAN) and Editor (EASYGL) (non-sanitizer) CI jobs:sharp-runtime/src/System/Environment.cpp:307ignoreschdir()'s return value, which-Werror=unused-result(enabled by these jobs' compiler flags) turns into a hard build failure. Confirmed via a real 2026-07-27 CI run, after fixing 5 unrelated CI-environment gaps in this same session let the Editor jobs progress this far for the first time (missing X11/XTEST/FFmpeg dev packages, and missingeasy-gl/meta-glsibling checkouts — all fixed in.github/workflows/ci.yml, seeNEXT.mdfor the full trail). Blocked: the fix is one line insidesharp-runtime's own source (checkchdir()'s return value, or cast tovoidif truly don't-care), a sibling repository outside this session's authorized scope (CLAUDE.md: no changes to CNA/sharp-runtime without owner permission) — user explicitly chose to report and defer, not fix, when asked 2026-07-27. -
SYS-W8-08
[DONE]P2— The Editor (EASYGL) ASan+UBSan CI job failed to build for a different reason thanSYS-W8-07above: mesh-craft's ownsrc/MeshCraft/Renderer/SceneRenderer.cpp(multiple call sites, e.g. line 875) callsShaderEffect::setWorldProperty/setViewProperty/setProjectionProperty/SetTexture, surfaced for the first time by the same 2026-07-27 CI run asSYS-W8-07. Corrected misdiagnosis: the initial writeup blamedsharp-runtimefor this (matching theMicrosoft::Xna::Frameworknamespace convention) — that was wrong.ShaderEffectis actually implemented in CNA (cna/include/Microsoft/Xna/Framework/Graphics/ShaderEffect.hpp), notsharp-runtime. Confirmed withgit show <ref>:<path>directly against the CI-pinned CNA commit (d0c21ee613ebd8f8b0055f7868103dd5c5c30fc7): none of the 4 methods existed there at all. Further confirmed that pin was not even an ancestor of CNA's currentdevelopbranch (git merge-base --is-ancestor d0c21ee6 <current-HEAD>→ false) — its history was rewritten after that commit was recorded (the same class of issueSYS-W13-03's AUD-064..073 hash-orphaning finding hit earlier this project). The methods were added by CNA's own Task 1079 (commitb08c7aa88f6ccbdf9afae3a25d9bd25bb9620f4d, "wire ShaderEffect into GraphicsDevice's 3D draw path", 2026-07-16) — well after the stale pin's 2026-07-11 recording. Fix: bumped the CI-pinned CNA revision (both editor jobs in.github/workflows/ci.yml) fromd0c21ee6to CNA's currentdeveloptip (ac3aaaeb2a5ba27dbd9e22e782c7041e6e40947c) — a mesh-craft-side CI configuration change, not a CNA source edit, so within this session's authorized scope even under the no-CNA-changes-without-permission rule. User explicitly confirmed this pin bump before it was made. Verified locally before pushing: fullMeshCrafteditor rebuilt clean against this CNA revision (already the sandbox's own local../cnacheckout), and the non-render suite passed 176/180 (the 4 failures are the already-known unrelated Blender/numpygap). Confirmed on the real CI run right after pushing: theShaderEffecterror is completely gone from bothEditor (EASYGL) ASan+UBSanandEditor (VULKAN)— this task is genuinely done, not just locally verified. -
SYS-W8-09
[BLOCKED]P2— Bumping the CNA pin forSYS-W8-08above unmasked a different CNA-internal build failure, only onEditor (EASYGL) ASan+UBSan:cna/include/CNA/Internal/Xnb/DecimalDateTimeContentTypeReaders.hpp:32callsContentReader::ReadDecimal(), which the runner's compiler rejects as not existing. Deliberately not chased further — this is not locally reproducible despite genuinely trying: an isolated single-file compile, a fullCNAtarget rebuild under the exact same flags (MESHCRAFT_SANITIZE=ON,g++-14, same CNA revision), and a ccache-cleared from-scratch rebuild of that target all succeeded cleanly in this sandbox. The method call itself is real (DecimalReader::Read()in the same header, guarded#if !defined(_MSC_VER), matching this GCC-only job), so this looks like a genuine GCC-version-sensitive difference between this sandbox'sg++-14and the GitHub Actionsubuntu-24.04runner'sg++-14(or some other runner-specific factor this session couldn't isolate) rather than a simple missing-method bug.Editor (VULKAN)/Editor (EASYGL)(non-sanitizer) don't hit this at all — they still fail earlier, onSYS-W8-07's unrelatedchdir()issue. Needs a future session with either CI-side debug instrumentation (print the exact GCC version, dump preprocessed output as a build artifact) or owner-side CNA investigation — deliberately not guessed at further here.
Net across all 2 AUD-### rows in this active plan: 0 DONE, 0 TODO, 1 DEFERRED, 1 BLOCKED. Completed audit evidence remains in the archives.
- Component:
UndoManagerand the editor history flow. - Evidence: Undo intentionally keeps 20 whole-document deep-copy entries; oldest snapshots are evicted. This is a known memory/retention trade-off, not a currently unbounded growth bug.
- Outcome: Retain the explicit product limit.
SYS-W9-05may later reduce duplicate ownership without changing user-visible restore semantics.
- Component: Android CMake/Gradle route, CNA EASYGL backend, and sibling
sharp-runtime. - Evidence: The source path selects GLES/EASYGL, but an Android NDK, repaired sibling-runtime cross-build, packaging environment, and device or emulator evidence are unavailable in this checkout.
- Outcome: Do not claim Android release readiness until configure, build, package, and device/emulator smoke verification are reproducible.
- Run
python3 test/validate_plan_consistency.py .after planning changes. - Keep local build/test parallelism at four jobs or fewer.
- Do not change sibling
cnaorsharp-runtimerepositories here. - Do not change the public
Mc3DocumentAPI without checking the editor,mc3togltf,mc3tomcb, and all format fixtures. - A fresh full build was not independently reproduced for this review: the editor needs sibling repositories and standalone FetchContent could not reach its network dependency. That is an environment limitation, not a source-code failure claim.