0.5.0 — an island's entry points arrive in the module's own namespace
docs/42 states one rule for both lanes: the module name and the namespace are one identifier path. The shader lane followed it; mcpp.tools.island did not, and emitted every entry point at global scope, so import app.kernels bought a file name and nothing else.
The names
A root's directories extend the namespace exactly as a payload tree's do:
src/backends/cuda/image/blur.cu myapp_blur -> myapp::kernels::image::myapp_blur
src/backends/cuda/saxpy.cu myapp_saxpy -> myapp::kernels::myapp_saxpy
One import, one flat header. The header keeps no namespace: it is read by a C or a device compiler.
The check that makes them honest
A namespace over a flat symbol is a lookup alias. Measured with clang++ (DPC++ 7.1.0), -std=c++23: two modules re-exporting one extern "C" name into two namespaces give two spellings of one entity, &a::f == &b::f. So one name declared twice in one root is refused, naming both files and saying that two implementations of one entry point belong in two roots.
Roots, not a file list
options::roots names the directories implementations live under; options::layout_root names the one whose structure decides where entry points live and defaults to the first. Every other root only has to define the same names, so a fallback tree may be one flat file and may be reorganised without renaming anything a consumer wrote.
A file list's common ancestor moves when a file is added. Roots must also not come from mcpp::device_sources(), which accel narrows to nothing under --no-accel.
scan registers every file it reads and each root as a glob, so adding a file re-runs the build program.
options::strip_prefix
Emits a short spelling beside the authored name, which stays canonical. A constexpr function pointer costs the artifact nothing: the pair is one symbol.
One sanitiser
common_base_dir and namespace_of had been written in rules/spirv.cppm and again in rules/slang.cppm. They move to mcpp::plugins::names with the identifier sanitiser, so a directory named default or 2d gets one answer.
Compatibility
Breaking. A consumer writes app::kernels::saxpy_device(...) where it wrote saxpy_device(...). The floor does not move: this changes what the package generates, not what it asks the engine for. Requires mcpp 2026.9.8.1.